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
3 changes: 2 additions & 1 deletion 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.3.0",
"version": "1.4.0",
"exports": {
".": "./src/index.mjs",
"./api": "./src/api.mjs"
Expand Down Expand Up @@ -50,6 +50,7 @@
"remark-lint-table-pipes": "^5.0.1",
"remark-lint-unordered-list-marker-style": "^4.0.1",
"remark-preset-lint-recommended": "^7.0.1",
"remark-validate-links": "^13.1.0",
"semver": "^7.8.1",
"unified-lint-rule": "^3.0.1",
"unist-util-visit": "^5.1.0",
Expand Down
40 changes: 40 additions & 0 deletions packages/remark-lint/src/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import remarkParse from 'remark-parse';
import remarkValidateLinks from 'remark-validate-links';
import { unified } from 'unified';

import basePreset from '../index.mjs';

describe('base preset', () => {
it('reports broken local links', async () => {
const linkValidator = basePreset.plugins.find(
plugin => plugin === remarkValidateLinks
);

assert.equal(linkValidator, remarkValidateLinks);

const processor = unified()
.use(remarkParse)
.use(linkValidator, { repository: false, root: process.cwd() });
const tree = processor.parse('# Existing heading\n\n[Missing](#missing)');
const file = await new Promise((resolve, reject) => {
processor.run(tree, { path: 'docs/example.md' }, (error, _, vfile) => {
if (error) {
reject(error);
} else {
resolve(vfile);
}
});
});

assert.ok(
file.messages.some(
message =>
message.source.startsWith('remark-validate-links') &&
message.ruleId === 'missing-heading'
)
);
});
});
2 changes: 2 additions & 0 deletions packages/remark-lint/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import remarkLintStrongMarker from 'remark-lint-strong-marker';
import remarkLintTableCellPadding from 'remark-lint-table-cell-padding';
import remarkLintTablePipes from 'remark-lint-table-pipes';
import remarkPresetLintRecommended from 'remark-preset-lint-recommended';
import remarkValidateLinks from 'remark-validate-links';

export default {
settings: {
Expand Down Expand Up @@ -54,6 +55,7 @@ export default {
remarkLintNofileNameOuterDashes,

// Heading and link rules
remarkValidateLinks,
Comment thread
avivkeller marked this conversation as resolved.
remarkLintFinalDefinition,
[remarkLintNoUnusedDefinitions, false],
[remarkLintNoLiteralURLs, false],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { testRule } from './utils.mjs';
Expand All @@ -24,6 +25,19 @@ const testCases = [
input: '[<number>]()',
expected: ['Type reference must be wrapped in "{}"; saw "<number>"'],
},
{
name: 'ignores references in non-prose nodes',
input: `<!-- {invalid} -->

\`{invalid}\`

\`\`\`js
const value = {invalid};
\`\`\`

<div>{invalid}</div>`,
expected: [],
},
{
name: 'multiple references',
input: 'Psst, are you a {string | boolean}',
Expand Down Expand Up @@ -54,9 +68,13 @@ const testCases = [
];

describe('invalid-type-reference', () => {
for (const { name, input, expected, options } of testCases) {
it(name, () =>
testRule(invalidTypeReference, input, expected, {}, options)
);
for (const { name, input, expected, options, replacement } of testCases) {
it(name, () => {
const tree = testRule(invalidTypeReference, input, expected, {}, options);

if (replacement) {
assert.equal(tree.children[0].children[0].value, replacement);
}
});
}
});
2 changes: 2 additions & 0 deletions packages/remark-lint/src/rules/__tests__/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const testRule = (
vfile.message.mock.calls.map(call => call.arguments[0]),
expected
);

return tree;
};

/**
Expand Down
80 changes: 44 additions & 36 deletions packages/remark-lint/src/rules/invalid-type-reference.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,55 @@ import { visit } from 'unist-util-visit';
const MATCH_RE = /\s\||\| /g;
const REPLACE_RE = /\s*\| */g;

const isTypeNode = node => {
if (node.type === 'text') {
return QUERIES.normalizeTypes.test(node.value);
}

if (node.type === 'html') {
return node.value.match(QUERIES.normalizeTypes)?.includes(node.value);
}

return false;
};

/**
* Ensures that all type references are valid
* @type {import('unified-lint-rule').Rule<, import('../api.mjs').Options>}
*/
const invalidTypeReference = (tree, vfile, { typeMap = {} }) => {
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);
}
});
}
);
visit(tree, isTypeNode, 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
41 changes: 41 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading