Skip to content

Commit 07d39a2

Browse files
committed
util: emit deprecation code only once
If another function has already emitted the deprecation warning with the same code as the warning that is about to be emitted, do not emit the warning. This is a breaking change. Previously, different functions could emit the same deprecation warning multiple times. This was a known bug rather than a feature, but this change is being treated as a breaking change out of caution. Identical deprecation warnings should not be emitted. PR-URL: #16393 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent ccc87eb commit 07d39a2

File tree

3 files changed

+86
-9
lines changed

3 files changed

+86
-9
lines changed

doc/api/util.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ environment variable. For example: `NODE_DEBUG=fs,net,tls`.
110110
## util.deprecate(fn, msg[, code])
111111
<!-- YAML
112112
added: v0.8.0
113+
changes:
114+
- version: REPLACEME
115+
description: Deprecation warnings are only emitted once for each code.
113116
-->
114117

115118
* `fn` {Function} The function that is being deprecated.
@@ -122,21 +125,31 @@ added: v0.8.0
122125
The `util.deprecate()` method wraps `fn` (which may be a function or class) in
123126
such a way that it is marked as deprecated.
124127

125-
<!-- eslint-disable prefer-rest-params -->
126128
```js
127129
const util = require('util');
128130

129-
exports.puts = util.deprecate(function() {
130-
for (let i = 0, len = arguments.length; i < len; ++i) {
131-
process.stdout.write(arguments[i] + '\n');
132-
}
133-
}, 'util.puts: Use console.log instead');
131+
exports.obsoleteFunction = util.deprecate(function() {
132+
// Do something here.
133+
}, 'obsoleteFunction() is deprecated. Use newShinyFunction() instead.');
134134
```
135135

136136
When called, `util.deprecate()` will return a function that will emit a
137137
`DeprecationWarning` using the `process.on('warning')` event. The warning will
138-
be emitted and printed to `stderr` exactly once, the first time it is called.
139-
After the warning is emitted, the wrapped function is called.
138+
be emitted and printed to `stderr` the first time the returned function is
139+
called. After the warning is emitted, the wrapped function is called without
140+
emitting a warning.
141+
142+
If the same optional `code` is supplied in multiple calls to `util.deprecate()`,
143+
the warning will be emitted only once for that `code`.
144+
145+
```js
146+
const util = require('util');
147+
148+
const fn1 = util.deprecate(someFunction, someMessage, 'DEP0001');
149+
const fn2 = util.deprecate(someOtherFunction, someOtherMessage, 'DEP0001');
150+
fn1(); // emits a deprecation warning with code DEP0001
151+
fn2(); // does not emit a deprecation warning because it has the same code
152+
```
140153

141154
If either the `--no-deprecation` or `--no-warnings` command line flags are
142155
used, or if the `process.noDeprecation` property is set to `true` *prior* to

lib/internal/util.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ function objectToString(o) {
2323
return Object.prototype.toString.call(o);
2424
}
2525

26+
// Keep a list of deprecation codes that have been warned on so we only warn on
27+
// each one once.
28+
const codesWarned = {};
29+
2630
// Mark that a method should not be used.
2731
// Returns a modified function which warns once by default.
2832
// If --no-deprecation is set, then it is a no-op.
@@ -46,7 +50,10 @@ function deprecate(fn, msg, code) {
4650
if (!warned) {
4751
warned = true;
4852
if (code !== undefined) {
49-
process.emitWarning(msg, 'DeprecationWarning', code, deprecated);
53+
if (!codesWarned[code]) {
54+
process.emitWarning(msg, 'DeprecationWarning', code, deprecated);
55+
codesWarned[code] = true;
56+
}
5057
} else {
5158
process.emitWarning(msg, 'DeprecationWarning', deprecated);
5259
}

test/parallel/test-util-deprecate.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
// Tests basic functionality of util.deprecate().
6+
7+
const assert = require('assert');
8+
const util = require('util');
9+
10+
const expectedWarnings = new Map();
11+
12+
// Emits deprecation only once if same function is called.
13+
{
14+
const msg = 'fhqwhgads';
15+
const fn = util.deprecate(() => {}, msg);
16+
expectedWarnings.set(msg, { code: undefined, count: 1 });
17+
fn();
18+
fn();
19+
}
20+
21+
// Emits deprecation twice for different functions.
22+
{
23+
const msg = 'sterrance';
24+
const fn1 = util.deprecate(() => {}, msg);
25+
const fn2 = util.deprecate(() => {}, msg);
26+
expectedWarnings.set(msg, { code: undefined, count: 2 });
27+
fn1();
28+
fn2();
29+
}
30+
31+
// Emits deprecation only once if optional code is the same, even for different
32+
// functions.
33+
{
34+
const msg = 'cannonmouth';
35+
const code = 'deprecatesque';
36+
const fn1 = util.deprecate(() => {}, msg, code);
37+
const fn2 = util.deprecate(() => {}, msg, code);
38+
expectedWarnings.set(msg, { code, count: 1 });
39+
fn1();
40+
fn2();
41+
fn1();
42+
fn2();
43+
}
44+
45+
process.on('warning', (warning) => {
46+
assert.strictEqual(warning.name, 'DeprecationWarning');
47+
assert.ok(expectedWarnings.has(warning.message));
48+
const expected = expectedWarnings.get(warning.message);
49+
assert.strictEqual(warning.code, expected.code);
50+
expected.count = expected.count - 1;
51+
if (expected.count === 0)
52+
expectedWarnings.delete(warning.message);
53+
});
54+
55+
process.on('exit', () => {
56+
assert.deepStrictEqual(expectedWarnings, new Map());
57+
});

0 commit comments

Comments
 (0)