Skip to content

Commit 1d5d0ad

Browse files
committed
feat(remark-lint): add
1 parent 0a9ead8 commit 1d5d0ad

24 files changed

+1167
-140
lines changed

apps/site/.remarkrc.json

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,3 @@
11
{
2-
"settings": {
3-
"bullet": "-",
4-
"resourceLink": true
5-
},
6-
"plugins": [
7-
"remark-frontmatter",
8-
"remark-preset-lint-node",
9-
["remark-gfm", false],
10-
["remark-lint-fenced-code-flag", false],
11-
["remark-lint-first-heading-level", false],
12-
["remark-lint-maximum-line-length", false],
13-
["remark-lint-no-file-name-articles", false],
14-
["remark-lint-no-literal-urls", false],
15-
["remark-lint-no-unused-definitions", false],
16-
["remark-lint-no-undefined-references", false],
17-
["remark-lint-prohibited-strings", false],
18-
["remark-lint-unordered-list-marker-style", "-"],
19-
["remark-preset-lint-node/remark-lint-nodejs-links.js", false]
20-
]
2+
"plugins": ["remark-frontmatter", "@node-core/remark-lint"]
213
}

apps/site/package.json

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"@eslint/eslintrc": "~3.3.1",
8080
"@flarelabs-net/wrangler-build-time-fs-assets-polyfilling": "^0.0.1",
8181
"@next/eslint-plugin-next": "15.4.4",
82+
"@node-core/remark-lint": "workspace:*",
8283
"@opennextjs/cloudflare": "^1.6.0",
8384
"@playwright/test": "^1.54.1",
8485
"@testing-library/user-event": "~14.6.1",
@@ -92,17 +93,7 @@
9293
"global-jsdom": "^26.0.0",
9394
"handlebars": "4.7.8",
9495
"jsdom": "^26.0.0",
95-
"remark-frontmatter": "5.0.0",
96-
"remark-lint-fenced-code-flag": "^4.2.0",
97-
"remark-lint-first-heading-level": "^4.0.1",
98-
"remark-lint-maximum-line-length": "^4.1.1",
99-
"remark-lint-no-file-name-articles": "^3.0.1",
100-
"remark-lint-no-literal-urls": "^4.0.1",
101-
"remark-lint-no-undefined-references": "^5.0.2",
102-
"remark-lint-no-unused-definitions": "^4.0.2",
103-
"remark-lint-prohibited-strings": "^4.0.0",
104-
"remark-lint-unordered-list-marker-style": "^4.0.1",
105-
"remark-preset-lint-node": "5.1.2",
96+
"remark-frontmatter": "^5.0.0",
10697
"stylelint": "16.22.0",
10798
"stylelint-config-standard": "38.0.0",
10899
"stylelint-order": "7.0.0",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"**/*.{js,mjs,ts,tsx,md,mdx}": ["prettier --check --write", "eslint --fix"],
3+
"**/*.{json,yml}": ["prettier --check --write"]
4+
}

packages/remark-lint/eslint.config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import globals from 'globals';
2+
3+
import baseConfig from '../../eslint.config.js';
4+
5+
export default [
6+
...baseConfig,
7+
{
8+
languageOptions: {
9+
globals: globals.nodeBuiltin,
10+
parserOptions: {
11+
// Allow nullish syntax (i.e. "?." or "??"),
12+
// and top-level await
13+
ecmaVersion: 'latest',
14+
},
15+
},
16+
},
17+
];

