-
Notifications
You must be signed in to change notification settings - Fork 30.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate object.inspect for custom inspection #16393
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,10 @@ function objectToString(o) { | |
return Object.prototype.toString.call(o); | ||
} | ||
|
||
// Keep a list of deprecation codes that have been warned on so we only warn on | ||
// each one once. | ||
const codesWarned = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ooo nice, this crosses a long standing todo off my list, thank you! |
||
|
||
// Mark that a method should not be used. | ||
// Returns a modified function which warns once by default. | ||
// If --no-deprecation is set, then it is a no-op. | ||
|
@@ -46,7 +50,10 @@ function deprecate(fn, msg, code) { | |
if (!warned) { | ||
warned = true; | ||
if (code !== undefined) { | ||
process.emitWarning(msg, 'DeprecationWarning', code, deprecated); | ||
if (!codesWarned[code]) { | ||
process.emitWarning(msg, 'DeprecationWarning', code, deprecated); | ||
codesWarned[code] = true; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be obsolete due to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @BridgeAR No, {
const fn1 = util.deprecate(() => { ... }, 'foo', 'DEP0001');
const fn2 = util.deprecate(() => { ... }, 'foo', 'DEP0001');
fn1();
fn2(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is correct but normally the deprecation function is only called once with a message. We could remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @BridgeAR I thought about removing |
||
} else { | ||
process.emitWarning(msg, 'DeprecationWarning', deprecated); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -428,14 +428,23 @@ function formatValue(ctx, value, recurseTimes, ln) { | |
// Provide a hook for user-specified inspect functions. | ||
// Check that value is an object with an inspect function on it | ||
if (ctx.customInspect) { | ||
const maybeCustomInspect = value[customInspectSymbol] || value.inspect; | ||
let maybeCustom = value[customInspectSymbol]; | ||
|
||
if (!maybeCustom && value.inspect !== exports.inspect && | ||
typeof value.inspect === 'function') { | ||
maybeCustom = deprecate( | ||
value.inspect, | ||
'Custom inspection function on Objects via .inspect() is deprecated', | ||
'DEP0079' | ||
); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t think we’d want to generate a deprecated function every time There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should also only trigger in case it is a function. Right now it would always trigger. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @addaleax OK, took care of the "it will generate a deprecated function every time" issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For simplicity, I'd fire the warning here, and not delegate it to the actual call, or just set a flag and fire next to the call. That saves the closure creation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @BridgeAR Yeah, definitely should have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
if (typeof maybeCustomInspect === 'function' && | ||
if (typeof maybeCustom === 'function' && | ||
// Filter out the util module, its inspect function is special | ||
maybeCustomInspect !== exports.inspect && | ||
maybeCustom !== exports.inspect && | ||
// Also filter out any prototype objects using the circular check. | ||
!(value.constructor && value.constructor.prototype === value)) { | ||
const ret = maybeCustomInspect.call(value, recurseTimes, ctx); | ||
const ret = maybeCustom.call(value, recurseTimes, ctx); | ||
|
||
// If the custom inspection method returned `this`, don't go into | ||
// infinite recursion. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
'use strict'; | ||
|
||
require('../common'); | ||
|
||
// Tests basic functionality of util.deprecate(). | ||
|
||
const assert = require('assert'); | ||
const util = require('util'); | ||
|
||
const expectedWarnings = new Map(); | ||
|
||
// Emits deprecation only once if same function is called. | ||
{ | ||
const msg = 'fhqwhgads'; | ||
const fn = util.deprecate(() => {}, msg); | ||
expectedWarnings.set(msg, { code: undefined, count: 1 }); | ||
fn(); | ||
fn(); | ||
} | ||
|
||
// Emits deprecation twice for different functions. | ||
{ | ||
const msg = 'sterrance'; | ||
const fn1 = util.deprecate(() => {}, msg); | ||
const fn2 = util.deprecate(() => {}, msg); | ||
expectedWarnings.set(msg, { code: undefined, count: 2 }); | ||
fn1(); | ||
fn2(); | ||
} | ||
|
||
// Emits deprecation only once if optional code is the same, even for different | ||
// functions. | ||
{ | ||
const msg = 'cannonmouth'; | ||
const code = 'deprecatesque'; | ||
const fn1 = util.deprecate(() => {}, msg, code); | ||
const fn2 = util.deprecate(() => {}, msg, code); | ||
expectedWarnings.set(msg, { code, count: 1 }); | ||
fn1(); | ||
fn2(); | ||
fn1(); | ||
fn2(); | ||
} | ||
|
||
process.on('warning', (warning) => { | ||
assert.strictEqual(warning.name, 'DeprecationWarning'); | ||
assert.ok(expectedWarnings.has(warning.message)); | ||
const expected = expectedWarnings.get(warning.message); | ||
assert.strictEqual(warning.code, expected.code); | ||
expected.count = expected.count - 1; | ||
if (expected.count === 0) | ||
expectedWarnings.delete(warning.message); | ||
}); | ||
|
||
process.on('exit', () => { | ||
assert.deepStrictEqual(expectedWarnings, new Map()); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
|
||
// Test that deprecation warning for custom inspection via the `.inspect()` | ||
// property (on the target object) is emitted once and only once. | ||
|
||
const util = require('util'); | ||
|
||
{ | ||
const target = { inspect: () => 'Fhqwhgads' }; | ||
// `common.expectWarning` will expect the warning exactly one time only | ||
common.expectWarning( | ||
'DeprecationWarning', | ||
'Custom inspection function on Objects via .inspect() is deprecated' | ||
); | ||
util.inspect(target); // should emit deprecation warning | ||
util.inspect(target); // should not emit deprecation warning | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
s/fn/function
,s/msg/message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not looking for a debate here, but do want to make sure we have broad agreement and perhaps something that needs an entry in the style guide or something:
If I'm not mistaken, we try to use the same argument name in the docs as we use for the variables in the source code.
I believe there are two reasons, but correction welcome. First, it makes the reading of source code along with docs easier. Second, it allows the mapping of documentation variable names to variable names in stack traces possible.
If I'm right about that, then we can not use
function
as it is a keyword and not a valid variable name. We usefn
andmsg
elsewhere in the docs. I'm inclined to leave them. They are pretty self-explanatory, and get an explanation immediately below anyway.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(More here: #20489.)