Skip to content

Commit 6f79ff5

Browse files
committed
feat(remark-lint): add strict rules
1 parent a9e90a2 commit 6f79ff5

File tree

6 files changed

+1011
-67
lines changed

6 files changed

+1011
-67
lines changed

packages/remark-lint/package.json

Lines changed: 2 additions & 1 deletion
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.0.0",
4+
"version": "1.1.0",
55
"exports": {
66
".": "./src/index.mjs",
77
"./api": "./src/api.mjs"
@@ -20,6 +20,7 @@
2020
"test:unit": "cross-env NODE_NO_WARNINGS=1 node --experimental-test-coverage --test \"**/*.test.mjs\""
2121
},
2222
"dependencies": {
23+
"@nodejs/doc-kit": "github:nodejs/doc-kit",
2324
"remark-gfm": "^4.0.1",
2425
"remark-lint-blockquote-indentation": "^4.0.1",
2526
"remark-lint-checkbox-character-style": "^5.0.1",

packages/remark-lint/src/api.mjs

Lines changed: 3 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 invalidTypeReference from './rules/invalid-type-reference.mjs';
1011
import orderedReferences from './rules/ordered-references.mjs';
1112
import requiredMetadata from './rules/required-metadata.mjs';
1213
import yamlComments from './rules/yaml/index.mjs';
@@ -34,6 +35,7 @@ export default (options = {}) => ({
3435
hashedSelfReference,
3536
orderedReferences,
3637
requiredMetadata,
38+
invalidTypeReference,
3739
].map(plugin => [plugin, options]),
3840

3941
// External Rules
@@ -61,6 +63,7 @@ export default (options = {}) => ({
6163
{ yes: 'Unix' },
6264
{ yes: 'Valgrind' },
6365
{ yes: 'V8' },
66+
{ yes: 'npm' },
6467
],
6568
],
6669
],
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, it } from 'node:test';
2+
3+
import { testRule } from './utils.mjs';
4+
import invalidTypeReference from '../invalid-type-reference.mjs';
5+
6+
const testCases = [
7+
{
8+
name: 'no references',
9+
input: 'Just some text.',
10+
expected: [],
11+
},
12+
{
13+
name: 'single reference',
14+
input: 'Just a {number}.',
15+
expected: [],
16+
},
17+
{
18+
name: 'multiple references',
19+
input: 'Psst, are you a {string} or a {boolean}',
20+
expected: [],
21+
},
22+
{
23+
name: 'invalid references',
24+
input: 'This is {invalid}.',
25+
expected: ['Invalid type reference: {invalid}'],
26+
},
27+
];
28+
29+
describe('hashed-self-references', () => {
30+
for (const { name, input, expected } of testCases) {
31+
it(name, () => testRule(invalidTypeReference, input, expected));
32+
}
33+
});

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

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,43 @@
1+
import createQueries from '@nodejs/doc-kit/src/utils/queries/index.mjs';
12
import { lintRule } from 'unified-lint-rule';
23
import { visit } from 'unist-util-visit';
34

4-
// TODO(@avivkeller): This is re-used from doc-kit
5-
// Regex to match "Stability: <number>" in blockquotes
6-
const STABILITY = /Stability: ([0-5](?:\.[0-3])?)/;
7-
85
/**
96
* Finds and reports duplicate stability nodes
107
* @type {import('unified-lint-rule').Rule}
118
*/
129
const duplicateStabilityNodes = (tree, vfile) => {
13-
let currentDepth = 0;
14-
let currentStability = -1;
15-
let currentHeaderDepth = 0;
10+
// Map depth → stability string recorded at that depth
11+
const stabilityByDepth = new Map();
12+
let currentHeadingDepth = 0; // Current heading depth (0 for "no heading")
1613

17-
visit(tree, node => {
18-
// Update the current heading depth whenever a heading node is encountered
14+
visit(tree, ['heading', 'blockquote'], node => {
1915
if (node.type === 'heading') {
20-
currentHeaderDepth = node.depth;
16+
// Update heading depth and clear deeper recorded stabilities
17+
currentHeadingDepth = node.depth;
18+
for (const depth of stabilityByDepth.keys()) {
19+
if (depth >= currentHeadingDepth) stabilityByDepth.delete(depth);
20+
}
21+
return;
2122
}
2223

23-
// Look for blockquotes which may contain stability indicators
24-
if (node.type === 'blockquote') {
25-
// Assume the first child is a paragraph
26-
const paragraph = node.children?.[0];
27-
// And the first child of that paragraph is text
28-
const text = paragraph?.children?.[0];
24+
// Handle blockquotes: extract text from paragraph > text structure
25+
const text = node.children?.[0]?.children?.[0]?.value;
26+
if (!text) return;
2927

30-
// Ensure structure is paragraph > text
31-
if (paragraph?.type === 'paragraph' && text?.type === 'text') {
32-
// Try to match "Stability: X"
33-
const match = text.value.match(STABILITY);
34-
if (match) {
35-
const stability = parseFloat(match[1]);
36-
// If the heading got deeper, and stability is valid and matches previous, report a duplicate
37-
if (
38-
currentHeaderDepth > currentDepth &&
39-
stability >= 0 &&
40-
stability === currentStability
41-
) {
42-
vfile.message('Duplicate stability node', node);
43-
} else {
44-
// Otherwise, record this stability and heading depth
45-
currentDepth = currentHeaderDepth;
46-
currentStability = stability;
47-
}
48-
}
28+
const match = createQueries.QUERIES.stabilityIndexPrefix.exec(text); // Match "Stability: X"
29+
if (!match) return;
30+
31+
const stability = match[1];
32+
// Report if a duplicate stability exists in a parent heading depth
33+
for (const [depth, prevStability] of stabilityByDepth) {
34+
if (depth < currentHeadingDepth && prevStability === stability) {
35+
vfile.message('Duplicate stability node', node);
36+
break;
4937
}
5038
}
39+
40+
stabilityByDepth.set(currentHeadingDepth, stability);
5141
});
5242
};
5343

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { transformTypeToReferenceLink } from '@nodejs/doc-kit/src/utils/parser/index.mjs';
2+
import createQueries from '@nodejs/doc-kit/src/utils/queries/index.mjs';
3+
import { lintRule } from 'unified-lint-rule';
4+
import { visit } from 'unist-util-visit';
5+
6+
/**
7+
* Ensures that all self-references begin with `#`
8+
* @type {import('unified-lint-rule').Rule}
9+
*/
10+
const invalidTypeReference = (tree, vfile) => {
11+
visit(tree, createQueries.UNIST.isTextWithType, node => {
12+
const types = node.value.match(createQueries.QUERIES.normalizeTypes);
13+
14+
types.forEach(type => {
15+
if (transformTypeToReferenceLink(type) === type) {
16+
vfile.message(`Invalid type reference: ${type}`, node);
17+
}
18+
});
19+
});
20+
};
21+
22+
export default lintRule(
23+
'node-core:invalid-type-reference',
24+
invalidTypeReference
25+
);

0 commit comments

Comments
 (0)