packages/remark-lint/package.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "@node-core/remark-lint",
3+
"type": "module",
4+
"exports": {
5+
".": "./src/index.mjs",
6+
"./api": "./src/api.mjs"
7+
},
8+
"scripts": {
9+
"lint": "turbo run lint:js",
10+
"lint:js": "eslint \"**/*.mjs\"",
11+
"lint:fix": "node --run lint -- -- --fix",
12+
"test": "node --run test:unit",
13+
"test:unit": "cross-env NODE_NO_WARNINGS=1 node --experimental-test-coverage --test \"**/*.test.mjs\""
14+
},
15+
"dependencies": {
16+
"remark-gfm": "~4.0.1",
17+
"remark-lint-blockquote-indentation": "^4.0.1",
18+
"remark-lint-checkbox-character-style": "^5.0.1",
19+
"remark-lint-checkbox-content-indent": "^5.0.1",
20+
"remark-lint-code-block-style": "^4.0.1",
21+
"remark-lint-definition-spacing": "^4.0.1",
22+
"remark-lint-fenced-code-flag": "^4.2.0",
23+
"remark-lint-fenced-code-marker": "^4.0.1",
24+
"remark-lint-file-extension": "^3.0.1",
25+
"remark-lint-final-definition": "^4.0.2",
26+
"remark-lint-heading-style": "^4.0.1",
27+
"remark-lint-maximum-line-length": "^4.1.1",
28+
"remark-lint-no-consecutive-blank-lines": "^5.0.1",
29+
"remark-lint-no-file-name-consecutive-dashes": "^3.0.1",
30+
"remark-lint-no-file-name-outer-dashes": "^3.0.1",
31+
"remark-lint-no-heading-indent": "^5.0.1",
32+
"remark-lint-no-literal-urls": "^4.0.1",
33+
"remark-lint-no-multiple-toplevel-headings": "^4.0.1",
34+
"remark-lint-no-shell-dollars": "^4.0.1",
35+
"remark-lint-no-table-indentation": "^5.0.1",
36+
"remark-lint-no-tabs": "^4.0.1",
37+
"remark-lint-no-trailing-spaces": "^3.0.2",
38+
"remark-lint-no-unused-definitions": "^4.0.2",
39+
"remark-lint-prohibited-strings": "^4.0.0",
40+
"remark-lint-rule-style": "^4.0.1",
41+
"remark-lint-strong-marker": "^4.0.1",
42+
"remark-lint-table-cell-padding": "^5.1.1",
43+
"remark-lint-table-pipes": "^5.0.1",
44+
"remark-lint-unordered-list-marker-style": "^4.0.1",
45+
"remark-preset-lint-recommended": "^7.0.1",
46+
"semver": "~7.7.2",
47+
"unified-lint-rule": "^3.0.1",
48+
"unist-util-visit": "^5.0.0",
49+
"yaml": "^2.8.0"
50+
},
51+
"devDependencies": {
52+
"cross-env": "catalog:",
53+
"dedent": "^1.6.0",
54+
"globals": "^16.3.0",
55+
"remark-parse": "^11.0.0",
56+
"unified": "^11.0.5"
57+
}
58+
}

packages/remark-lint/src/api.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import remarkGfm from 'remark-gfm';
2+
import remarkLintFencedCodeFlag from 'remark-lint-fenced-code-flag';
3+
import remarkLintMaximumLineLength from 'remark-lint-maximum-line-length';
4+
import remarkLintNoUnusedDefinitions from 'remark-lint-no-unused-definitions';
5+
import remarkLintProhibitedStrings from 'remark-lint-prohibited-strings';
6+
import remarkLintUnorderedListMarkerStyle from 'remark-lint-unordered-list-marker-style';
7+
8+
import basePreset from './index.mjs';
9+
import duplicateStabilityNodes from './rules/duplicate-stability-nodes.mjs';
10+
import hashedSelfReference from './rules/hashed-self-reference.mjs';
11+
import orderedReferences from './rules/ordered-references.mjs';
12+
import yamlComments from './rules/yaml/index.mjs';
13+
14+
export default {
15+
settings: {
16+
...basePreset.settings,
17+
bullet: '*',
18+
},
19+
plugins: [
20+
remarkGfm,
21+
...basePreset.plugins,
22+
duplicateStabilityNodes,
23+
yamlComments,
24+
hashedSelfReference,
25+
orderedReferences,
26+
remarkLintNoUnusedDefinitions,
27+
[remarkLintFencedCodeFlag, { allowEmpty: false }],
28+
[remarkLintMaximumLineLength, 120],
29+
[remarkLintUnorderedListMarkerStyle, '*'],
30+
[
31+
remarkLintProhibitedStrings,
32+
[
33+
{ yes: 'End-of-Life' },
34+
{ no: 'filesystem', yes: 'file system' },
35+
{ yes: 'GitHub' },
36+
{ no: 'hostname', yes: 'host name' },
37+
{ yes: 'JavaScript' },
38+
{ no: '[Ll]ong[ -][Tt]erm [Ss]upport', yes: 'Long Term Support' },
39+
{ no: 'Node', yes: 'Node.js', ignoreNextTo: '-API' },
40+
{ yes: 'Node.js' },
41+
{ no: 'Node[Jj][Ss]', yes: 'Node.js' },
42+
{ no: "Node\\.js's?", yes: 'the Node.js' },
43+
{ no: '[Nn]ote that', yes: '<nothing>' },
44+
{ yes: 'RFC' },
45+
{ no: '[Rr][Ff][Cc]\\d+', yes: 'RFC <number>' },
46+
{ yes: 'TypeScript' },
47+
{ yes: 'Unix' },
48+
{ yes: 'Valgrind' },
49+
{ yes: 'V8' },
50+
],
51+
],
52+
],
53+
};

