|
|
/*!
|
|
|
* depd
|
|
|
* Copyright(c) 2014-2018 Douglas Christopher Wilson
|
|
|
* MIT Licensed
|
|
|
*/
|
|
|
|
|
|
/**
|
|
|
* Module dependencies.
|
|
|
*/
|
|
|
|
|
|
var relative = require('path').relative
|
|
|
|
|
|
/**
|
|
|
* Module exports.
|
|
|
*/
|
|
|
|
|
|
module.exports = depd
|
|
|
|
|
|
/**
|
|
|
* Get the path to base files on.
|
|
|
*/
|
|
|
|
|
|
var basePath = process.cwd()
|
|
|
|
|
|
/**
|
|
|
* Determine if namespace is contained in the string.
|
|
|
*/
|
|
|
|
|
|
function containsNamespace (str, namespace) {
|
|
|
var vals = str.split(/[ ,]+/)
|
|
|
var ns = String(namespace).toLowerCase()
|
|
|
|
|
|
for (var i = 0; i < vals.length; i++) {
|
|
|
var val = vals[i]
|
|
|
|
|
|
// namespace contained
|
|
|
if (val && (val === '*' || val.toLowerCase() === ns)) {
|
|
|
return true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Convert a data descriptor to accessor descriptor.
|
|
|
*/
|
|
|
|
|
|
function containsNamespace(str, namespace) {
|
|
|
// 使用正则表达式将字符串按空格或逗号分割成数组
|
|
|
var vals = str.split(/[ ,]+/);
|
|
|
// 将命名空间转换为小写以进行不区分大小写的比较
|
|
|
var ns = String(namespace).toLowerCase();
|
|
|
|
|
|
// 遍历分割后的数组
|
|
|
for (var i = 0; i < vals.length; i++) {
|
|
|
var val = vals[i];
|
|
|
|
|
|
// 如果 val 存在,并且等于 '*' 或者等于指定的命名空间,则返回 true
|
|
|
if (val && (val === '*' || val.toLowerCase() === ns)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没有找到匹配的命名空间,则返回 false
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 将数据描述符转换为访问器描述符。
|
|
|
* @param {Object} obj - 要修改的对象。
|
|
|
* @param {string} prop - 要转换的属性名。
|
|
|
* @param {string} message - 相关消息(未使用)。
|
|
|
* @returns {Object} - 返回转换后的描述符。
|
|
|
*/
|
|
|
function convertDataDescriptorToAccessor(obj, prop, message) {
|
|
|
// 获取对象的属性描述符
|
|
|
var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
|
|
// 获取属性的值
|
|
|
var value = descriptor.value;
|
|
|
|
|
|
// 定义 getter 方法,返回属性的值
|
|
|
descriptor.get = function getter() { return value; };
|
|
|
|
|
|
// 如果属性是可写的,则定义 setter 方法
|
|
|
if (descriptor.writable) {
|
|
|
descriptor.set = function setter(val) { return (value = val); };
|
|
|
}
|
|
|
|
|
|
// 删除原有的值和可写性描述符
|
|
|
delete descriptor.value;
|
|
|
delete descriptor.writable;
|
|
|
|
|
|
// 使用新的描述符重新定义对象的属性
|
|
|
Object.defineProperty(obj, prop, descriptor);
|
|
|
|
|
|
// 返回转换后的描述符
|
|
|
return descriptor;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Create arguments string to keep arity.
|
|
|
*/
|
|
|
|
|
|
/**
|
|
|
* 检查给定的字符串中是否包含指定的命名空间。
|
|
|
* @param {string} str - 要检查的字符串,可能包含多个命名空间。
|
|
|
* @param {string} namespace - 要查找的命名空间。
|
|
|
* @returns {boolean} - 如果字符串中包含命名空间,则返回 true;否则返回 false。
|
|
|
*/
|
|
|
function containsNamespace(str, namespace) {
|
|
|
// 使用正则表达式将字符串按空格或逗号分割成数组
|
|
|
var vals = str.split(/[ ,]+/);
|
|
|
// 将命名空间转换为小写以进行不区分大小写的比较
|
|
|
var ns = String(namespace).toLowerCase();
|
|
|
|
|
|
// 遍历分割后的数组
|
|
|
for (var i = 0; i < vals.length; i++) {
|
|
|
var val = vals[i];
|
|
|
|
|
|
// 如果 val 存在,并且等于 '*' 或者等于指定的命名空间,则返回 true
|
|
|
if (val && (val === '*' || val.toLowerCase() === ns)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没有找到匹配的命名空间,则返回 false
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 将数据描述符转换为访问器描述符。
|
|
|
* @param {Object} obj - 要修改的对象。
|
|
|
* @param {string} prop - 要转换的属性名。
|
|
|
* @param {string} message - 相关消息(未使用)。
|
|
|
* @returns {Object} - 返回转换后的描述符。
|
|
|
*/
|
|
|
function convertDataDescriptorToAccessor(obj, prop, message) {
|
|
|
// 获取对象的属性描述符
|
|
|
var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
|
|
// 获取属性的值
|
|
|
var value = descriptor.value;
|
|
|
|
|
|
// 定义 getter 方法,返回属性的值
|
|
|
descriptor.get = function getter() { return value; };
|
|
|
|
|
|
// 如果属性是可写的,则定义 setter 方法
|
|
|
if (descriptor.writable) {
|
|
|
descriptor.set = function setter(val) { return (value = val); };
|
|
|
}
|
|
|
|
|
|
// 删除原有的值和可写性描述符
|
|
|
delete descriptor.value;
|
|
|
delete descriptor.writable;
|
|
|
|
|
|
// 使用新的描述符重新定义对象的属性
|
|
|
Object.defineProperty(obj, prop, descriptor);
|
|
|
|
|
|
// 返回转换后的描述符
|
|
|
return descriptor;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Determine if namespace is ignored.
|
|
|
*/
|
|
|
|
|
|
|
|
|
// 生成一个唯一的键,用于标识调用者的弃用消息
|
|
|
var key = caller
|
|
|
? depSite.join(':') + '__' + caller.join(':') // 如果有调用者,组合依赖站点和调用者信息
|
|
|
: undefined; // 如果没有调用者,键为 undefined
|
|
|
|
|
|
// 检查该键是否已经被警告过
|
|
|
if (key !== undefined && key in this._warned) {
|
|
|
// 如果已经发出警告,则提前返回
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 将该键标记为已警告
|
|
|
this._warned[key] = true;
|
|
|
|
|
|
// 生成自动弃用消息
|
|
|
var msg = message; // 首先使用提供的消息
|
|
|
if (!msg) {
|
|
|
// 如果没有提供消息,则根据调用位置生成默认消息
|
|
|
msg = callSite === depSite || !callSite.name
|
|
|
? defaultMessage(depSite) // 如果调用位置与依赖站点相同或没有名称,使用依赖站点生成默认消息
|
|
|
: defaultMessage(callSite); // 否则,使用调用位置生成默认消息
|
|
|
}
|
|
|
|
|
|
// 如果存在监听器,则发出弃用事件
|
|
|
if (haslisteners) {
|
|
|
// 创建一个弃用错误对象并发出事件
|
|
|
var err = DeprecationError(this._namespace, msg, stack.slice(i));
|
|
|
process.emit('deprecation', err); // 发出 'deprecation' 事件
|
|
|
return; // 结束函数
|
|
|
}
|
|
|
|
|
|
// 格式化并写入消息
|
|
|
var format = process.stderr.isTTY // 检查标准错误输出是否为 TTY
|
|
|
? formatColor // 如果是 TTY,使用彩色格式
|
|
|
: formatPlain; // 否则,使用普通格式
|
|
|
var output = format.call(this, msg, caller, stack.slice(i)); // 格式化输出消息
|
|
|
process.stderr.write(output + '\n', 'utf8'); // 将消息写入标准错误输出
|
|
|
|
|
|
/**
|
|
|
* 获取调用位置的文件位置作为数组。
|
|
|
*/
|
|
|
function callSiteLocation(callSite) {
|
|
|
var file = callSite.getFileName() || '<anonymous>'; // 获取文件名,如果没有则标记为 '<anonymous>'
|
|
|
var line = callSite.getLineNumber(); // 获取行号
|
|
|
var colm = callSite.getColumnNumber(); // 获取列号
|
|
|
|
|
|
// 如果调用位置是 eval,则获取 eval 的原始位置
|
|
|
if (callSite.isEval()) {
|
|
|
file = callSite.getEvalOrigin() + ', ' + file; // 组合 eval 原始位置和文件名
|
|
|
}
|
|
|
|
|
|
var site = [file, line, colm]; // 创建位置数组
|
|
|
|
|
|
site.callSite = callSite; // 将原始调用位置附加到数组
|
|
|
site.name = callSite.getFunctionName(); // 获取函数名称并附加
|
|
|
|
|
|
return site; // 返回位置数组
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 从调用位置生成默认消息。
|
|
|
*/
|
|
|
function defaultMessage(site) {
|
|
|
var callSite = site.callSite; // 获取调用位置
|
|
|
var funcName = site.name; // 获取函数名称
|
|
|
|
|
|
// 为匿名函数生成有用的名称
|
|
|
if (!funcName) {
|
|
|
funcName = '<anonymous@' + formatLocation(site) + '>'; // 如果没有函数名,生成匿名名称
|
|
|
}
|
|
|
|
|
|
var context = callSite.getThis(); // 获取调用上下文
|
|
|
var typeName = context && callSite.getTypeName(); // 获取类型名称
|
|
|
|
|
|
// 忽略无用的类型名称
|
|
|
if (typeName === 'Object') {
|
|
|
typeName = undefined; // 如果类型名称是 'Object',则设置为 undefined
|
|
|
}
|
|
|
|
|
|
// 为有用的类型名称
|
|
|
if (typeName === 'Function') {
|
|
|
typeName = context.name || typeName; // 如果上下文是函数,使用上下文的名称
|
|
|
}
|
|
|
|
|
|
// 生成默认消息
|
|
|
return typeName && callSite.getMethodName()
|
|
|
? typeName + '.' + funcName // 如果有类型名称和方法名称,组合成 '类型名.函数名'
|
|
|
: funcName; // 否则,仅返回函数名称
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Format deprecation message without color.
|
|
|
*/
|
|
|
|
|
|
/**
|
|
|
* 格式化普通的弃用消息。
|
|
|
* @param {string} msg - 弃用消息。
|
|
|
* @param {Object} caller - 调用者信息。
|
|
|
* @param {Array} stack - 堆栈跟踪数组。
|
|
|
* @returns {string} - 格式化后的弃用消息。
|
|
|
*/
|
|
|
function formatPlain(msg, caller, stack) {
|
|
|
var timestamp = new Date().toUTCString(); // 获取当前时间的 UTC 字符串表示
|
|
|
|
|
|
// 组合格式化的消息,包括时间戳、命名空间和弃用消息
|
|
|
var formatted = timestamp +
|
|
|
' ' + this._namespace +
|
|
|
' deprecated ' + msg;
|
|
|
|
|
|
// 添加堆栈跟踪
|
|
|
if (this._traced) {
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
formatted += '\n at ' + stack[i].toString(); // 将每个堆栈条目添加到消息中
|
|
|
}
|
|
|
|
|
|
return formatted; // 返回格式化后的消息
|
|
|
}
|
|
|
|
|
|
// 如果有调用者信息,则添加调用者位置
|
|
|
if (caller) {
|
|
|
formatted += ' at ' + formatLocation(caller); // 格式化调用位置并添加
|
|
|
}
|
|
|
|
|
|
return formatted; // 返回最终格式化后的消息
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 使用颜色格式化弃用消息。
|
|
|
* @param {string} msg - 弃用消息。
|
|
|
* @param {Object} caller - 调用者信息。
|
|
|
* @param {Array} stack - 堆栈跟踪数组。
|
|
|
* @returns {string} - 格式化后的彩色弃用消息。
|
|
|
*/
|
|
|
function formatColor(msg, caller, stack) {
|
|
|
// 组合格式化的消息,使用 ANSI 转义码为不同部分添加颜色
|
|
|
var formatted = '\x1b[36;1m' + this._namespace + '\x1b[22;39m' + // 粗体青色命名空间
|
|
|
' \x1b[33;1mdeprecated\x1b[22;39m' + // 粗体黄色 'deprecated'
|
|
|
' \x1b[0m' + msg + '\x1b[39m'; // 重置颜色并添加消息
|
|
|
|
|
|
// 添加堆栈跟踪
|
|
|
if (this._traced) {
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
formatted += '\n \x1b[36mat ' + stack[i].toString() + '\x1b[39m'; // 在堆栈条目前添加 'at' 和颜色
|
|
|
}
|
|
|
|
|
|
return formatted; // 返回格式化后的消息
|
|
|
}
|
|
|
|
|
|
// 如果有调用者信息,则添加调用者位置
|
|
|
if (caller) {
|
|
|
formatted += ' \x1b[36m' + formatLocation(caller) + '\x1b[39m'; // 格式化调用位置并添加颜色
|
|
|
}
|
|
|
|
|
|
return formatted; // 返回最终格式化后的消息
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 格式化调用位置。
|
|
|
* @param {Array} callSite - 调用位置数组,包含文件名、行号和列号。
|
|
|
* @returns {string} - 格式化后的调用位置字符串。
|
|
|
*/
|
|
|
function formatLocation(callSite) {
|
|
|
// 返回相对路径、行号和列号的组合
|
|
|
return relative(basePath, callSite[0]) +
|
|
|
':' + callSite[1] +
|
|
|
':' + callSite[2];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取调用堆栈作为调用位置数组。
|
|
|
* @returns {Array} - 堆栈跟踪数组。
|
|
|
*/
|
|
|
function getStack() {
|
|
|
var limit = Error.stackTraceLimit; // 保存当前的堆栈跟踪限制
|
|
|
var obj = {}; // 创建一个对象用于捕获堆栈
|
|
|
var prep = Error.prepareStackTrace; // 保存原始的 prepareStackTrace 函数
|
|
|
|
|
|
// 设置自定义的堆栈准备函数
|
|
|
Error.prepareStackTrace = prepareObjectStackTrace;
|
|
|
Error.stackTraceLimit = Math.max(10, limit); // 确保堆栈跟踪限制至少为 10
|
|
|
|
|
|
// 捕获堆栈
|
|
|
Error.captureStackTrace(obj);
|
|
|
|
|
|
// 从堆栈中切除当前函数
|
|
|
var stack = obj.stack.slice(1);
|
|
|
|
|
|
// 恢复原始的堆栈准备函数和堆栈跟踪限制
|
|
|
Error.prepareStackTrace = prep;
|
|
|
Error.stackTraceLimit = limit;
|
|
|
|
|
|
return stack; // 返回堆栈数组
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Capture call site stack from v8.
|
|
|
*/
|
|
|
|
|
|
/**
|
|
|
* 准备堆栈跟踪,返回传入的堆栈。
|
|
|
* @param {Object} obj - 捕获堆栈的对象。
|
|
|
* @param {Array} stack - 堆栈数组。
|
|
|
* @returns {Array} - 返回原始堆栈数组。
|
|
|
*/
|
|
|
function prepareObjectStackTrace(obj, stack) {
|
|
|
return stack; // 直接返回传入的堆栈数组
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 返回一个包装的函数,并添加弃用消息。
|
|
|
* @param {Function} fn - 要包装的函数。
|
|
|
* @param {string} message - 弃用消息。
|
|
|
* @returns {Function} - 包装后的函数。
|
|
|
*/
|
|
|
function wrapfunction(fn, message) {
|
|
|
// 检查传入的参数是否为函数
|
|
|
if (typeof fn !== 'function') {
|
|
|
throw new TypeError('argument fn must be a function'); // 如果不是,抛出类型错误
|
|
|
}
|
|
|
|
|
|
var args = createArgumentsString(fn.length); // 创建函数参数字符串
|
|
|
var stack = getStack(); // 获取当前堆栈
|
|
|
var site = callSiteLocation(stack[1]); // 获取调用位置
|
|
|
|
|
|
site.name = fn.name; // 设置调用位置的名称为函数名
|
|
|
|
|
|
// 使用新函数创建一个包装函数
|
|
|
// eslint-disable-next-line no-new-func
|
|
|
var deprecatedfn = new Function('fn', 'log', 'deprecate', 'message', 'site',
|
|
|
'"use strict"\n' +
|
|
|
'return function (' + args + ') {' +
|
|
|
'log.call(deprecate, message, site)\n' + // 调用日志记录弃用消息
|
|
|
'return fn.apply(this, arguments)\n' + // 调用原始函数并传递参数
|
|
|
'}')(fn, log, this, message, site); // 立即调用新函数并传入参数
|
|
|
|
|
|
return deprecatedfn; // 返回包装后的函数
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 在属性上添加弃用消息的包装。
|
|
|
* @param {Object} obj - 拥有属性的对象。
|
|
|
* @param {string} prop - 属性名称。
|
|
|
* @param {string} message - 弃用消息。
|
|
|
*/
|
|
|
function wrapproperty(obj, prop, message) {
|
|
|
// 检查传入的对象是否有效
|
|
|
if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
|
|
|
throw new TypeError('argument obj must be object'); // 如果无效,抛出类型错误
|
|
|
}
|
|
|
|
|
|
var descriptor = Object.getOwnPropertyDescriptor(obj, prop); // 获取属性描述符
|
|
|
|
|
|
// 检查属性是否存在
|
|
|
if (!descriptor) {
|
|
|
throw new TypeError('must call property on owner object'); // 如果不存在,抛出类型错误
|
|
|
}
|
|
|
|
|
|
// 检查属性是否可配置
|
|
|
if (!descriptor.configurable) {
|
|
|
throw new TypeError('property must be configurable'); // 如果不可配置,抛出类型错误
|
|
|
}
|
|
|
|
|
|
var deprecate = this; // 保存当前上下文
|
|
|
var stack = getStack(); // 获取当前堆栈
|
|
|
var site = callSiteLocation(stack[1]); // 获取调用位置
|
|
|
|
|
|
// 设置调用位置的名称为属性名称
|
|
|
site.name = prop;
|
|
|
|
|
|
// 如果是数据描述符,则转换为访问器描述符
|
|
|
if ('value' in descriptor) {
|
|
|
descriptor = convertDataDescriptorToAccessor(obj, prop, message);
|
|
|
}
|
|
|
|
|
|
var get = descriptor.get; // 获取 getter
|
|
|
var set = descriptor.set; // 获取 setter
|
|
|
|
|
|
// 包装 getter
|
|
|
if (typeof get === 'function') {
|
|
|
descriptor.get = function getter() {
|
|
|
log.call(deprecate, message, site); // 调用日志记录弃用消息
|
|
|
return get.apply(this, arguments); // 调用原始 getter
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// 包装 setter
|
|
|
if (typeof set === 'function') {
|
|
|
descriptor.set = function setter() {
|
|
|
log.call(deprecate, message, site); // 调用日志记录弃用消息
|
|
|
return set.apply(this, arguments); // 调用原始 setter
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// 重新定义属性,应用新的描述符
|
|
|
Object.defineProperty(obj, prop, descriptor);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建一个弃用错误对象。
|
|
|
* @param {string} namespace - 命名空间。
|
|
|
* @param {string} message - 弃用消息。
|
|
|
* @param {Array} stack - 堆栈数组。
|
|
|
*/
|
|
|
function DeprecationError(namespace, message, stack) {
|
|
|
var error = new Error(); // 创建一个新的错误对象
|
|
|
var stackString;
|
|
|
|
|
|
// 定义构造函数属性
|
|
|
Object.defineProperty(error, 'constructor', {
|
|
|
value: DeprecationError,
|
|
|
});
|
|
|
|
|
|
// 定义消息属性
|
|
|
Object.defineProperty(error, 'message', {
|
|
|
configurable: true,
|
|
|
enumerable: false,
|
|
|
value: message, // 设置错误消息
|
|
|
writable: true,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 准备堆栈跟踪,返回传入的堆栈。
|
|
|
* @param {Object} obj - 捕获堆栈的对象。
|
|
|
* @param {Array} stack - 堆栈数组。
|
|
|
* @returns {Array} - 返回原始堆栈数组。
|
|
|
*/
|
|
|
function prepareObjectStackTrace(obj, stack) {
|
|
|
return stack; // 直接返回传入的堆栈数组
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 返回一个包装的函数,并添加弃用消息。
|
|
|
* @param {Function} fn - 要包装的函数。
|
|
|
* @param {string} message - 弃用消息。
|
|
|
* @returns {Function} - 包装后的函数。
|
|
|
*/
|
|
|
function wrapfunction(fn, message) {
|
|
|
// 检查传入的参数是否为函数
|
|
|
if (typeof fn !== 'function') {
|
|
|
throw new TypeError('argument fn must be a function'); // 如果不是,抛出类型错误
|
|
|
}
|
|
|
|
|
|
var args = createArgumentsString(fn.length); // 创建函数参数字符串
|
|
|
var stack = getStack(); // 获取当前堆栈
|
|
|
var site = callSiteLocation(stack[1]); // 获取调用位置
|
|
|
|
|
|
site.name = fn.name; // 设置调用位置的名称为函数名
|
|
|
|
|
|
// 使用新函数创建一个包装函数
|
|
|
// eslint-disable-next-line no-new-func
|
|
|
var deprecatedfn = new Function('fn', 'log', 'deprecate', 'message', 'site',
|
|
|
'"use strict"\n' +
|
|
|
'return function (' + args + ') {' +
|
|
|
'log.call(deprecate, message, site)\n' + // 调用日志记录弃用消息
|
|
|
'return fn.apply(this, arguments)\n' + // 调用原始函数并传递参数
|
|
|
'}')(fn, log, this, message, site); // 立即调用新函数并传入参数
|
|
|
|
|
|
return deprecatedfn; // 返回包装后的函数
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 在属性上添加弃用消息的包装。
|
|
|
* @param {Object} obj - 拥有属性的对象。
|
|
|
* @param {string} prop - 属性名称。
|
|
|
* @param {string} message - 弃用消息。
|
|
|
*/
|
|
|
function wrapproperty(obj, prop, message) {
|
|
|
// 检查传入的对象是否有效
|
|
|
if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
|
|
|
throw new TypeError('argument obj must be object'); // 如果无效,抛出类型错误
|
|
|
}
|
|
|
|
|
|
var descriptor = Object.getOwnPropertyDescriptor(obj, prop); // 获取属性描述符
|
|
|
|
|
|
// 检查属性是否存在
|
|
|
if (!descriptor) {
|
|
|
throw new TypeError('must call property on owner object'); // 如果不存在,抛出类型错误
|
|
|
}
|
|
|
|
|
|
// 检查属性是否可配置
|
|
|
if (!descriptor.configurable) {
|
|
|
throw new TypeError('property must be configurable'); // 如果不可配置,抛出类型错误
|
|
|
}
|
|
|
|
|
|
var deprecate = this; // 保存当前上下文
|
|
|
var stack = getStack(); // 获取当前堆栈
|
|
|
var site = callSiteLocation(stack[1]); // 获取调用位置
|
|
|
|
|
|
// 设置调用位置的名称为属性名称
|
|
|
site.name = prop;
|
|
|
|
|
|
// 如果是数据描述符,则转换为访问器描述符
|
|
|
if ('value' in descriptor) {
|
|
|
descriptor = convertDataDescriptorToAccessor(obj, prop, message);
|
|
|
}
|
|
|
|
|
|
var get = descriptor.get; // 获取 getter
|
|
|
var set = descriptor.set; // 获取 setter
|
|
|
|
|
|
// 包装 getter
|
|
|
if (typeof get === 'function') {
|
|
|
descriptor.get = function getter() {
|
|
|
log.call(deprecate, message, site); // 调用日志记录弃用消息
|
|
|
return get.apply(this, arguments); // 调用原始 getter
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// 包装 setter
|
|
|
if (typeof set === 'function') {
|
|
|
descriptor.set = function setter() {
|
|
|
log.call(deprecate, message, site); // 调用日志记录弃用消息
|
|
|
return set.apply(this, arguments); // 调用原始 setter
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// 重新定义属性,应用新的描述符
|
|
|
Object.defineProperty(obj, prop, descriptor);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建一个弃用错误对象。
|
|
|
* @param {string} namespace - 命名空间。
|
|
|
* @param {string} message - 弃用消息。
|
|
|
* @param {Array} stack - 堆栈数组。
|
|
|
*/
|
|
|
function DeprecationError(namespace, message, stack) {
|
|
|
var error = new Error(); // 创建一个新的错误对象
|
|
|
var stackString;
|
|
|
|
|
|
// 定义构造函数属性
|
|
|
Object.defineProperty(error, 'constructor', {
|
|
|
value: DeprecationError,
|
|
|
});
|
|
|
|
|
|
// 定义消息属性
|
|
|
Object.defineProperty(error, 'message', {
|
|
|
configurable: true,
|
|
|
enumerable: false,
|
|
|
value: message, // 设置错误消息
|
|
|
writable: true,
|
|
|
});
|
|
|
}
|