Skip to content

Commit 86faa0f

Browse files
committed
add stability linting
1 parent 3443d7e commit 86faa0f

File tree

7 files changed

+76
-12
lines changed

7 files changed

+76
-12
lines changed

bin/cli.mjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import createMarkdownParser from '../src/parsers/markdown.mjs';
1414
import createNodeReleases from '../src/releases.mjs';
1515
import createLinter from '../src/linter/index.mjs';
1616
import reporters from '../src/linter/reporters/index.mjs';
17-
import rules from '../src/linter/rules/index.mjs';
17+
import {
18+
multiEntryRules,
19+
singleEntryRules,
20+
} from '../src/linter/rules/index.mjs';
1821

1922
const availableGenerators = Object.keys(generators);
2023

@@ -61,7 +64,9 @@ program
6164
)
6265
.addOption(
6366
new Option('--disable-rule [rule...]', 'Disable a specific linter rule')
64-
.choices(Object.keys(rules))
67+
.choices(
68+
Object.keys(multiEntryRules).concat(Object.keys(singleEntryRules))
69+
)
6570
.default([])
6671
)
6772
.addOption(

src/constants.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,5 @@ export const LINT_MESSAGES = {
425425
missingIntroducedIn: "Missing 'introduced_in' field in the API doc entry",
426426
missingChangeVersion: 'Missing version field in the API doc entry',
427427
invalidChangeVersion: 'Invalid version number: {{version}}',
428+
duplicateStabilityNode: 'Duplicate stability node',
428429
};

src/linter/engine.mjs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
/**
44
* Creates a linter engine instance to validate ApiDocMetadataEntry entries
55
*
6-
* @param {import('./types').LintRule} rules Lint rules to validate the entries against
6+
* @param {{
7+
* multiEntryRules: import('./types').MultipleEntriesLintRules[]
8+
* singleEntryRules: import('./types').SingleEntryLintRule[]
9+
* }} rules Lint rules to validate the entries against
710
*/
8-
const createLinterEngine = rules => {
11+
const createLinterEngine = ({ multiEntryRules, singleEntryRules }) => {
912
/**
1013
* Validates a ApiDocMetadataEntry entry against all defined rules
1114
*
@@ -15,7 +18,7 @@ const createLinterEngine = rules => {
1518
const lint = entry => {
1619
const issues = [];
1720

18-
for (const rule of rules) {
21+
for (const rule of singleEntryRules) {
1922
const ruleIssues = rule(entry);
2023

2124
if (ruleIssues.length > 0) {
@@ -35,6 +38,10 @@ const createLinterEngine = rules => {
3538
const lintAll = entries => {
3639
const issues = [];
3740

41+
for (const rule of multiEntryRules) {
42+
issues.push(...rule(entries));
43+
}
44+
3845
for (const entry of entries) {
3946
issues.push(...lint(entry));
4047
}

src/linter/index.mjs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import createLinterEngine from './engine.mjs';
44
import reporters from './reporters/index.mjs';
5-
import rules from './rules/index.mjs';
5+
import { multiEntryRules, singleEntryRules } from './rules/index.mjs';
66

77
/**
88
* Creates a linter instance to validate ApiDocMetadataEntry entries
@@ -13,16 +13,19 @@ import rules from './rules/index.mjs';
1313
const createLinter = (dryRun, disabledRules) => {
1414
/**
1515
* Retrieves all enabled rules
16-
*
16+
* @param {Record<string, import('./types').LintRule>} rules
1717
* @returns {import('./types').LintRule[]}
1818
*/
19-
const getEnabledRules = () => {
19+
const getEnabledRules = rules => {
2020
return Object.entries(rules)
2121
.filter(([ruleName]) => !disabledRules.includes(ruleName))
2222
.map(([, rule]) => rule);
2323
};
2424

25-
const engine = createLinterEngine(getEnabledRules(disabledRules));
25+
const engine = createLinterEngine({
26+
multiEntryRules: getEnabledRules(multiEntryRules),
27+
singleEntryRules: getEnabledRules(singleEntryRules),
28+
});
2629

2730
/**
2831
* Lint issues found during validations
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { LINT_MESSAGES } from '../../constants.mjs';
2+
3+
/**
4+
* Checks if there are multiple stability nodes within a chain.
5+
*
6+
* @param {ApiDocMetadataEntry[]} entries
7+
* @returns {Array<import('../types').LintIssue>}
8+
*/
9+
export const duplicateStabilityNodes = entries => {
10+
const issues = [];
11+
let currentDepth = 0;
12+
let currentStability = -1;
13+
14+
for (const entry of entries) {
15+
const { depth } = entry.heading.data;
16+
const entryStability = entry.stability.children[0]?.data.index ?? -1;
17+
18+
if (
19+
depth > currentDepth &&
20+
entryStability >= 0 &&
21+
entryStability === currentStability
22+
) {
23+
issues.push({
24+
level: 'warn',
25+
message: LINT_MESSAGES.duplicateStabilityNode,
26+
location: {
27+
path: entry.api_doc_source,
28+
position: entry.yaml_position,
29+
},
30+
});
31+
} else {
32+
currentDepth = depth;
33+
currentStability = entryStability;
34+
}
35+
}
36+
37+
return issues;
38+
};

src/linter/rules/index.mjs

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

3+
import { duplicateStabilityNodes } from './duplicate-stability-nodes.mjs';
34
import { invalidChangeVersion } from './invalid-change-version.mjs';
45
import { missingChangeVersion } from './missing-change-version.mjs';
56
import { missingIntroducedIn } from './missing-introduced-in.mjs';
67

78
/**
8-
* @type {Record<string, import('../types').LintRule>}
9+
* @type {Record<string, import('../types').SingleEntryLintRule>}
910
*/
10-
export default {
11+
export const singleEntryRules = {
1112
'invalid-change-version': invalidChangeVersion,
1213
'missing-change-version': missingChangeVersion,
1314
'missing-introduced-in': missingIntroducedIn,
1415
};
16+
17+
/**
18+
* @type {Record<string, import('../types').MultipleEntriesLintRule>}
19+
*/
20+
export const multiEntryRules = {
21+
'duplicate-stability-nodes': duplicateStabilityNodes,
22+
};

src/linter/types.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export interface LintIssue {
1313
location: LintIssueLocation;
1414
}
1515

16-
type LintRule = (input: ApiDocMetadataEntry) => LintIssue[];
16+
type LintRule = MultipleEntriesLintRules | SingleEntryLintRule;
17+
type MultipleEntriesLintRules = (input: ApiDocMetadataEntry[]) => LintIssue[];
18+
type SingleEntryLintRule = (input: ApiDocMetadataEntry) => LintIssue[];
1719

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

0 commit comments

Comments
 (0)