Skip to content

Commit 94ba959

Browse files
committed
feat(linter): introduce raw ast rules
1 parent e991c6f commit 94ba959

File tree

7 files changed

+53
-38
lines changed

7 files changed

+53
-38
lines changed

bin/cli.mjs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const {
113113
const linter = createLinter(lintDryRun, disableRule);
114114

115115
const { loadFiles } = createMarkdownLoader();
116-
const { parseApiDocs } = createMarkdownParser();
116+
const { parseApiDocs } = createMarkdownParser(linter);
117117

118118
const apiDocFiles = await loadFiles(input, ignore);
119119

@@ -124,9 +124,6 @@ const { runGenerators } = createGenerator(parsedApiDocs);
124124
// Retrieves Node.js release metadata from a given Node.js version and CHANGELOG.md file
125125
const { getAllMajors } = createNodeReleases(changelog);
126126

127-
// Runs the Linter on the parsed API docs
128-
linter.lintAll(parsedApiDocs);
129-
130127
if (target) {
131128
await runGenerators({
132129
// A list of target modes for the API docs parser

src/linter/engine.mjs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
'use strict';
22

33
/**
4-
* Creates a linter engine instance to validate ApiDocMetadataEntry entries
4+
* Creates a linter engine instance to validate mdast trees.
55
*
66
* @param {import('./types').LintRule[]} rules Lint rules to validate the entries against
77
*/
88
const createLinterEngine = rules => {
99
/**
10-
* Validates an array of ApiDocMetadataEntry entries against all defined rules
10+
* Validates an array of mdast trees against all defined rules
1111
*
12-
* @param {ApiDocMetadataEntry[]} entries
12+
* @param {import('mdast').Root[]} ast
1313
* @returns {import('./types').LintIssue[]}
1414
*/
15-
const lintAll = entries => {
15+
const lint = ast => {
1616
const issues = [];
1717

1818
for (const rule of rules) {
19-
issues.push(...rule(entries));
19+
issues.push(...rule(ast));
2020
}
2121

2222
return issues;
2323
};
2424

2525
return {
26-
lintAll,
26+
lint,
2727
};
2828
};
2929

src/linter/index.mjs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import reporters from './reporters/index.mjs';
55
import rules from './rules/index.mjs';
66

77
/**
8-
* Creates a linter instance to validate ApiDocMetadataEntry entries
8+
* Creates a linter instance to validate mdast trees
99
*
1010
* @param {boolean} dryRun Whether to run the engine in dry-run mode
1111
* @param {string[]} disabledRules List of disabled rules names
12+
* @returns {import('./types').Linter}
1213
*/
1314
const createLinter = (dryRun, disabledRules) => {
1415
/**
@@ -34,10 +35,11 @@ const createLinter = (dryRun, disabledRules) => {
3435
/**
3536
* Lints all entries using the linter engine
3637
*
37-
* @param entries
38+
* @param {import('mdast').Root} ast
39+
* @returns {void}
3840
*/
39-
const lintAll = entries => {
40-
issues.push(...engine.lintAll(entries));
41+
const lint = ast => {
42+
issues.push(...engine.lint(ast));
4143
};
4244

4345
/**
@@ -68,7 +70,7 @@ const createLinter = (dryRun, disabledRules) => {
6870
};
6971

7072
return {
71-
lintAll,
73+
lint,
7274
report,
7375
hasError,
7476
};

src/linter/rules/index.mjs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
'use strict';
22

3-
import { duplicateStabilityNodes } from './duplicate-stability-nodes.mjs';
4-
import { invalidChangeVersion } from './invalid-change-version.mjs';
5-
import { missingChangeVersion } from './missing-change-version.mjs';
3+
// import { duplicateStabilityNodes } from './duplicate-stability-nodes.mjs';
4+
// import { invalidChangeVersion } from './invalid-change-version.mjs';
5+
// import { missingChangeVersion } from './missing-change-version.mjs';
66
import { missingIntroducedIn } from './missing-introduced-in.mjs';
77

88
/**
99
* @type {Record<string, import('../types').LintRule>}
1010
*/
1111
export default {
12-
'duplicate-stability-nodes': duplicateStabilityNodes,
13-
'invalid-change-version': invalidChangeVersion,
14-
'missing-change-version': missingChangeVersion,
12+
// 'duplicate-stability-nodes': duplicateStabilityNodes,
13+
// 'invalid-change-version': invalidChangeVersion,
14+
// 'missing-change-version': missingChangeVersion,
1515
'missing-introduced-in': missingIntroducedIn,
1616
};
Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
11
import { LINT_MESSAGES } from '../constants.mjs';
22

33
/**
4-
* Checks if `introduced_in` field is missing
4+
* Checks if `introduced_in` node is missing
55
*
6-
* @param {ApiDocMetadataEntry[]} entries
6+
* @param {import('mdast').Root} tree
77
* @returns {Array<import('../types.d.ts').LintIssue>}
88
*/
9-
export const missingIntroducedIn = entries => {
10-
const issues = [];
9+
export const missingIntroducedIn = tree => {
10+
const regex = /<!--introduced_in=.*-->/;
1111

12-
for (const entry of entries) {
13-
// Early continue if not a top-level heading or if introduced_in exists
14-
if (entry.heading.depth !== 1 || entry.introduced_in) continue;
12+
const introduced_in = tree.children.find(
13+
node => node.type === 'html' && regex.test(node.value)
14+
);
1515

16-
issues.push({
17-
level: 'info',
18-
message: LINT_MESSAGES.missingIntroducedIn,
19-
location: {
20-
path: entry.api_doc_source,
16+
if (!introduced_in) {
17+
return [
18+
{
19+
level: 'info',
20+
message: LINT_MESSAGES.missingIntroducedIn,
21+
location: {
22+
path: '?',
23+
position: {
24+
start: { line: 1, column: 1 },
25+
end: { line: 1, column: 1 },
26+
},
27+
},
2128
},
22-
});
29+
];
2330
}
2431

25-
return issues;
32+
return [];
2633
};

src/linter/types.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import { Root } from 'mdast';
12
import { Position } from 'unist';
23

4+
export interface Linter {
5+
lint: (ast: Root) => void;
6+
report: (reporterName: keyof typeof reporters) => void;
7+
hasError: () => boolean;
8+
}
9+
310
export type IssueLevel = 'info' | 'warn' | 'error';
411

512
export interface LintIssueLocation {
@@ -13,6 +20,6 @@ export interface LintIssue {
1320
location: LintIssueLocation;
1421
}
1522

16-
type LintRule = (input: ApiDocMetadataEntry[]) => LintIssue[];
23+
type LintRule = (input: Root[]) => LintIssue[];
1724

1825
export type Reporter = (msg: LintIssue) => void;

src/parsers/markdown.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import { createNodeSlugger } from '../utils/slugger/index.mjs';
1515
/**
1616
* Creates an API doc parser for a given Markdown API doc file
1717
*
18-
* @param {import('./linter/index.mjs').Linter | undefined} linter
18+
* @param {import('../linter/types').Linter} linter
1919
*/
20-
const createParser = () => {
20+
const createParser = linter => {
2121
// Creates an instance of the Remark processor with GFM support
2222
// which is used for stringifying the AST tree back to Markdown
2323
const remarkProcessor = getRemark();
@@ -63,6 +63,8 @@ const createParser = () => {
6363
// Parses the API doc into an AST tree using `unified` and `remark`
6464
const apiDocTree = remarkProcessor.parse(resolvedApiDoc);
6565

66+
linter.lint(apiDocTree);
67+
6668
// Get all Markdown Footnote definitions from the tree
6769
const markdownDefinitions = selectAll('definition', apiDocTree);
6870

0 commit comments

Comments
 (0)