packages/remark-lint/src/index.mjs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import remarkLintBlockquoteIndentation from 'remark-lint-blockquote-indentation';
2+
import remarkLintCheckboxCharacterStyle from 'remark-lint-checkbox-character-style';
3+
import remarkLintCheckboxContentIndent from 'remark-lint-checkbox-content-indent';
4+
import remarkLintCodeBlockStyle from 'remark-lint-code-block-style';
5+
import remarkLintDefinitionSpacing from 'remark-lint-definition-spacing';
6+
import remarkLintFencedCodeMarker from 'remark-lint-fenced-code-marker';
7+
import remarkLintFileExtension from 'remark-lint-file-extension';
8+
import remarkLintFinalDefinition from 'remark-lint-final-definition';
9+
import remarkLintHeadingStyle from 'remark-lint-heading-style';
10+
import remarkLintNoConsecutiveBlankLines from 'remark-lint-no-consecutive-blank-lines';
11+
import remarkLintNoFileNameConsecutiveDashes from 'remark-lint-no-file-name-consecutive-dashes';
12+
import remarkLintNofileNameOuterDashes from 'remark-lint-no-file-name-outer-dashes';
13+
import remarkLintNoHeadingIndent from 'remark-lint-no-heading-indent';
14+
import remarkLintNoLiteralURLs from 'remark-lint-no-literal-urls';
15+
import remarkLintNoMultipleToplevelHeadings from 'remark-lint-no-multiple-toplevel-headings';
16+
import remarkLintNoShellDollars from 'remark-lint-no-shell-dollars';
17+
import remarkLintNoTableIndentation from 'remark-lint-no-table-indentation';
18+
import remarkLintNoTabs from 'remark-lint-no-tabs';
19+
import remarkLintNoTrailingSpaces from 'remark-lint-no-trailing-spaces';
20+
import remarkLintNoUnusedDefinitions from 'remark-lint-no-unused-definitions';
21+
import remarkLintRuleStyle from 'remark-lint-rule-style';
22+
import remarkLintStrongMarker from 'remark-lint-strong-marker';
23+
import remarkLintTableCellPadding from 'remark-lint-table-cell-padding';
24+
import remarkLintTablePipes from 'remark-lint-table-pipes';
25+
import remarkPresetLintRecommended from 'remark-preset-lint-recommended';
26+
27+
export default {
28+
settings: {
29+
tightDefinitions: true,
30+
emphasis: '_',
31+
bullet: '-',
32+
rule: '-',
33+
},
34+
plugins: [
35+
// Base plugins
36+
remarkPresetLintRecommended,
37+
38+
// Blockquote and list rules
39+
[remarkLintBlockquoteIndentation, 2],
40+
[remarkLintCheckboxCharacterStyle, { checked: 'x', unchecked: ' ' }],
41+
remarkLintCheckboxContentIndent,
42+
43+
// Code and formatting rules
44+
[remarkLintCodeBlockStyle, 'fenced'],
45+
[remarkLintFencedCodeMarker, '`'],
46+
[remarkLintRuleStyle, '---'],
47+
[remarkLintStrongMarker, '*'],
48+
49+
// File and filename rules
50+
[remarkLintFileExtension, 'md'],
51+
remarkLintNoFileNameConsecutiveDashes,
52+
remarkLintNofileNameOuterDashes,
53+
54+
// Heading and link rules
55+
remarkLintFinalDefinition,
56+
[remarkLintNoUnusedDefinitions, false],
57+
[remarkLintNoLiteralURLs, false],
58+
[remarkLintHeadingStyle, 'atx'],
59+
remarkLintNoHeadingIndent,
60+
remarkLintNoMultipleToplevelHeadings,
61+
62+
// Layout and spacing rules
63+
remarkLintDefinitionSpacing,
64+
remarkLintNoConsecutiveBlankLines,
65+
remarkLintNoTableIndentation,
66+
remarkLintNoTabs,
67+
remarkLintNoTrailingSpaces,
68+
[remarkLintTableCellPadding, 'padded'],
69+
remarkLintTablePipes,
70+
71+
// Shell and console rules
72+
remarkLintNoShellDollars,
73+
],
74+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, it } from 'node:test';
2+
3+
import dedent from 'dedent';
4+
5+
import duplicateStabilityNodes from '../duplicate-stability-nodes.mjs';
6+
import { testRule } from './utils.mjs';
7+
8+
const testCases = [
9+
{
10+
name: 'no stability nodes',
11+
input: dedent`
12+
# Heading 1
13+
14+
> No stability here.
15+
`,
16+
expected: [],
17+
},
18+
{
19+
name: 'single stability blockquote',
20+
input: dedent`
21+
# Heading 1
22+
23+
> Stability: 1.0
24+
`,
25+
expected: [],
26+
},
27+
{
28+
name: 'different stabilities under different headings',
29+
input: dedent`
30+
# Heading 1
31+
32+
> Stability: 1.0
33+
34+
## Heading 2
35+
36+
> Stability: 2.0
37+
`,
38+
expected: [],
39+
},
40+
{
41+
name: 'duplicate stability at deeper heading triggers message',
42+
input: dedent`
43+
# Heading 1
44+
45+
> Stability: 1.0
46+
47+
## Heading 2
48+
49+
> Stability: 1.0
50+
`,
51+
expected: ['Duplicate stability node'],
52+
},
53+
];
54+
55+
describe('duplicate-stability-nodes', () => {
56+
for (const { name, input, expected } of testCases) {
57+
it(name, () => testRule(duplicateStabilityNodes, input, expected));
58+
}
59+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { describe, it } from 'node:test';
2+
3+
import hashedSelfReference from '../hashed-self-reference.mjs';
4+
import { testRule } from './utils.mjs';
5+
6+
const testCases = [
7+
{
8+
name: 'ignores links to other paths',
9+
input: '[external](./other.md)',
10+
path: 'docs/current.md',
11+
expected: [],
12+
},
13+
{
14+
name: 'accepts hashed self-reference',
15+
input: '[header](#my-heading)',
16+
path: 'docs/current.md',
17+
expected: [],
18+
},
19+
{
20+
name: 'reports self-reference with fragment',
21+
input: '[bad link](./current.md#section)',
22+
path: 'docs/current.md',
23+
expected: [
24+
'Self-reference must start with hash (expected "#section", got "./current.md#section")',
25+
],
26+
},
27+
{
28+
name: 'reports self-reference to path only',
29+
input: '[bad link](./current.md)',
30+
path: 'docs/current.md',
31+
expected: [
32+
'Self-reference must start with hash (expected "#", got "./current.md")',
33+
],
34+
},
35+
{
36+
name: 'ignores external links',
37+
input: '[nodejs](https://nodejs.org)',
38+
path: 'docs/current.md',
39+
expected: [],
40+
},
41+
];
42+
43+
describe('hashed-self-references', () => {
44+
for (const { name, input, expected, ...options } of testCases) {
45+
it(name, () =>
46+
testRule(hashedSelfReference, input, expected, {
47+
...options,
48+
cwd: process.cwd(),
49+
})
50+
);
51+
}
52+
});

0 commit comments

Comments
 (0)