diff options
author | Antoine du Hamel <duhamelantoine1995@gmail.com> | 2022-02-19 20:14:09 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-19 20:14:09 +0300 |
commit | 5d4da62514cdf24c582fd4a8e5f86ef37860e8af (patch) | |
tree | 98abbfa5fdb6decb4773ac12b853d0e3b969cf4a /tools | |
parent | 3a1a4408022f657d0f4bf13000bc37e1e72bd40a (diff) |
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>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/doc/deprecationCodes.mjs | 92 | ||||
-rw-r--r-- | tools/eslint-rules/documented-deprecation-codes.js | 37 | ||||
-rw-r--r-- | tools/eslint-rules/rules-utils.js | 8 |
3 files changed, 137 insertions, 0 deletions
diff --git a/tools/doc/deprecationCodes.mjs b/tools/doc/deprecationCodes.mjs new file mode 100644 index 00000000000..6715bb04b60 --- /dev/null +++ b/tools/doc/deprecationCodes.mjs @@ -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]); + } +} diff --git a/tools/eslint-rules/documented-deprecation-codes.js b/tools/eslint-rules/documented-deprecation-codes.js new file mode 100644 index 00000000000..3317f3c983c --- /dev/null +++ b/tools/eslint-rules/documented-deprecation-codes.js @@ -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 }); + } + }, + }; + }, +}; diff --git a/tools/eslint-rules/rules-utils.js b/tools/eslint-rules/rules-utils.js index 1cba9218a1d..c5362c96cda 100644 --- a/tools/eslint-rules/rules-utils.js +++ b/tools/eslint-rules/rules-utils.js @@ -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. |