Skip to content

Commit dcb51b0

Browse files
aduh95bengl
authored andcommitted
tools: lint deprecation codes
Add a rule to make sure deprecation codes are in order. PR-URL: #41992 Reviewed-By: Richard Lau <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 72cb44b commit dcb51b0

6 files changed

+215
-4
lines changed

doc/api/deprecations.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -1822,6 +1822,10 @@ Type: End-of-Life
18221822
`runInAsyncIdScope` doesn't emit the `'before'` or `'after'` event and can thus
18231823
cause a lot of issues. See <https://github.com/nodejs/node/issues/14328>.
18241824

1825+
<!-- md-lint skip-deprecation DEP0087 -->
1826+
1827+
<!-- md-lint skip-deprecation DEP0088 -->
1828+
18251829
### DEP0089: `require('assert')`
18261830

18271831
<!-- YAML
@@ -2262,10 +2266,10 @@ Type: End-of-Life
22622266
The `crypto._toBuf()` function was not designed to be used by modules outside
22632267
of Node.js core and was removed.
22642268

2265-
### DEP0115: `crypto.prng()`, `crypto.pseudoRandomBytes()`, `crypto.rng()`
2266-
22672269
<!--lint disable nodejs-yaml-comments -->
22682270

2271+
### DEP0115: `crypto.prng()`, `crypto.pseudoRandomBytes()`, `crypto.rng()`
2272+
22692273
<!-- YAML
22702274
changes:
22712275
- version: v11.0.0
@@ -2276,10 +2280,10 @@ changes:
22762280
with `--pending-deprecation` support.
22772281
-->
22782282

2279-
<!--lint enable nodejs-yaml-comments -->
2280-
22812283
Type: Documentation-only (supports [`--pending-deprecation`][])
22822284

2285+
<!--lint enable nodejs-yaml-comments -->
2286+
22832287
In recent versions of Node.js, there is no difference between
22842288
[`crypto.randomBytes()`][] and `crypto.pseudoRandomBytes()`. The latter is
22852289
deprecated along with the undocumented aliases `crypto.prng()` and
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
require('../common');
4+
const path = require('path');
5+
const { spawn } = require('child_process');
6+
7+
const script = path.join(
8+
__dirname,
9+
'..',
10+
'..',
11+
'tools',
12+
'doc',
13+
'deprecationCodes.mjs'
14+
);
15+
16+
const mdPath = path.join(
17+
__dirname,
18+
'..',
19+
'..',
20+
'doc',
21+
'api',
22+
'deprecations.md'
23+
);
24+
25+
const cp = spawn(process.execPath, [script, mdPath], { encoding: 'utf-8', stdio: 'inherit' });
26+
27+
cp.on('error', (err) => { throw err; });
28+
cp.on('exit', (code) => process.exit(code));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
if (!common.hasIntl)
7+
common.skip('missing Intl');
8+
common.skipIfEslintMissing();
9+
10+
const RuleTester = require('../../tools/node_modules/eslint').RuleTester;
11+
const rule = require('../../tools/eslint-rules/documented-deprecation-codes');
12+
13+
const mdFile = 'doc/api/deprecations.md';
14+
15+
const invalidCode = 'UNDOCUMENTED INVALID CODE';
16+
17+
new RuleTester().run('documented-deprecation-codes', rule, {
18+
valid: [
19+
`
20+
deprecate(function() {
21+
return this.getHeaders();
22+
}, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066')
23+
`,
24+
],
25+
invalid: [
26+
{
27+
code: `
28+
deprecate(function foo(){}, 'bar', '${invalidCode}');
29+
`,
30+
errors: [
31+
{
32+
message: `"${invalidCode}" does not match the expected pattern`,
33+
line: 2
34+
},
35+
{
36+
message: `"${invalidCode}" is not documented in ${mdFile}`,
37+
line: 2
38+
},
39+
]
40+
},
41+
]
42+
});

