Skip to content

Commit 0036260

Browse files
committed
feat: wip
1 parent 63e0d97 commit 0036260

14 files changed

+245
-78
lines changed

bin/cli.mjs

+4-3
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ program
5252
'Set the processing target modes'
5353
).choices(availableGenerators)
5454
)
55-
.addOption(new Option('--skip-validation', 'TODO').default(false))
55+
.addOption(new Option('--skip-linting', 'Skip linting').default(false))
5656
.addOption(
57-
new Option('--reporter', 'TODO')
57+
new Option('--reporter', 'Specify the linter reporter')
5858
.choices(Object.keys(reporters))
5959
.default('console')
6060
)
@@ -98,6 +98,8 @@ const { runGenerators } = createGenerator(parsedApiDocs);
9898
// Retrieves Node.js release metadata from a given Node.js version and CHANGELOG.md file
9999
const { getAllMajors } = createNodeReleases(changelog);
100100

101+
linter?.lintAll(parsedApiDocs);
102+
101103
await runGenerators({
102104
// A list of target modes for the API docs parser
103105
generators: target,
@@ -109,7 +111,6 @@ await runGenerators({
109111
version: coerce(version),
110112
// A list of all Node.js major versions with LTS status
111113
releases: await getAllMajors(),
112-
linter,
113114
});
114115

115116
if (linter) {

package-lock.json

+66
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
},
3131
"dependencies": {
3232
"acorn": "^8.14.0",
33+
"@actions/core": "^1.11.1",
3334
"commander": "^13.1.0",
3435
"estree-util-visit": "^2.0.0",
3536
"dedent": "^1.5.3",

src/linter/index.mjs

+27-51
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,46 @@
22
'use strict';
33

44
import reporters from './reporters/index.mjs';
5+
import { invalidChangeVersion } from './rules/invalid-change-version.mjs';
6+
import { missingChangeVersion } from './rules/missing-change-version.mjs';
7+
import { missingIntroducedIn } from './rules/missing-introduced-in.mjs';
58

69
/**
7-
*
10+
* Lint issues in ApiDocMetadataEntry entries
811
*/
912
export class Linter {
10-
#_hasError = false;
13+
/**
14+
* @type {Array<import('./types.d.ts').LintIssue>}
15+
*/
16+
#issues = [];
1117

1218
/**
13-
* @type {Array<import('./types.d.ts').LintMessage>}
19+
* @type {Array<import('./types.d.ts').LintRule>}
1420
*/
15-
#messages = [];
21+
#rules = [missingIntroducedIn, missingChangeVersion, invalidChangeVersion];
1622

1723
/**
18-
*
24+
* @param {ApiDocMetadataEntry} entry
25+
* @returns {void}
1926
*/
20-
get hasError() {
21-
return this.#_hasError;
27+
lint(entry) {
28+
for (const rule of this.#rules) {
29+
const issues = rule(entry);
30+
31+
if (issues.length > 0) {
32+
this.#issues.push(...issues);
33+
}
34+
}
2235
}
2336

2437
/**
25-
* @param {import('./types.d.ts').LintMessage} msg
38+
* @param {ApiDocMetadataEntry[]} entries
39+
* @returns {void}
2640
*/
27-
log(msg) {
28-
if (msg.level === 'error') {
29-
this.#_hasError = true;
41+
lintAll(entries) {
42+
for (const entry of entries) {
43+
this.lint(entry);
3044
}
31-
32-
this.#messages.push(msg);
3345
}
3446

3547
/**
@@ -38,44 +50,8 @@ export class Linter {
3850
report(reporterName) {
3951
const reporter = reporters[reporterName];
4052

41-
for (const message of this.#messages) {
42-
reporter(message);
53+
for (const issue of this.#issues) {
54+
reporter(issue);
4355
}
4456
}
45-
46-
/**
47-
* @param {string} msg
48-
* @param {import('./types.d.ts').LintMessageLocation | undefined} location
49-
*/
50-
info(msg, location) {
51-
this.log({
52-
level: 'info',
53-
msg,
54-
location,
55-
});
56-
}
57-
58-
/**
59-
* @param {string} msg
60-
* @param {import('./types.d.ts').LintMessageLocation | undefined} location
61-
*/
62-
warn(msg, location) {
63-
this.log({
64-
level: 'warn',
65-
msg,
66-
location,
67-
});
68-
}
69-
70-
/**
71-
* @param {string} msg
72-
* @param {import('./types.d.ts').LintMessageLocation | undefined} location
73-
*/
74-
error(msg, location) {
75-
this.log({
76-
level: 'error',
77-
msg,
78-
location,
79-
});
80-
}
8157
}

src/linter/reporters/console.mjs

+9-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import { styleText } from 'node:util';
66

77
/**
8-
* TODO is there a way to grab the parameter type for styleText since the types aren't exported
9-
* @type {Record<import('../types.d.ts').LintLevel, string>}
8+
* @type {Record<import('../types.d.ts').IssueLevel, string>}
109
*/
1110
const levelToColorMap = {
1211
info: 'gray',
@@ -17,6 +16,12 @@ const levelToColorMap = {
1716
/**
1817
* @type {import('../types.d.ts').Reporter}
1918
*/
20-
export default msg => {
21-
console.log(styleText(levelToColorMap[msg.level], msg.msg));
19+
export default issue => {
20+
console.log(
21+
styleText(
22+
// @ts-expect-error ForegroundColors is not exported
23+
levelToColorMap[issue.level],
24+
`${issue.message} at ${issue.location.path} (${issue.location.position.start}:${issue.location.position.end})`
25+
)
26+
);
2227
};

src/linter/reporters/github.mjs

+14-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,23 @@
22

33
'use strict';
44

5+
import * as core from '@actions/core';
6+
57
/**
68
* GitHub action reporter for
79
*
810
* @type {import('../types.d.ts').Reporter}
911
*/
10-
export default msg => {
11-
// TODO
12-
console.log(msg);
12+
export default issue => {
13+
const actions = {
14+
warn: core.warning,
15+
error: core.error,
16+
info: core.notice,
17+
};
18+
19+
(actions[issue.level] || core.notice)(issue.message, {
20+
file: issue.location.path,
21+
startLine: issue.location.position.start.line,
22+
endLine: issue.location.position.end.line,
23+
});
1324
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { validateVersion } from '../utils/semver.mjs';
2+
3+
/**
4+
* Checks if any change version is invalid.
5+
*
6+
* @param {ApiDocMetadataEntry} entry
7+
* @returns {Array<import('../types').LintIssue>}
8+
*/
9+
export const invalidChangeVersion = entry => {
10+
if (entry.changes.length === 0) {
11+
return [];
12+
}
13+
14+
const allVersions = entry.changes
15+
.filter(change => change.version)
16+
.flatMap(change =>
17+
Array.isArray(change.version) ? change.version : [change.version]
18+
);
19+
20+
const invalidVersions = allVersions.filter(
21+
version => !validateVersion(version.substring(1)) // Trim the leading 'v' from the version string
22+
);
23+
24+
return invalidVersions.map(version => ({
25+
level: 'warn',
26+
message: `Invalid version number: ${version}`,
27+
location: {
28+
path: entry.api_doc_source,
29+
line: entry.yaml_position.start.line,
30+
column: entry.yaml_position.start.column,
31+
},
32+
}));
33+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Checks if any change version is missing.
3+
*
4+
* @param {ApiDocMetadataEntry} entry
5+
* @returns {Array<import('../types').LintIssue>}
6+
*/
7+
export const missingChangeVersion = entry => {
8+
if (entry.changes.length === 0) {
9+
return [];
10+
}
11+
12+
return entry.changes
13+
.filter(change => !change.version)
14+
.map(() => ({
15+
level: 'warn',
16+
message: 'Missing change version',
17+
location: {
18+
path: entry.api_doc_source,
19+
line: entry.yaml_position.start.line,
20+
column: entry.yaml_position.start.column,
21+
},
22+
}));
23+
};
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Checks if `introduced_in` field is missing in the API doc entry.
3+
*
4+
* @param {ApiDocMetadataEntry} entry
5+
* @returns {Array<import('../types.d.ts').LintIssue>}
6+
*/
7+
export const missingIntroducedIn = entry => {
8+
// Early return if not a top-level heading or if introduced_in exists
9+
if (entry.heading.depth !== 1 || entry.introduced_in) {
10+
return [];
11+
}
12+
13+
return [
14+
{
15+
level: 'info',
16+
message: 'Missing `introduced_in` field in the API doc entry',
17+
location: {
18+
path: entry.api_doc_source,
19+
// line: entry.yaml_position.start,
20+
// column: entry.yaml_position.end,
21+
},
22+
},
23+
];
24+
};

0 commit comments

Comments
 (0)