Skip to content

Commit 9be11fd

Browse files
authored
chore(lint): report error on invalid change versions, except REPLACEME (#238)
* chore(lint): report error on invalid change versions, except when REPLACEME * don't assume`v` in NODE_RELEASED_VERSIONS * code review
1 parent e991c6f commit 9be11fd

File tree

4 files changed

+130
-50
lines changed

4 files changed

+130
-50
lines changed
+46-34
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,54 @@
11
import { LINT_MESSAGES } from '../constants.mjs';
22
import { valid } from 'semver';
3+
import { env } from 'node:process';
4+
5+
const NODE_RELEASED_VERSIONS = env.NODE_RELEASED_VERSIONS?.split(',');
36

47
/**
5-
* Checks if any change version is invalid
8+
* Checks if the given version is "REPLACEME" and the array length is 1.
69
*
7-
* @param {ApiDocMetadataEntry[]} entries
8-
* @returns {Array<import('../types').LintIssue>}
10+
* @param {string} version - The version to check.
11+
* @param {number} length - Length of the version array.
12+
* @returns {boolean} True if conditions match, otherwise false.
913
*/
10-
export const invalidChangeVersion = entries => {
11-
const issues = [];
12-
13-
for (const entry of entries) {
14-
if (entry.changes.length === 0) continue;
15-
16-
const allVersions = entry.changes
17-
.filter(change => change.version)
18-
.flatMap(change =>
19-
Array.isArray(change.version) ? change.version : [change.version]
20-
);
14+
const isValidReplaceMe = (version, length) =>
15+
length === 1 && version === 'REPLACEME';
2116

22-
const invalidVersions = allVersions.filter(
23-
version => valid(version) === null
24-
);
25-
26-
issues.push(
27-
...invalidVersions.map(version => ({
28-
level: 'warn',
29-
message: LINT_MESSAGES.invalidChangeVersion.replace(
30-
'{{version}}',
31-
version
32-
),
33-
location: {
34-
path: entry.api_doc_source,
35-
position: entry.yaml_position,
36-
},
37-
}))
38-
);
39-
}
17+
/**
18+
* Determines if a given version is invalid.
19+
*
20+
* @param {string} version - The version to check.
21+
* @param {unknown} _ - Unused parameter.
22+
* @param {{ length: number }} context - Array containing the length property.
23+
* @returns {boolean} True if the version is invalid, otherwise false.
24+
*/
25+
const isInvalid = NODE_RELEASED_VERSIONS
26+
? (version, _, { length }) =>
27+
!(
28+
isValidReplaceMe(version, length) ||
29+
NODE_RELEASED_VERSIONS.includes(version.replace(/^v/, ''))
30+
)
31+
: (version, _, { length }) =>
32+
!(isValidReplaceMe(version, length) || valid(version));
4033

41-
return issues;
42-
};
34+
/**
35+
* Identifies invalid change versions from metadata entries.
36+
*
37+
* @param {ApiDocMetadataEntry[]} entries - Metadata entries to check.
38+
* @returns {import('../types').LintIssue[]} List of detected lint issues.
39+
*/
40+
export const invalidChangeVersion = entries =>
41+
entries.flatMap(({ changes, api_doc_source, yaml_position }) =>
42+
changes.flatMap(({ version }) =>
43+
(Array.isArray(version) ? version : [version])
44+
.filter(isInvalid)
45+
.map(version => ({
46+
level: 'error',
47+
message: LINT_MESSAGES.invalidChangeVersion.replace(
48+
'{{version}}',
49+
version
50+
),
51+
location: { path: api_doc_source, position: yaml_position },
52+
}))
53+
)
54+
);

src/linter/tests/fixtures/entries.mjs

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export const assertEntry = {
3939
'pr-url': 'https://github.com/nodejs/node/pull/34001',
4040
description: "Exposed as `require('node:assert/strict')`.",
4141
},
42+
{
43+
version: 'REPLACEME',
44+
'pr-url': 'https://github.com/nodejs/node/pull/12345',
45+
description: 'This is a test entry.',
46+
},
4247
],
4348
heading: {
4449
type: 'heading',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { invalidChangeVersion } from '../../rules/invalid-change-version.mjs';
2+
import { deepEqual } from 'node:assert';
3+
import { assertEntry } from './entries.mjs';
4+
5+
const issues = invalidChangeVersion([
6+
{
7+
...assertEntry,
8+
changes: [
9+
...assertEntry.changes,
10+
{ version: ['SOME_OTHER_RELEASED_VERSION'] },
11+
],
12+
},
13+
]);
14+
15+
deepEqual(issues, []);

src/linter/tests/rules/invalid-change-version.test.mjs

+64-16
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,43 @@
11
import { describe, it } from 'node:test';
22
import { invalidChangeVersion } from '../../rules/invalid-change-version.mjs';
3-
import { deepEqual } from 'node:assert';
3+
import { deepEqual, strictEqual } from 'node:assert';
44
import { assertEntry } from '../fixtures/entries.mjs';
5+
import { spawnSync } from 'node:child_process';
6+
import { fileURLToPath } from 'node:url';
7+
import { execPath } from 'node:process';
58

69
describe('invalidChangeVersion', () => {
7-
it('should return an empty array if all change versions are valid', () => {
8-
const issues = invalidChangeVersion([assertEntry]);
10+
it('should work with NODE_RELEASED_VERSIONS', () => {
11+
const result = spawnSync(
12+
execPath,
13+
[
14+
fileURLToPath(
15+
new URL(
16+
'../fixtures/invalidChangeVersion-environment.mjs',
17+
import.meta.url
18+
)
19+
),
20+
],
21+
{
22+
env: {
23+
NODE_RELEASED_VERSIONS: [
24+
'9.9.0',
25+
'13.9.0',
26+
'12.16.2',
27+
'15.0.0',
28+
'REPLACEME',
29+
'SOME_OTHER_RELEASED_VERSION',
30+
].join(','),
31+
},
32+
}
33+
);
934

10-
deepEqual(issues, []);
35+
strictEqual(result.status, 0);
36+
strictEqual(result.error, undefined);
37+
});
38+
39+
it('should return an empty array if all change versions are valid', () => {
40+
deepEqual(invalidChangeVersion([assertEntry]), []);
1141
});
1242

1343
it('should return an issue if a change version is invalid', () => {
@@ -16,27 +46,45 @@ describe('invalidChangeVersion', () => {
1646
...assertEntry,
1747
changes: [
1848
...assertEntry.changes,
19-
{ version: ['v13.9.0', 'REPLACEME'] },
49+
{ version: ['v13.9.0', 'INVALID_VERSION'] },
50+
],
51+
},
52+
]);
53+
54+
deepEqual(issues, [
55+
{
56+
level: 'error',
57+
location: {
58+
path: 'doc/api/assert.md',
59+
position: {
60+
start: { column: 1, line: 7, offset: 103 },
61+
end: { column: 35, line: 7, offset: 137 },
62+
},
63+
},
64+
message: 'Invalid version number: INVALID_VERSION',
65+
},
66+
]);
67+
});
68+
69+
it('should return an issue if a change version contains a REPLACEME and a version', () => {
70+
const issues = invalidChangeVersion([
71+
{
72+
...assertEntry,
73+
changes: [
74+
...assertEntry.changes,
75+
{ version: ['v24.0.0', 'REPLACEME'] },
2076
],
2177
},
2278
]);
2379

2480
deepEqual(issues, [
2581
{
26-
level: 'warn',
82+
level: 'error',
2783
location: {
2884
path: 'doc/api/assert.md',
2985
position: {
30-
end: {
31-
column: 35,
32-
line: 7,
33-
offset: 137,
34-
},
35-
start: {
36-
column: 1,
37-
line: 7,
38-
offset: 103,
39-
},
86+
start: { column: 1, line: 7, offset: 103 },
87+
end: { column: 35, line: 7, offset: 137 },
4088
},
4189
},
4290
message: 'Invalid version number: REPLACEME',

0 commit comments

Comments
 (0)