Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/remark-lint/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@node-core/remark-lint",
"type": "module",
"version": "1.2.1",
"version": "1.3.0",
"exports": {
".": "./src/index.mjs",
"./api": "./src/api.mjs"
Expand All @@ -20,7 +20,7 @@
"test:unit": "cross-env NODE_NO_WARNINGS=1 node --experimental-test-coverage --test \"**/*.test.mjs\""
},
"dependencies": {
"@node-core/doc-kit": "^1.0.0",
"@node-core/doc-kit": "^1.0.2",
"remark-gfm": "^4.0.1",
"remark-lint-blockquote-indentation": "^4.0.1",
"remark-lint-checkbox-character-style": "^5.0.1",
Expand Down
2 changes: 2 additions & 0 deletions packages/remark-lint/src/api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import remarkLintUnorderedListMarkerStyle from 'remark-lint-unordered-list-marke
import basePreset from './index.mjs';
import duplicateStabilityNodes from './rules/duplicate-stability-nodes.mjs';
import hashedSelfReference from './rules/hashed-self-reference.mjs';
import invalidDeprecations from './rules/invalid-deprecations.mjs';
import invalidTypeReference from './rules/invalid-type-reference.mjs';
import orderedReferences from './rules/ordered-references.mjs';
import requiredMetadata from './rules/required-metadata.mjs';
Expand Down Expand Up @@ -37,6 +38,7 @@ export default (options = {}) => ({
orderedReferences,
requiredMetadata,
invalidTypeReference,
invalidDeprecations,
].map(plugin => [plugin, options]),

// External Rules
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { describe, it } from 'node:test';

import dedent from 'dedent';

import { testRule } from './utils.mjs';
import invalidDeprecations from '../invalid-deprecations.mjs';

const TYPE = 'Type: [...]';
const CHANGES = dedent`
<!-- YAML
changes:
-->
`;
const BODY = TYPE + '\n' + CHANGES;

const testCases = [
{
name: 'in order deprecations',
input: dedent`
## DEP0001:
${BODY}
## DEP0002:
${BODY}
`,
expected: [],
},
{
name: 'out of order deprecations',
input: dedent`
## DEP0001:
${BODY}
## DEP0003:
${BODY}
`,
expected: [
'Deprecation codes are out of order. Expected DEP0002, saw "DEP0003"',
],
},
{
name: 'skipped deprecations',
input: dedent`
## DEP0001:
${BODY}
<!-- md-lint skip-deprecation DEP0002 -->
## DEP0003:
${BODY}
`,
expected: [],
},
{
name: 'out of order skipped deprecations',
input: dedent`
## DEP0001:
${BODY}
<!-- md-lint skip-deprecation DEP0004 -->
## DEP0003:
${BODY}
`,
expected: [
'Deprecation codes are out of order. Expected DEP0002, saw "DEP0004"',
],
},
{
name: 'not enough skipped deprecations',
input: dedent`
## DEP0001:
${BODY}
<!-- md-lint skip-deprecation DEP0002 -->
## DEP0004:
${BODY}
`,
expected: [
'Deprecation codes are out of order. Expected DEP0003, saw "DEP0004"',
],
},
{
name: 'no type',
input: dedent`
## DEP0001:
some text
${CHANGES}
`,
expected: ['Deprecation "DEP0001" is missing a "Type"'],
},
{
name: 'no changes',
input: dedent`
## DEP0001:
${TYPE}
`,
expected: ['Deprecation "DEP0001" is missing changes'],
},
];

describe('invalid-deprecations', () => {
for (const { name, input, expected, options } of testCases) {
it(name, () =>
testRule(
invalidDeprecations,
input,
expected,
{ stem: 'deprecations' },
options
)
);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const testCases = [
input: 'First a {string}, then a \\<number>.',
expected: ['Type reference must be wrapped in "{}"; saw "<number>"'],
},
{
name: 'miswrapped reference inside link',
input: '[<number>]()',
expected: ['Type reference must be wrapped in "{}"; saw "<number>"'],
},
{
name: 'multiple references',
input: 'Psst, are you a {string | boolean}',
Expand Down
65 changes: 65 additions & 0 deletions packages/remark-lint/src/rules/invalid-deprecations.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { lintRule } from 'unified-lint-rule';
import { visit } from 'unist-util-visit';

const DEPRECATION_COMMENT = /^<!-- md-lint skip-deprecation (DEP\d{4}) -->$/;
const DEPRECATION_HEADING = /^(DEP\d{4}):/;
const DEPRECATION_YAML = /^<!-- YAML\r?\nchanges:\r?\n/;

const generateDeprecation = code => `DEP${code.toString().padStart(4, '0')}`;

/**
* Ensures all needed metadata exists
* @type {import('unified-lint-rule').Rule}
*/
const invalidDeprecations = (tree, vfile) => {
if (vfile.stem !== 'deprecations') {
// Skip non-deprecation files
return;
}

let expectedDeprecationCode = 1;

visit(tree, ['heading', 'html'], (node, idx, parent) => {
let currentDeprecationCode;

// Get the current deprecation
if (node.type === 'html') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what it really does with? currentDeprecationCode = node.value.match(DEPRECATION_COMMENT)?.[1]; it seems like that's supposed to be when we skip a deprecation note?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This matches when we skip a deprecation code, and ensures that the code we skip is the next code in line (1, 2, etc).

currentDeprecationCode = node.value.match(DEPRECATION_COMMENT)?.[1];
} else {
currentDeprecationCode =
node.children[0].value.match(DEPRECATION_HEADING)?.[1];

if (!parent.children[idx + 1]?.children[0]?.value?.startsWith('Type:')) {
vfile.message(
`Deprecation "${currentDeprecationCode}" is missing a "Type"`,
node
);
}

if (!DEPRECATION_YAML.test(parent.children[idx + 2]?.value)) {
vfile.message(
`Deprecation "${currentDeprecationCode}" is missing changes`,
node
);
}
}

// If not found, skip
if (!currentDeprecationCode) {
return;
}

if (
currentDeprecationCode !== generateDeprecation(expectedDeprecationCode)
) {
vfile.message(
`Deprecation codes are out of order. Expected ${generateDeprecation(expectedDeprecationCode)}, saw "${currentDeprecationCode}"`,
node
);
}

expectedDeprecationCode++;
});
};

export default lintRule('node-core:invalid-deprecations', invalidDeprecations);
70 changes: 38 additions & 32 deletions packages/remark-lint/src/rules/invalid-type-reference.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { transformTypeToReferenceLink } from '@node-core/doc-kit/src/utils/parser/index.mjs';
import createQueries from '@node-core/doc-kit/src/utils/queries/index.mjs';
import { transformTypeToReferenceLink } from '@node-core/doc-kit/src/generators/metadata/utils/transformers.mjs';
import { QUERIES } from '@node-core/doc-kit/src/utils/queries/index.mjs';
import { lintRule } from 'unified-lint-rule';
import { visit } from 'unist-util-visit';

Expand All @@ -11,36 +11,42 @@ const REPLACE_RE = /\s*\| */g;
* @type {import('unified-lint-rule').Rule<, import('../api.mjs').Options>}
*/
const invalidTypeReference = (tree, vfile, { typeMap = {} }) => {
visit(tree, createQueries.UNIST.isTextWithType, node => {
const types = node.value.match(createQueries.QUERIES.normalizeTypes);

types.forEach(type => {
// Ensure wrapped in {}
if (type[0] !== '{' || type[type.length - 1] !== '}') {
vfile.message(
`Type reference must be wrapped in "{}"; saw "${type}"`,
node
);

node.value = node.value.replace(type, `{${type.slice(1, -1)}}`);
}

// Fix spaces around |
if (MATCH_RE.test(type)) {
vfile.message(
`Type reference should be separated by "|", without spaces; saw "${type}"`,
node
);

const normalized = type.replace(REPLACE_RE, '|');
node.value = node.value.replace(type, normalized);
}

if (transformTypeToReferenceLink(type, typeMap) === type) {
vfile.message(`Invalid type reference: ${type}`, node);
}
});
});
visit(
tree,
({ value }) => QUERIES.normalizeTypes.test(value),
node => {
const types = node.value.match(QUERIES.normalizeTypes);

types.forEach(type => {
// Ensure wrapped in {}
if (type[0] !== '{' || type[type.length - 1] !== '}') {
vfile.message(
`Type reference must be wrapped in "{}"; saw "${type}"`,
node
);

const newType = `{${type.slice(1, -1)}}`;
node.value = node.value.replace(type, newType);
type = newType;
}

// Fix spaces around |
if (MATCH_RE.test(type)) {
vfile.message(
`Type reference should be separated by "|", without spaces; saw "${type}"`,
node
);

const normalized = type.replace(REPLACE_RE, '|');
node.value = node.value.replace(type, normalized);
}

if (transformTypeToReferenceLink(type, typeMap) === type) {
vfile.message(`Invalid type reference: ${type}`, node);
}
});
}
);
};

export default lintRule(
Expand Down
Loading
Loading