tools/doc/deprecationCodes.mjs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import fs from 'fs';
2+
import { resolve } from 'path';
3+
import assert from 'assert';
4+
5+
import { unified } from 'unified';
6+
import remarkParse from 'remark-parse';
7+
8+
const source = resolve(process.argv[2]);
9+
10+
const skipDeprecationComment = /^<!-- md-lint skip-deprecation (DEP\d{4}) -->$/;
11+
12+
const generateDeprecationCode = (codeAsNumber) =>
13+
`DEP${codeAsNumber.toString().padStart(4, '0')}`;
14+
15+
const addMarkdownPathToErrorStack = (error, node) => {
16+
const { line, column } = node.position.start;
17+
const [header, ...lines] = error.stack.split('\n');
18+
error.stack =
19+
header +
20+
`\n at <anonymous> (${source}:${line}:${column})\n` +
21+
lines.join('\n');
22+
return error;
23+
};
24+
25+
const testHeading = (headingNode, expectedDeprecationCode) => {
26+
try {
27+
assert.strictEqual(
28+
headingNode?.children[0]?.value.substring(0, 9),
29+
`${expectedDeprecationCode}: `,
30+
'Ill-formed or out-of-order deprecation code.'
31+
);
32+
} catch (e) {
33+
throw addMarkdownPathToErrorStack(e, headingNode);
34+
}
35+
};
36+
37+
const testYAMLComment = (commentNode) => {
38+
try {
39+
assert.match(
40+
commentNode?.value?.substring(0, 21),
41+
/^<!-- YAML\r?\nchanges:\r?\n/,
42+
'Missing or ill-formed YAML comment.'
43+
);
44+
} catch (e) {
45+
throw addMarkdownPathToErrorStack(e, commentNode);
46+
}
47+
};
48+
49+
const testDeprecationType = (paragraphNode) => {
50+
try {
51+
assert.strictEqual(
52+
paragraphNode?.children[0]?.value?.substring(0, 6),
53+
'Type: ',
54+
'Missing deprecation type.'
55+
);
56+
} catch (e) {
57+
throw addMarkdownPathToErrorStack(e, paragraphNode);
58+
}
59+
};
60+
61+
const tree = unified()
62+
.use(remarkParse)
63+
.parse(fs.readFileSync(source));
64+
65+
let expectedDeprecationCodeNumber = 0;
66+
for (let i = 0; i < tree.children.length; i++) {
67+
const node = tree.children[i];
68+
if (node.type === 'html' && skipDeprecationComment.test(node.value)) {
69+
const expectedDeprecationCode =
70+
generateDeprecationCode(++expectedDeprecationCodeNumber);
71+
const deprecationCodeAsText = node.value.match(skipDeprecationComment)[1];
72+
73+
try {
74+
assert.strictEqual(
75+
deprecationCodeAsText,
76+
expectedDeprecationCode,
77+
'Deprecation codes are not ordered correctly.'
78+
);
79+
} catch (e) {
80+
throw addMarkdownPathToErrorStack(e, node);
81+
}
82+
}
83+
if (node.type === 'heading' && node.depth === 3) {
84+
const expectedDeprecationCode =
85+
generateDeprecationCode(++expectedDeprecationCodeNumber);
86+
87+
testHeading(node, expectedDeprecationCode);
88+
89+
testYAMLComment(tree.children[i + 1]);
90+
testDeprecationType(tree.children[i + 2]);
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const { isDefiningDeprecation } = require('./rules-utils.js');
6+
7+
const patternToMatch = /^DEP\d+$/;
8+
9+
const mdFile = 'doc/api/deprecations.md';
10+
const doc = fs.readFileSync(path.resolve(__dirname, '../..', mdFile), 'utf8');
11+
12+
function isInDoc(code) {
13+
return doc.includes(`### ${code}:`);
14+
}
15+
16+
function getDeprecationCode(node) {
17+
return node.expression.arguments[2].value;
18+
}
19+
20+
module.exports = {
21+
create: function(context) {
22+
return {
23+
ExpressionStatement: function(node) {
24+
if (!isDefiningDeprecation(node) || !getDeprecationCode(node)) return;
25+
const code = getDeprecationCode(node);
26+
if (!patternToMatch.test(code)) {
27+
const message = `"${code}" does not match the expected pattern`;
28+
context.report({ node, message });
29+
}
30+
if (!isInDoc(code)) {
31+
const message = `"${code}" is not documented in ${mdFile}`;
32+
context.report({ node, message });
33+
}
34+
},
35+
};
36+
},
37+
};

tools/eslint-rules/rules-utils.js

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ module.exports.isDefiningError = function(node) {
2020
node.expression.arguments.length !== 0;
2121
};
2222

23+
module.exports.isDefiningDeprecation = function(node) {
24+
return node.expression &&
25+
node.expression.type === 'CallExpression' &&
26+
node.expression.callee &&
27+
node.expression.callee.name.endsWith('deprecate') &&
28+
node.expression.arguments.length !== 0;
29+
};
30+
2331
/**
2432
* Returns true if any of the passed in modules are used in
2533
* require calls.

0 commit comments

Comments
 (0)