tools: lint deprecation codes

Add a rule to make sure deprecation codes are in order.

PR-URL: https://github.com/nodejs/node/pull/41992
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Antoine du Hamel 2022-02-19 18:14:09 +01:00 committed by GitHub
parent 3a1a440802
commit 5d4da62514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 215 additions and 4 deletions

View File

@ -1815,6 +1815,10 @@ Type: End-of-Life
`runInAsyncIdScope` doesn't emit the `'before'` or `'after'` event and can thus
cause a lot of issues. See <https://github.com/nodejs/node/issues/14328>.
<!-- md-lint skip-deprecation DEP0087 -->
<!-- md-lint skip-deprecation DEP0088 -->
### DEP0089: `require('assert')`
<!-- YAML
@ -2255,10 +2259,10 @@ Type: End-of-Life
The `crypto._toBuf()` function was not designed to be used by modules outside
of Node.js core and was removed.
### DEP0115: `crypto.prng()`, `crypto.pseudoRandomBytes()`, `crypto.rng()`
<!--lint disable nodejs-yaml-comments -->
### DEP0115: `crypto.prng()`, `crypto.pseudoRandomBytes()`, `crypto.rng()`
<!-- YAML
changes:
- version: v11.0.0
@ -2269,10 +2273,10 @@ changes:
with `--pending-deprecation` support.
-->
<!--lint enable nodejs-yaml-comments -->
Type: Documentation-only (supports [`--pending-deprecation`][])
<!--lint enable nodejs-yaml-comments -->
In recent versions of Node.js, there is no difference between
[`crypto.randomBytes()`][] and `crypto.pseudoRandomBytes()`. The latter is
deprecated along with the undocumented aliases `crypto.prng()` and

View File

@ -0,0 +1,28 @@
'use strict';
require('../common');
const path = require('path');
const { spawn } = require('child_process');
const script = path.join(
__dirname,
'..',
'..',
'tools',
'doc',
'deprecationCodes.mjs'
);
const mdPath = path.join(
__dirname,
'..',
'..',
'doc',
'api',
'deprecations.md'
);
const cp = spawn(process.execPath, [script, mdPath], { encoding: 'utf-8', stdio: 'inherit' });
cp.on('error', (err) => { throw err; });
cp.on('exit', (code) => process.exit(code));

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
if (!common.hasIntl)
common.skip('missing Intl');
common.skipIfEslintMissing();
const RuleTester = require('../../tools/node_modules/eslint').RuleTester;
const rule = require('../../tools/eslint-rules/documented-deprecation-codes');
const mdFile = 'doc/api/deprecations.md';
const invalidCode = 'UNDOCUMENTED INVALID CODE';
new RuleTester().run('documented-deprecation-codes', rule, {
valid: [
`
deprecate(function() {
return this.getHeaders();
}, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066')
`,
],
invalid: [
{
code: `
deprecate(function foo(){}, 'bar', '${invalidCode}');
`,
errors: [
{
message: `"${invalidCode}" does not match the expected pattern`,
line: 2
},
{
message: `"${invalidCode}" is not documented in ${mdFile}`,
line: 2
},
]
},
]
});

View File

@ -0,0 +1,92 @@
import fs from 'fs';
import { resolve } from 'path';
import assert from 'assert';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
const source = resolve(process.argv[2]);
const skipDeprecationComment = /^<!-- md-lint skip-deprecation (DEP\d{4}) -->$/;
const generateDeprecationCode = (codeAsNumber) =>
`DEP${codeAsNumber.toString().padStart(4, '0')}`;
const addMarkdownPathToErrorStack = (error, node) => {
const { line, column } = node.position.start;
const [header, ...lines] = error.stack.split('\n');
error.stack =
header +
`\n at <anonymous> (${source}:${line}:${column})\n` +
lines.join('\n');
return error;
};
const testHeading = (headingNode, expectedDeprecationCode) => {
try {
assert.strictEqual(
headingNode?.children[0]?.value.substring(0, 9),
`${expectedDeprecationCode}: `,
'Ill-formed or out-of-order deprecation code.'
);
} catch (e) {
throw addMarkdownPathToErrorStack(e, headingNode);
}
};
const testYAMLComment = (commentNode) => {
try {
assert.match(
commentNode?.value?.substring(0, 21),
/^<!-- YAML\r?\nchanges:\r?\n/,
'Missing or ill-formed YAML comment.'
);
} catch (e) {
throw addMarkdownPathToErrorStack(e, commentNode);
}
};
const testDeprecationType = (paragraphNode) => {
try {
assert.strictEqual(
paragraphNode?.children[0]?.value?.substring(0, 6),
'Type: ',
'Missing deprecation type.'
);
} catch (e) {
throw addMarkdownPathToErrorStack(e, paragraphNode);
}
};
const tree = unified()
.use(remarkParse)
.parse(fs.readFileSync(source));
let expectedDeprecationCodeNumber = 0;
for (let i = 0; i < tree.children.length; i++) {
const node = tree.children[i];
if (node.type === 'html' && skipDeprecationComment.test(node.value)) {
const expectedDeprecationCode =
generateDeprecationCode(++expectedDeprecationCodeNumber);
const deprecationCodeAsText = node.value.match(skipDeprecationComment)[1];
try {
assert.strictEqual(
deprecationCodeAsText,
expectedDeprecationCode,
'Deprecation codes are not ordered correctly.'
);
} catch (e) {
throw addMarkdownPathToErrorStack(e, node);
}
}
if (node.type === 'heading' && node.depth === 3) {
const expectedDeprecationCode =
generateDeprecationCode(++expectedDeprecationCodeNumber);
testHeading(node, expectedDeprecationCode);
testYAMLComment(tree.children[i + 1]);
testDeprecationType(tree.children[i + 2]);
}
}

View File

@ -0,0 +1,37 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { isDefiningDeprecation } = require('./rules-utils.js');
const patternToMatch = /^DEP\d+$/;
const mdFile = 'doc/api/deprecations.md';
const doc = fs.readFileSync(path.resolve(__dirname, '../..', mdFile), 'utf8');
function isInDoc(code) {
return doc.includes(`### ${code}:`);
}
function getDeprecationCode(node) {
return node.expression.arguments[2].value;
}
module.exports = {
create: function(context) {
return {
ExpressionStatement: function(node) {
if (!isDefiningDeprecation(node) || !getDeprecationCode(node)) return;
const code = getDeprecationCode(node);
if (!patternToMatch.test(code)) {
const message = `"${code}" does not match the expected pattern`;
context.report({ node, message });
}
if (!isInDoc(code)) {
const message = `"${code}" is not documented in ${mdFile}`;
context.report({ node, message });
}
},
};
},
};

View File

@ -20,6 +20,14 @@ module.exports.isDefiningError = function(node) {
node.expression.arguments.length !== 0;
};
module.exports.isDefiningDeprecation = function(node) {
return node.expression &&
node.expression.type === 'CallExpression' &&
node.expression.callee &&
node.expression.callee.name.endsWith('deprecate') &&
node.expression.arguments.length !== 0;
};
/**
* Returns true if any of the passed in modules are used in
* require calls.