Skip to content

Commit 1dd8a0b

Browse files
feat(remark-lint): improve validation (#8746)
feat(remark-lint): add deprecation validation, improve type validation Signed-off-by: Aviv Keller <me@aviv.sh> Co-authored-by: Caner Akdas <canerakdas@gmail.com>
1 parent 801e49a commit 1dd8a0b

File tree

8 files changed

+250
-58
lines changed

8 files changed

+250
-58
lines changed

packages/remark-lint/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@node-core/remark-lint",
33
"type": "module",
4-
"version": "1.2.1",
4+
"version": "1.3.0",
55
"exports": {
66
".": "./src/index.mjs",
77
"./api": "./src/api.mjs"
@@ -20,7 +20,7 @@
2020
"test:unit": "cross-env NODE_NO_WARNINGS=1 node --experimental-test-coverage --test \"**/*.test.mjs\""
2121
},
2222
"dependencies": {
23-
"@node-core/doc-kit": "^1.0.0",
23+
"@node-core/doc-kit": "^1.0.2",
2424
"remark-gfm": "^4.0.1",
2525
"remark-lint-blockquote-indentation": "^4.0.1",
2626
"remark-lint-checkbox-character-style": "^5.0.1",

packages/remark-lint/src/api.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import remarkLintUnorderedListMarkerStyle from 'remark-lint-unordered-list-marke
77
import basePreset from './index.mjs';
88
import duplicateStabilityNodes from './rules/duplicate-stability-nodes.mjs';
99
import hashedSelfReference from './rules/hashed-self-reference.mjs';
10+
import invalidDeprecations from './rules/invalid-deprecations.mjs';
1011
import invalidTypeReference from './rules/invalid-type-reference.mjs';
1112
import orderedReferences from './rules/ordered-references.mjs';
1213
import requiredMetadata from './rules/required-metadata.mjs';
@@ -37,6 +38,7 @@ export default (options = {}) => ({
3738
orderedReferences,
3839
requiredMetadata,
3940
invalidTypeReference,
41+
invalidDeprecations,
4042
].map(plugin => [plugin, options]),
4143

4244
// External Rules
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, it } from 'node:test';
2+
3+
import dedent from 'dedent';
4+
5+
import { testRule } from './utils.mjs';
6+
import invalidDeprecations from '../invalid-deprecations.mjs';
7+
8+
const TYPE = 'Type: [...]';
9+
const CHANGES = dedent`
10+
<!-- YAML
11+
changes:
12+
-->
13+
`;
14+
const BODY = TYPE + '\n' + CHANGES;
15+
16+
const testCases = [
17+
{
18+
name: 'in order deprecations',
19+
input: dedent`
20+
## DEP0001:
21+
${BODY}
22+
## DEP0002:
23+
${BODY}
24+
`,
25+
expected: [],
26+
},
27+
{
28+
name: 'out of order deprecations',
29+
input: dedent`
30+
## DEP0001:
31+
${BODY}
32+
## DEP0003:
33+
${BODY}
34+
`,
35+
expected: [
36+
'Deprecation codes are out of order. Expected DEP0002, saw "DEP0003"',
37+
],
38+
},
39+
{
40+
name: 'skipped deprecations',
41+
input: dedent`
42+
## DEP0001:
43+
${BODY}
44+
<!-- md-lint skip-deprecation DEP0002 -->
45+
## DEP0003:
46+
${BODY}
47+
`,
48+
expected: [],
49+
},
50+
{
51+
name: 'out of order skipped deprecations',
52+
input: dedent`
53+
## DEP0001:
54+
${BODY}
55+
<!-- md-lint skip-deprecation DEP0004 -->
56+
## DEP0003:
57+
${BODY}
58+
`,
59+
expected: [
60+
'Deprecation codes are out of order. Expected DEP0002, saw "DEP0004"',
61+
],
62+
},
63+
{
64+
name: 'not enough skipped deprecations',
65+
input: dedent`
66+
## DEP0001:
67+
${BODY}
68+
<!-- md-lint skip-deprecation DEP0002 -->
69+
## DEP0004:
70+
${BODY}
71+
`,
72+
expected: [
73+
'Deprecation codes are out of order. Expected DEP0003, saw "DEP0004"',
74+
],
75+
},
76+
{
77+
name: 'no type',
78+
input: dedent`
79+
## DEP0001:
80+
some text
81+
${CHANGES}
82+
`,
83+
expected: ['Deprecation "DEP0001" is missing a "Type"'],
84+
},
85+
{
86+
name: 'no changes',
87+
input: dedent`
88+
## DEP0001:
89+
${TYPE}
90+
`,
91+
expected: ['Deprecation "DEP0001" is missing changes'],
92+
},
93+
];
94+
95+
describe('invalid-deprecations', () => {
96+
for (const { name, input, expected, options } of testCases) {
97+
it(name, () =>
98+
testRule(
99+
invalidDeprecations,
100+
input,
101+
expected,
102+
{ stem: 'deprecations' },
103+
options
104+
)
105+
);
106+
}
107+
});

packages/remark-lint/src/rules/__tests__/invalid-type-reference.test.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ const testCases = [
1919
input: 'First a {string}, then a \\<number>.',
2020
expected: ['Type reference must be wrapped in "{}"; saw "<number>"'],
2121
},
22+
{
23+
name: 'miswrapped reference inside link',
24+
input: '[<number>]()',
25+
expected: ['Type reference must be wrapped in "{}"; saw "<number>"'],
26+
},
2227
{
2328
name: 'multiple references',
2429
input: 'Psst, are you a {string | boolean}',

packages/remark-lint/src/rules/duplicate-stability-nodes.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import createQueries from '@node-core/doc-kit/src/utils/queries/index.mjs';
1+
import { QUERIES } from '@node-core/doc-kit/src/utils/queries/index.mjs';
22
import { lintRule } from 'unified-lint-rule';
33
import { visit } from 'unist-util-visit';
44

@@ -29,7 +29,7 @@ const duplicateStabilityNodes = (tree, vfile) => {
2929
return;
3030
}
3131

32-
const match = createQueries.QUERIES.stabilityIndexPrefix.exec(text); // Match "Stability: X"
32+
const match = QUERIES.stabilityIndexPrefix.exec(text); // Match "Stability: X"
3333
if (!match) {
3434
return;
3535
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { lintRule } from 'unified-lint-rule';
2+
import { visit } from 'unist-util-visit';
3+
4+
const DEPRECATION_COMMENT = /^<!-- md-lint skip-deprecation (DEP\d{4}) -->$/;
5+
const DEPRECATION_HEADING = /^(DEP\d{4}):/;
6+
const DEPRECATION_YAML = /^<!-- YAML\r?\nchanges:\r?\n/;
7+
8+
const generateDeprecation = code => `DEP${code.toString().padStart(4, '0')}`;
9+
10+
/**
11+
* Ensures all needed metadata exists
12+
* @type {import('unified-lint-rule').Rule}
13+
*/
14+
const invalidDeprecations = (tree, vfile) => {
15+
if (vfile.stem !== 'deprecations') {
16+
// Skip non-deprecation files
17+
return;
18+
}
19+
20+
let expectedDeprecationCode = 1;
21+
22+
visit(tree, ['heading', 'html'], (node, idx, parent) => {
23+
// Get the current deprecation
24+
const deprecationCode =
25+
node.type === 'html'
26+
? node.value.match(DEPRECATION_COMMENT)?.[1]
27+
: node.children[0].value.match(DEPRECATION_HEADING)?.[1];
28+
29+
if (node.type === 'heading') {
30+
const typeNode = parent.children[idx + 1]?.children[0];
31+
32+
if (!typeNode?.value?.startsWith('Type:')) {
33+
vfile.message(
34+
`Deprecation "${deprecationCode}" is missing a "Type"`,
35+
node
36+
);
37+
}
38+
39+
const changesNode = parent.children[idx + 2];
40+
41+
if (!DEPRECATION_YAML.test(changesNode?.value)) {
42+
vfile.message(
43+
`Deprecation "${deprecationCode}" is missing changes`,
44+
node
45+
);
46+
}
47+
}
48+
49+
// If not found, skip
50+
if (!deprecationCode) {
51+
return;
52+
}
53+
54+
const generatedDeprecationCode = generateDeprecation(
55+
expectedDeprecationCode
56+
);
57+
58+
if (deprecationCode !== generatedDeprecationCode) {
59+
vfile.message(
60+
`Deprecation codes are out of order. Expected ${generatedDeprecationCode}, saw "${deprecationCode}"`,
61+
node
62+
);
63+
}
64+
65+
expectedDeprecationCode++;
66+
});
67+
};
68+
69+
export default lintRule('node-core:invalid-deprecations', invalidDeprecations);

packages/remark-lint/src/rules/invalid-type-reference.mjs

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { transformTypeToReferenceLink } from '@node-core/doc-kit/src/utils/parser/index.mjs';
2-
import createQueries from '@node-core/doc-kit/src/utils/queries/index.mjs';
1+
import { transformTypeToReferenceLink } from '@node-core/doc-kit/src/generators/metadata/utils/transformers.mjs';
2+
import { QUERIES } from '@node-core/doc-kit/src/utils/queries/index.mjs';
33
import { lintRule } from 'unified-lint-rule';
44
import { visit } from 'unist-util-visit';
55

@@ -11,36 +11,42 @@ const REPLACE_RE = /\s*\| */g;
1111
* @type {import('unified-lint-rule').Rule<, import('../api.mjs').Options>}
1212
*/
1313
const invalidTypeReference = (tree, vfile, { typeMap = {} }) => {
14-
visit(tree, createQueries.UNIST.isTextWithType, node => {
15-
const types = node.value.match(createQueries.QUERIES.normalizeTypes);
16-
17-
types.forEach(type => {
18-
// Ensure wrapped in {}
19-
if (type[0] !== '{' || type[type.length - 1] !== '}') {
20-
vfile.message(
21-
`Type reference must be wrapped in "{}"; saw "${type}"`,
22-
node
23-
);
24-
25-
node.value = node.value.replace(type, `{${type.slice(1, -1)}}`);
26-
}
27-
28-
// Fix spaces around |
29-
if (MATCH_RE.test(type)) {
30-
vfile.message(
31-
`Type reference should be separated by "|", without spaces; saw "${type}"`,
32-
node
33-
);
34-
35-
const normalized = type.replace(REPLACE_RE, '|');
36-
node.value = node.value.replace(type, normalized);
37-
}
38-
39-
if (transformTypeToReferenceLink(type, typeMap) === type) {
40-
vfile.message(`Invalid type reference: ${type}`, node);
41-
}
42-
});
43-
});
14+
visit(
15+
tree,
16+
({ value }) => QUERIES.normalizeTypes.test(value),
17+
node => {
18+
const types = node.value.match(QUERIES.normalizeTypes);
19+
20+
types.forEach(type => {
21+
// Ensure wrapped in {}
22+
if (type[0] !== '{' || type[type.length - 1] !== '}') {
23+
vfile.message(
24+
`Type reference must be wrapped in "{}"; saw "${type}"`,
25+
node
26+
);
27+
28+
const newType = `{${type.slice(1, -1)}}`;
29+
node.value = node.value.replace(type, newType);
30+
type = newType;
31+
}
32+
33+
// Fix spaces around |
34+
if (MATCH_RE.test(type)) {
35+
vfile.message(
36+
`Type reference should be separated by "|", without spaces; saw "${type}"`,
37+
node
38+
);
39+
40+
const normalized = type.replace(REPLACE_RE, '|');
41+
node.value = node.value.replace(type, normalized);
42+
}
43+
44+
if (transformTypeToReferenceLink(type, typeMap) === type) {
45+
vfile.message(`Invalid type reference: ${type}`, node);
46+
}
47+
});
48+
}
49+
);
4450
};
4551

4652
export default lintRule(

0 commit comments

Comments
 (0)