Refactor code style in scripts

This commit is contained in:
Titus Wormer 2023-11-11 17:47:26 +01:00
parent db4810d891
commit 0e79b65cc7
No known key found for this signature in database
GPG Key ID: E6E581152ED04E2E
89 changed files with 3128 additions and 3384 deletions

View File

@ -58,9 +58,14 @@ touch .remarkrc.js
```js
// .remarkrc.js
module.exports = {
plugins: []
}
/**
* @typedef {import('unified').Preset} Preset
*/
/** @type {Preset} */
const preset = {plugins: []}
export default preset
```
Then, in our `package.json`, add the following script to process all the
@ -142,7 +147,7 @@ import {lintRule} from 'unified-lint-rule'
const remarkLintNoGifAllowed = lintRule(
'remark-lint:no-gif-allowed',
(tree, file, options) => {
function (tree, file, options) {
// Rule implementation
}
)
@ -155,9 +160,16 @@ namespace.
If your project was named `my-project`, then you can export your rule as:
```js
const remarkLintNoGifAllowed = lintRule('my-project-name:no-gif-allowed', () => {})
const remarkLintNoGifAllowed = lintRule(
'my-project-name:no-gif-allowed',
function () {}
)
// Or:
const remarkLintNoGifAllowed = lintRule('my-npm-published-package:no-gif-allowed', () => {})
const remarkLintNoGifAllowed = lintRule(
'my-npm-published-package:no-gif-allowed',
function () {}
)
```
This can help you when wanting to create a group of rules under the same
@ -168,7 +180,7 @@ This can help you when wanting to create a group of rules under the same
Your rule function will receive three arguments:
```js
(tree, file, options) => {}
function rule(tree, file, options) {}
```
* `tree` (*required*): [mdast][]
@ -187,38 +199,36 @@ recursively inspect all the image nodes, and
nodes that we have generated ourselves and do not belong to the `doc.md`.
```js
import {lintRule} from 'unified-lint-rule'
import {visit} from 'unist-visit-util'
import {generated} from 'unist-util-generated'
/**
* @typedef {import('mdast').Root} Root
*/
function isValidNode(node) {
// Here we check whether the given node violates our rule.
// Implementation details are not relevant to the scope of this example.
// This is an overly simplified solution for demonstration purposes
if (node.url && typeof node.url === 'string') {
return !node.url.endsWith('.gif')
}
}
import {lintRule} from 'unified-lint-rule'
import {visit} from 'unist-util-visit'
const remarkLintNoGifAllowed = lintRule(
'remark-lint:no-gif-allowed',
(tree, file, options) => {
visit(tree, 'image', (node) => {
if (!generated(node)) {
// This is an extremely simplified example of how to structure
// the logic to check whether a node violates your rule.
// You have complete freedom over how to visit/inspect the tree,
// and on how to implement the validation logic for your node.
const isValid = isValidNode(node)
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
function (tree, file, options) {
visit(tree, 'image', function (node) {
// This is an extremely simplified example of how to structure
// the logic to check whether a node violates your rule.
// You have complete freedom over how to visit/inspect the tree,
// and on how to implement the validation logic for your node.
const isValid = !node.url.endsWith('.gif')
if (!isValid) {
// Remember to provide the node as second argument to the message,
// in order to obtain the position and column where the violation occurred.
file.message(
'Invalid image file extensions. Please do not use gifs',
node
)
}
if (!isValid) {
// Remember to provide the node as second argument to the message,
// in order to obtain the position and column where the violation occurred.
file.message(
'Invalid image file extensions. Please do not use gifs',
node
)
}
})
}
@ -236,13 +246,14 @@ You can do that by importing your rule and adding it in `plugins` array:
```js
// .remarkrc.js
/**
* @typedef {import('unified').Preset} Preset
*/
import remarkLintNoGifAllowed from './rules/no-gif-allowed.js'
const plugins = {
plugins: [remarkLintNoGifAllowed]
}
const preset = {plugins}
/** @type {Preset} */
const preset = {plugins: [remarkLintNoGifAllowed]}
export default preset
```

View File

@ -116,7 +116,11 @@
"c8": "^8.0.0",
"comment-parser": "^1.0.0",
"github-slugger": "^2.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-gfm": "^3.0.0",
"mdast-util-to-markdown": "^2.0.0",
"mdast-zone": "^6.0.0",
"micromark-extension-gfm": "^3.0.0",
"parse-author": "^2.0.0",
"prettier": "^3.0.0",
"remark": "^15.0.0",
@ -127,16 +131,16 @@
"remark-toc": "^9.0.0",
"remark-validate-links": "^13.0.0",
"strip-indent": "^4.0.0",
"to-vfile": "^8.0.0",
"type-coverage": "^2.0.0",
"type-fest": "^4.0.0",
"typescript": "^5.0.0",
"unist-builder": "^4.0.0",
"unist-util-remove-position": "^5.0.0",
"vfile": "^6.0.0",
"xo": "^0.56.0"
},
"scripts": {
"generate": "node --conditions development script/build-presets.js && node --conditions development script/build-rules.js",
"generate": "node --conditions development script/build-plugins.js && node --conditions development script/build-presets.js",
"build": "tsc --build --clean && tsc --build && type-coverage",
"format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix",
"test": "npm run build && npm run generate && npm run format && npm run test-coverage",
@ -174,8 +178,8 @@
"remark-lint-list-item-indent",
"space"
],
"./script/plugin/list-of-presets.js",
"./script/plugin/list-of-rules.js"
"./script/plugin/list-of-plugins.js",
"./script/plugin/list-of-presets.js"
]
},
"typeCoverage": {
@ -193,7 +197,8 @@
],
"rules": {
"max-depth": "off",
"no-await-in-loop": "off"
"no-await-in-loop": "off",
"unicorn/no-array-callback-reference": "off"
}
}
],

View File

@ -77,22 +77,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintBlockquoteIndentation from 'remark-lint-blockquote-indentation'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintBlockquoteIndentation)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintBlockquoteIndentation)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintCheckboxCharacterStyle from 'remark-lint-checkbox-character-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintCheckboxCharacterStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintCheckboxCharacterStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -72,22 +72,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintCheckboxContentIndent from 'remark-lint-checkbox-content-indent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintCheckboxContentIndent)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintCheckboxContentIndent)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintCodeBlockStyle from 'remark-lint-code-block-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintCodeBlockStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintCodeBlockStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintDefinitionCase from 'remark-lint-definition-case'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintDefinitionCase)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintDefinitionCase)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintDefinitionSpacing from 'remark-lint-definition-spacing'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintDefinitionSpacing)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintDefinitionSpacing)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintEmphasisMarker from 'remark-lint-emphasis-marker'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintEmphasisMarker)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintEmphasisMarker)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintFencedCodeFlag from 'remark-lint-fenced-code-flag'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintFencedCodeFlag)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintFencedCodeFlag)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintFencedCodeMarker from 'remark-lint-fenced-code-marker'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintFencedCodeMarker)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintFencedCodeMarker)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -162,46 +160,6 @@ Indented code blocks are not affected by this rule:
No messages.
##### `not-ok-consistent-tick.md`
###### In
````markdown
```alpha
bravo()
```
~~~
charlie()
~~~
````
###### Out
```text
5:1-7:4: Fenced code should use `` ` `` as a marker
```
##### `not-ok-consistent-tilde.md`
###### In
````markdown
~~~alpha
bravo()
~~~
```
charlie()
```
````
###### Out
```text
5:1-7:4: Fenced code should use `~` as a marker
```
##### `ok.md`
When configured with ``'`'``.
@ -242,6 +200,46 @@ charlie()
No messages.
##### `not-ok-consistent-tick.md`
###### In
````markdown
```alpha
bravo()
```
~~~
charlie()
~~~
````
###### Out
```text
5:1-7:4: Fenced code should use `` ` `` as a marker
```
##### `not-ok-consistent-tilde.md`
###### In
````markdown
~~~alpha
bravo()
~~~
```
charlie()
```
````
###### Out
```text
5:1-7:4: Fenced code should use `~` as a marker
```
##### `not-ok-incorrect.md`
When configured with `'💩'`.

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintFileExtension from 'remark-lint-file-extension'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintFileExtension)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintFileExtension)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintFinalDefinition from 'remark-lint-final-definition'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintFinalDefinition)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintFinalDefinition)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintFinalNewline from 'remark-lint-final-newline'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintFinalNewline)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintFinalNewline)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -69,22 +69,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintFirstHeadingLevel from 'remark-lint-first-heading-level'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintFirstHeadingLevel)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintFirstHeadingLevel)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintHardBreakSpaces from 'remark-lint-hard-break-spaces'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintHardBreakSpaces)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintHardBreakSpaces)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintHeadingIncrement from 'remark-lint-heading-increment'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintHeadingIncrement)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintHeadingIncrement)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintHeadingStyle from 'remark-lint-heading-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintHeadingStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintHeadingStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -70,22 +70,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintLinebreakStyle from 'remark-lint-linebreak-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintLinebreakStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintLinebreakStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintLinkTitleStyle from 'remark-lint-link-title-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintLinkTitleStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintLinkTitleStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintListItemBulletIndent from 'remark-lint-list-item-bullet-indent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintListItemBulletIndent)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintListItemBulletIndent)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintListItemContentIndent from 'remark-lint-list-item-content-indent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintListItemContentIndent)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintListItemContentIndent)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -77,22 +77,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintListItemIndent from 'remark-lint-list-item-indent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintListItemIndent)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintListItemIndent)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -230,24 +228,6 @@ Paragraph.
No messages.
##### `not-ok.md`
When configured with `'mixed'`.
###### In
> 👉 **Note**: `·` represents a space.
```markdown
*···List item.
```
###### Out
```text
1:5: Incorrect list-item indent: remove 2 spaces
```
##### `ok.md`
When configured with `'space'`.
@ -316,6 +296,24 @@ When configured with `'tab-size'`.
##### `not-ok.md`
When configured with `'mixed'`.
###### In
> 👉 **Note**: `·` represents a space.
```markdown
*···List item.
```
###### Out
```text
1:5: Incorrect list-item indent: remove 2 spaces
```
##### `not-ok.md`
When configured with `'💩'`.
###### Out

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintListItemSpacing from 'remark-lint-list-item-spacing'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintListItemSpacing)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintListItemSpacing)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintMaximumHeadingLength from 'remark-lint-maximum-heading-length'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintMaximumHeadingLength)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintMaximumHeadingLength)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintMaximumLineLength from 'remark-lint-maximum-line-length'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintMaximumLineLength)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintMaximumLineLength)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoBlockquoteWithoutMarker from 'remark-lint-no-blockquote-without-marker'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoBlockquoteWithoutMarker)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoBlockquoteWithoutMarker)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoConsecutiveBlankLines from 'remark-lint-no-consecutive-blank-lines'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoConsecutiveBlankLines)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoConsecutiveBlankLines)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -69,22 +69,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoDuplicateDefinedUrls from 'remark-lint-no-duplicate-defined-urls'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateDefinedUrls)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateDefinedUrls)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoDuplicateDefinitions from 'remark-lint-no-duplicate-definitions'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateDefinitions)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateDefinitions)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -71,22 +71,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoDuplicateHeadingsInSection from 'remark-lint-no-duplicate-headings-in-section'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateHeadingsInSection)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateHeadingsInSection)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoDuplicateHeadings from 'remark-lint-no-duplicate-headings'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateHeadings)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoDuplicateHeadings)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoEmphasisAsHeading from 'remark-lint-no-emphasis-as-heading'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoEmphasisAsHeading)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoEmphasisAsHeading)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -69,22 +69,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoEmptyUrl from 'remark-lint-no-empty-url'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoEmptyUrl)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoEmptyUrl)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoFileNameArticles from 'remark-lint-no-file-name-articles'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoFileNameArticles)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoFileNameArticles)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoFileNameConsecutiveDashes from 'remark-lint-no-file-name-consecutive-dashes'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoFileNameConsecutiveDashes)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoFileNameConsecutiveDashes)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -72,22 +72,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoFileNameIrregularCharacters from 'remark-lint-no-file-name-irregular-characters'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoFileNameIrregularCharacters)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoFileNameIrregularCharacters)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -151,14 +149,6 @@ No messages.
1:1: Do not use `_` in a file name
```
##### `plug ins.md`
###### Out
```text
1:1: Do not use ` ` in a file name
```
##### `README.md`
When configured with `'\\.a-z0-9'`.
@ -169,6 +159,14 @@ When configured with `'\\.a-z0-9'`.
1:1: Do not use `R` in a file name
```
##### `plug ins.md`
###### Out
```text
1:1: Do not use ` ` in a file name
```
## Compatibility
Projects maintained by the unified collective are compatible with all maintained

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoFileNameMixedCase from 'remark-lint-no-file-name-mixed-case'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoFileNameMixedCase)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoFileNameMixedCase)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoFileNameOuterDashes from 'remark-lint-no-file-name-outer-dashes'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoFileNameOuterDashes)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoFileNameOuterDashes)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoHeadingContentIndent from 'remark-lint-no-heading-content-indent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoHeadingContentIndent)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoHeadingContentIndent)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -70,22 +70,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoHeadingIndent from 'remark-lint-no-heading-indent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoHeadingIndent)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoHeadingIndent)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -70,22 +70,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoHeadingLikeParagraph from 'remark-lint-no-heading-like-paragraph'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoHeadingLikeParagraph)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoHeadingLikeParagraph)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -72,22 +72,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoHeadingPunctuation from 'remark-lint-no-heading-punctuation'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoHeadingPunctuation)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoHeadingPunctuation)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -143,6 +141,20 @@ The following options (default: `'\\.,;:!?'`) are accepted:
No messages.
##### `ok.md`
When configured with `',;:!?'`.
###### In
```markdown
# Hello…
```
###### Out
No messages.
##### `not-ok.md`
###### In
@ -169,20 +181,6 @@ No messages.
9:1-9:9: Dont add a trailing `;` to headings
```
##### `ok.md`
When configured with `',;:!?'`.
###### In
```markdown
# Hello…
```
###### Out
No messages.
## Compatibility
Projects maintained by the unified collective are compatible with all maintained

View File

@ -68,22 +68,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoHtml from 'remark-lint-no-html'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoHtml)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoHtml)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoInlinePadding from 'remark-lint-no-inline-padding'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoInlinePadding)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoInlinePadding)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoLiteralUrls from 'remark-lint-no-literal-urls'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoLiteralUrls)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoLiteralUrls)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -70,22 +70,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoMissingBlankLines from 'remark-lint-no-missing-blank-lines'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoMissingBlankLines)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoMissingBlankLines)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoMultipleToplevelHeadings from 'remark-lint-no-multiple-toplevel-headings'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoMultipleToplevelHeadings)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoMultipleToplevelHeadings)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -106,7 +106,6 @@ const remarkLintNoParagraphContentIndent = lintRule(
const value = String(file)
const loc = location(value)
// eslint-disable-next-line complexity
visit(tree, 'paragraph', (node, _, parent) => {
const end = pointEnd(node)?.line
let line = pointStart(node)?.line

View File

@ -53,7 +53,8 @@
"xo": {
"prettier": true,
"rules": {
"capitalized-comments": "off"
"capitalized-comments": "off",
"complexity": "off"
}
}
}

View File

@ -70,22 +70,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoParagraphContentIndent from 'remark-lint-no-paragraph-content-indent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoParagraphContentIndent)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoParagraphContentIndent)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -70,22 +70,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoReferenceLikeUrl from 'remark-lint-no-reference-like-url'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoReferenceLikeUrl)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoReferenceLikeUrl)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoShellDollars from 'remark-lint-no-shell-dollars'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoShellDollars)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoShellDollars)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoShortcutReferenceImage from 'remark-lint-no-shortcut-reference-image'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoShortcutReferenceImage)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoShortcutReferenceImage)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoShortcutReferenceLink from 'remark-lint-no-shortcut-reference-link'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoShortcutReferenceLink)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoShortcutReferenceLink)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoTableIndentation from 'remark-lint-no-table-indentation'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoTableIndentation)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoTableIndentation)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -70,22 +70,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoTabs from 'remark-lint-no-tabs'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoTabs)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoTabs)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -213,7 +213,6 @@ const remarkLintNoUndefinedReferences = lintRule(
/** @type {Array<Range>} */
let ranges = []
// eslint-disable-next-line complexity
visit(node, (child) => {
// Ignore the node itself.
if (child === node) return
@ -292,12 +291,10 @@ const remarkLintNoUndefinedReferences = lintRule(
let range = ranges.pop()
// Range should always exist.
// eslint-disable-next-line max-depth
if (range) {
range.push(lines[lineIndex][0] + index)
// This is the end of a reference already.
// eslint-disable-next-line max-depth
if (range.length === 4) {
handleRange(range)
range = []
@ -314,7 +311,6 @@ const remarkLintNoUndefinedReferences = lintRule(
const range = ranges.pop()
// Range should always exist.
// eslint-disable-next-line max-depth
if (range) {
range.push(lines[lineIndex][0] + index)
handleRange(range)

View File

@ -55,6 +55,8 @@
"prettier": true,
"rules": {
"capitalized-comments": "off",
"complexity": "off",
"max-depth": "off",
"unicorn/prefer-at": "off",
"unicorn/prefer-code-point": "off",
"unicorn/prefer-string-replace-all": "off",

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoUndefinedReferences from 'remark-lint-no-undefined-references'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoUndefinedReferences)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoUndefinedReferences)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -178,6 +176,38 @@ Just two braces cant link: [].
No messages.
##### `ok-allow.md`
When configured with `{ allow: [ '...', '…' ] }`.
###### In
```markdown
> Eliding a portion of a quoted passage […] is acceptable.
```
###### Out
No messages.
##### `ok-allow.md`
When configured with `{ allow: [ 'a', { source: '^b\\.' } ] }`.
###### In
```markdown
[foo][b.c]
[bar][a]
Matching is case-insensitive: [bar][B.C]
```
###### Out
No messages.
##### `not-ok.md`
###### In
@ -216,38 +246,6 @@ Multiple pairs: [a][b][c].
17:23-17:26: Found reference to undefined definition
```
##### `ok-allow.md`
When configured with `{ allow: [ '...', '…' ] }`.
###### In
```markdown
> Eliding a portion of a quoted passage […] is acceptable.
```
###### Out
No messages.
##### `ok-allow.md`
When configured with `{ allow: [ 'a', { source: '^b\\.' } ] }`.
###### In
```markdown
[foo][b.c]
[bar][a]
Matching is case-insensitive: [bar][B.C]
```
###### Out
No messages.
##### `not-ok.md`
When configured with `{ allow: [ 'a', { source: '^b\\.' } ] }`.

View File

@ -71,22 +71,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoUnneededFullReferenceImage from 'remark-lint-no-unneeded-full-reference-image'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoUnneededFullReferenceImage)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoUnneededFullReferenceImage)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -71,22 +71,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoUnneededFullReferenceLink from 'remark-lint-no-unneeded-full-reference-link'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoUnneededFullReferenceLink)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoUnneededFullReferenceLink)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -73,22 +73,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintNoUnusedDefinitions from 'remark-lint-no-unused-definitions'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoUnusedDefinitions)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintNoUnusedDefinitions)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintOrderedListMarkerStyle from 'remark-lint-ordered-list-marker-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintOrderedListMarkerStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintOrderedListMarkerStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -170,22 +168,6 @@ Unordered lists are not affected by this rule.
No messages.
##### `not-ok.md`
###### In
```markdown
1. Foo
2) Bar
```
###### Out
```text
3:1-3:8: Marker style should be `.`
```
##### `ok.md`
When configured with `'.'`.
@ -220,6 +202,22 @@ No messages.
##### `not-ok.md`
###### In
```markdown
1. Foo
2) Bar
```
###### Out
```text
3:1-3:8: Marker style should be `.`
```
##### `not-ok.md`
When configured with `'💩'`.
###### Out

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintOrderedListMarkerValue from 'remark-lint-ordered-list-marker-value'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintOrderedListMarkerValue)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintOrderedListMarkerValue)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -202,40 +200,6 @@ Paragraph.
No messages.
##### `not-ok.md`
When configured with `'one'`.
###### In
```markdown
1. Foo
2. Bar
```
###### Out
```text
2:1-2:8: Marker should be `1`, was `2`
```
##### `also-not-ok.md`
When configured with `'one'`.
###### In
```markdown
2. Foo
1. Bar
```
###### Out
```text
1:1-1:8: Marker should be `1`, was `2`
```
##### `ok.md`
When configured with `'single'`.
@ -294,6 +258,40 @@ No messages.
##### `not-ok.md`
When configured with `'one'`.
###### In
```markdown
1. Foo
2. Bar
```
###### Out
```text
2:1-2:8: Marker should be `1`, was `2`
```
##### `also-not-ok.md`
When configured with `'one'`.
###### In
```markdown
2. Foo
1. Bar
```
###### Out
```text
1:1-1:8: Marker should be `1`, was `2`
```
##### `not-ok.md`
When configured with `'ordered'`.
###### In

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintRuleStyle from 'remark-lint-rule-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintRuleStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintRuleStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -74,22 +74,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintStrikethroughMarker from 'remark-lint-strikethrough-marker'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintStrikethroughMarker)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintStrikethroughMarker)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintStrongMarker from 'remark-lint-strong-marker'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintStrongMarker)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintStrongMarker)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -178,20 +176,6 @@ __foo__ and __bar__.
No messages.
##### `not-ok.md`
###### In
```markdown
**foo** and __bar__.
```
###### Out
```text
1:13-1:20: Strong should use `*` as a marker
```
##### `ok.md`
When configured with `'*'`.
@ -222,6 +206,20 @@ No messages.
##### `not-ok.md`
###### In
```markdown
**foo** and __bar__.
```
###### Out
```text
1:13-1:20: Strong should use `*` as a marker
```
##### `not-ok.md`
When configured with `'💩'`.
###### Out

View File

@ -77,22 +77,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintTableCellPadding from 'remark-lint-table-cell-padding'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintTableCellPadding)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintTableCellPadding)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -206,51 +204,6 @@ Too much padding isnt good either:
13:32: Cell should be padded with 1 space, not 2
```
##### `empty.md`
When configured with `'padded'`.
###### In
> 👉 **Note**: this example uses GFM ([`remark-gfm`][gfm]).
```markdown
<!-- Empty cells are OK, but those surrounding them may not be. -->
| | Alpha | Bravo|
| ------ | ----- | ---: |
| Charlie| | Echo|
```
###### Out
```text
3:25: Cell should be padded
5:10: Cell should be padded
5:25: Cell should be padded
```
##### `missing-body.md`
When configured with `'padded'`.
###### In
> 👉 **Note**: this example uses GFM ([`remark-gfm`][gfm]).
```markdown
<!-- Missing cells are fine as well. -->
| Alpha | Bravo | Charlie |
| ----- | ------- | ------- |
| Delta |
| Echo | Foxtrot |
```
###### Out
No messages.
##### `ok.md`
When configured with `'compact'`.
@ -397,6 +350,51 @@ When configured with `'💩'`.
1:1: Incorrect table cell padding style `💩`, expected `'padded'`, `'compact'`, or `'consistent'`
```
##### `empty.md`
When configured with `'padded'`.
###### In
> 👉 **Note**: this example uses GFM ([`remark-gfm`][gfm]).
```markdown
<!-- Empty cells are OK, but those surrounding them may not be. -->
| | Alpha | Bravo|
| ------ | ----- | ---: |
| Charlie| | Echo|
```
###### Out
```text
3:25: Cell should be padded
5:10: Cell should be padded
5:25: Cell should be padded
```
##### `missing-body.md`
When configured with `'padded'`.
###### In
> 👉 **Note**: this example uses GFM ([`remark-gfm`][gfm]).
```markdown
<!-- Missing cells are fine as well. -->
| Alpha | Bravo | Charlie |
| ----- | ------- | ------- |
| Delta |
| Echo | Foxtrot |
```
###### Out
No messages.
## Compatibility
Projects maintained by the unified collective are compatible with all maintained

View File

@ -76,22 +76,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintTablePipeAlignment from 'remark-lint-table-pipe-alignment'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintTablePipeAlignment)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintTablePipeAlignment)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -78,22 +78,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintTablePipes from 'remark-lint-table-pipes'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintTablePipes)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintTablePipes)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -75,22 +75,20 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLint from 'remark-lint'
import remarkLintUnorderedListMarkerStyle from 'remark-lint-unordered-list-marker-style'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkLint)
.use(remarkLintUnorderedListMarkerStyle)
.process(await read('example.md'))
await remark()
.use(remarkLint)
.use(remarkLintUnorderedListMarkerStyle)
.process(file)
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:
@ -174,23 +172,6 @@ Ordered lists are not affected.
No messages.
##### `not-ok.md`
###### In
```markdown
* Foo
- Bar
+ Baz
```
###### Out
```text
2:1-2:6: Marker style should be `*`
3:1-3:6: Marker style should be `*`
```
##### `ok.md`
When configured with `'*'`.
@ -235,6 +216,23 @@ No messages.
##### `not-ok.md`
###### In
```markdown
* Foo
- Bar
+ Baz
```
###### Out
```text
2:1-2:6: Marker style should be `*`
3:1-3:6: Marker style should be `*`
```
##### `not-ok.md`
When configured with `'💩'`.
###### Out

View File

@ -82,20 +82,18 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkPresetLintConsistent from 'remark-preset-lint-consistent'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkPresetLintConsistent)
.process(await read('example.md'))
await remark()
.use(remarkPresetLintConsistent)
.process(await read('example.md'))
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -203,20 +203,18 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkPresetLintMarkdownStyleGuide from 'remark-preset-lint-markdown-style-guide'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkPresetLintMarkdownStyleGuide)
.process(await read('example.md'))
await remark()
.use(remarkPresetLintMarkdownStyleGuide)
.process(await read('example.md'))
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -84,20 +84,18 @@ In browsers with [`esm.sh`][esmsh]:
On the API:
```js
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import {read} from 'to-vfile'
import {reporter} from 'vfile-reporter'
main()
const file = await read('example.md')
async function main() {
const file = await remark()
.use(remarkPresetLintRecommended)
.process(await read('example.md'))
await remark()
.use(remarkPresetLintRecommended)
.process(await read('example.md'))
console.error(reporter(file))
}
console.error(reporter(file))
```
On the CLI:

View File

@ -369,28 +369,24 @@ npm install vfile-reporter remark remark-preset-lint-consistent remark-preset-li
Then create a module `example.js` that contains:
```js
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLintListItemIndent from 'remark-lint-list-item-indent'
import remarkPresetLintConsistent from 'remark-preset-lint-consistent'
import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
import remarkLintListItemIndent from 'remark-lint-list-item-indent'
import {reporter} from 'vfile-reporter'
main()
const file = await remark()
// Check that markdown is consistent.
.use(remarkPresetLintConsistent)
// Few recommended rules.
.use(remarkPresetLintRecommended)
// `remark-lint-list-item-indent` is configured with `tab-size` in the
// recommended preset, but if wed prefer something else, it can be
// reconfigured:
.use(remarkLintListItemIndent, 'space')
.process('1) Hello, _Jupiter_ and *Neptune*!')
async function main() {
const file = await remark()
// Check that markdown is consistent.
.use(remarkPresetLintConsistent)
// Few recommended rules.
.use(remarkPresetLintRecommended)
// `remark-lint-list-item-indent` is configured with `tab-size` in the
// recommended preset, but if wed prefer something else, it can be
// reconfigured:
.use(remarkLintListItemIndent, 'space')
.process('1) Hello, _Jupiter_ and *Neptune*!')
console.error(reporter(file))
}
console.error(reporter(file))
```
Running that with `node example.js` yields:
@ -411,25 +407,21 @@ When you configure lint rules and use remark to format markdown, you must
manually synchronize their configuration:
```js
import {reporter} from 'vfile-reporter'
import {remark} from 'remark'
import remarkLintEmphasisMarker from 'remark-lint-emphasis-marker'
import remarkLintStrongMarker from 'remark-lint-strong-marker'
import {reporter} from 'vfile-reporter'
main()
const file = await remark()
.use(remarkLintEmphasisMarker, '*')
.use(remarkLintStrongMarker, '*')
.use({
settings: {emphasis: '*', strong: '*'} // `remark-stringify` settings.
})
.process('_Hello_, __world__!')
async function main() {
const file = await remark()
.use(remarkLintEmphasisMarker, '*')
.use(remarkLintStrongMarker, '*')
.use({
settings: {emphasis: '*', strong: '*'} // `remark-stringify` settings.
})
.process('_Hello_, __world__!')
console.error(reporter(file))
console.log(String(file))
}
console.error(reporter(file))
console.log(String(file))
```
Yields:

919
script/build-plugins.js Normal file
View File

@ -0,0 +1,919 @@
/**
* @typedef {import('mdast').TableContent} TableContent
* @typedef {import('mdast').TopLevelContent} TopLevelContent
*
* @typedef {import('type-fest').PackageJson} PackageJson
*/
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import {inspect} from 'node:util'
import {slug as githubSlug} from 'github-slugger'
import {findAndReplace} from 'mdast-util-find-and-replace'
import {fromMarkdown} from 'mdast-util-from-markdown'
import {gfmToMarkdown} from 'mdast-util-gfm'
import {toMarkdown} from 'mdast-util-to-markdown'
import {toString} from 'mdast-util-to-string'
import parseAuthor from 'parse-author'
import {packagesUrl, plugins, presets} from './info.js'
import {characters} from './characters.js'
/** @type {PackageJson} */
const pack = JSON.parse(await fs.readFile('package.json', 'utf8'))
assert(pack.repository && typeof pack.repository === 'object')
const remote = pack.repository.url
let index = -1
while (++index < plugins.length) {
const info = plugins[index]
const packageUrl = new URL(info.name + '/', packagesUrl)
/** @type {PackageJson} */
const pack = JSON.parse(
await fs.readFile(new URL('package.json', packageUrl), 'utf8')
)
const version = (pack.version || '0').split('.')[0]
const author =
typeof pack.author === 'string' ? parseAuthor(pack.author) : pack.author
const camelcased = info.name.replace(
/-(\w)/g,
function (_, /** @type {string} */ $1) {
return $1.toUpperCase()
}
)
const org = remote.split('/').slice(0, -1).join('/')
const main = remote + '/blob/main'
const health = org + '/.github'
const hMain = health + '/blob/main'
const slug = remote.split('/').slice(-2).join('/')
let hasGfm = false
const descriptionTree = fromMarkdown(info.description)
const summaryTree = fromMarkdown(info.summary || '')
// Autolink `remark-lint`
findAndReplace(summaryTree, [
/remark-lint/g,
function () {
return {
type: 'linkReference',
identifier: 'mono',
referenceType: 'full',
children: [{type: 'inlineCode', value: 'remark-lint'}]
}
}
])
const descriptionContent = /** @type {Array<TopLevelContent>} */ (
descriptionTree.children
)
const summaryContent = /** @type {Array<TopLevelContent>} */ (
summaryTree.children
)
assert.equal(info.name, pack.name, 'expected correct package name')
/** @type {Record<string, Array<TopLevelContent>>} */
const categories = {}
let category = 'Intro'
let contentIndex = -1
while (++contentIndex < descriptionContent.length) {
const node = descriptionContent[contentIndex]
if (node.type === 'heading' && node.depth === 2) {
category = githubSlug(toString(node))
}
if (!(category in categories)) {
categories[category] = []
}
categories[category].push(node)
}
const includes = presets.filter(function (preset) {
return preset.plugins.find(function (d) {
return d[0] === info.name
})
})
/** @type {Array<TopLevelContent>} */
const children = [
{type: 'html', value: '<!--This file is generated-->'},
{
type: 'heading',
depth: 1,
children: [{type: 'text', value: info.name}]
}
]
if (info.deprecated) {
children.push(...descriptionContent)
} else {
children.push(
{
type: 'paragraph',
children: [
{
type: 'linkReference',
identifier: 'build',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'build-badge',
referenceType: 'full',
alt: 'Build'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'coverage',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'coverage-badge',
referenceType: 'full',
alt: 'Coverage'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'downloads',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'downloads-badge',
referenceType: 'full',
alt: 'Downloads'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'size',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'size-badge',
referenceType: 'full',
alt: 'Size'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'collective',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'sponsors-badge',
referenceType: 'full',
alt: 'Sponsors'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'collective',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'backers-badge',
referenceType: 'full',
alt: 'Backers'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'chat',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'chat-badge',
referenceType: 'full',
alt: 'Chat'
}
]
}
]
},
...summaryContent,
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Contents'}]
},
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'What is this?'}]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'This package is a '},
{
type: 'linkReference',
identifier: 'unified',
referenceType: 'collapsed',
children: [{type: 'text', value: 'unified'}]
},
{type: 'text', value: ' ('},
{
type: 'linkReference',
identifier: 'remark',
referenceType: 'collapsed',
children: [{type: 'text', value: 'remark'}]
},
{
type: 'text',
value: ') plugin, specifically a '
},
{
type: 'inlineCode',
value: 'remark-lint'
},
{
type: 'text',
value: '\nrule.\nLint rules check markdown code style.'
}
]
},
...(categories['when-should-i-use-this'] || []),
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Presets'}]
}
)
if (includes.length === 0) {
children.push({
type: 'paragraph',
children: [
{
type: 'text',
value: 'This rule is not included in a preset maintained here.'
}
]
})
} else {
children.push(
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'This rule is included in the following presets:'
}
]
},
{
type: 'table',
align: [],
children: [
{
type: 'tableRow',
children: [
{
type: 'tableCell',
children: [{type: 'text', value: 'Preset'}]
},
{
type: 'tableCell',
children: [{type: 'text', value: 'Setting'}]
}
]
},
...includes.map(function (preset) {
const tuple = preset.plugins.find(function (d) {
return d[0] === info.name
})
assert(tuple)
const option = tuple[1]
/** @type {TableContent} */
const row = {
type: 'tableRow',
children: [
{
type: 'tableCell',
children: [
{
type: 'link',
url: remote + '/tree/main/packages/' + preset.name,
children: [{type: 'inlineCode', value: preset.name}]
}
]
},
{
type: 'tableCell',
children: option
? [{type: 'inlineCode', value: inspect(option)}]
: []
}
]
}
return row
})
]
}
)
}
children.push(
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Install'}]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'This package is '},
{
type: 'linkReference',
identifier: 'esm',
referenceType: 'full',
children: [{type: 'text', value: 'ESM only'}]
},
{
type: 'text',
value:
'.\nIn Node.js (version 12.20+, 14.14+, or 16.0+), ' +
'install with '
},
{
type: 'linkReference',
identifier: 'npm',
referenceType: 'collapsed',
children: [{type: 'text', value: 'npm'}]
},
{type: 'text', value: ':'}
]
},
{type: 'code', lang: 'sh', value: 'npm install ' + info.name},
{
type: 'paragraph',
children: [
{type: 'text', value: 'In Deno with '},
{
type: 'linkReference',
identifier: 'esmsh',
label: 'esmsh',
referenceType: 'full',
children: [{type: 'inlineCode', value: 'esm.sh'}]
},
{type: 'text', value: ':'}
]
},
{
type: 'code',
lang: 'js',
value:
'import ' +
camelcased +
" from 'https://esm.sh/" +
info.name +
'@' +
version +
"'"
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'In browsers with '},
{
type: 'linkReference',
identifier: 'esmsh',
label: 'esmsh',
referenceType: 'full',
children: [{type: 'inlineCode', value: 'esm.sh'}]
},
{type: 'text', value: ':'}
]
},
{
type: 'code',
lang: 'html',
value:
'<script type="module">\n import ' +
camelcased +
" from 'https://esm.sh/" +
info.name +
'@' +
version +
"?bundle'\n</script>"
},
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Use'}]
},
{
type: 'paragraph',
children: [{type: 'text', value: 'On the API:'}]
},
{
type: 'code',
lang: 'js',
value: [
"import {remark} from 'remark'",
"import remarkLint from 'remark-lint'",
'import ' + camelcased + " from '" + info.name + "'",
"import {read} from 'to-vfile'",
"import {reporter} from 'vfile-reporter'",
'',
"const file = await read('example.md')",
'',
'await remark()',
' .use(remarkLint)',
' .use(' + camelcased + ')',
' .process(file)',
'',
'console.error(reporter(file))'
].join('\n')
},
{
type: 'paragraph',
children: [{type: 'text', value: 'On the CLI:'}]
},
{
type: 'code',
lang: 'sh',
value: 'remark --use remark-lint --use ' + info.name + ' example.md'
},
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'On the CLI in a config file (here a '
},
{
type: 'inlineCode',
value: 'package.json'
},
{
type: 'text',
value: '):'
}
]
},
{
type: 'code',
lang: 'diff',
value: [
' …',
' "remarkConfig": {',
' "plugins": [',
' …',
' "remark-lint",',
'+ "' + info.name + '",',
' …',
' ]',
' }',
' …'
].join('\n')
}
)
if ('api' in categories) {
const [apiHeading, ...apiBody] = categories.api
children.push(
apiHeading,
{
type: 'paragraph',
children: [
{
type: 'text',
value:
'This package exports no identifiers.\nThe default export is '
},
{type: 'inlineCode', value: camelcased},
{type: 'text', value: '.'}
]
},
{
type: 'heading',
depth: 3,
children: [
{
type: 'inlineCode',
value: 'unified().use(' + camelcased + '[, config])'
}
]
},
{
type: 'paragraph',
children: [
{
type: 'text',
value:
'This rule supports standard configuration that all remark lint rules accept\n(such as '
},
{type: 'inlineCode', value: 'false'},
{type: 'text', value: ' to turn it off or '},
{type: 'inlineCode', value: '[1, options]'},
{type: 'text', value: ' to configure it).'}
]
},
...apiBody
)
}
children.push(
...(categories.recommendation || []),
...(categories.fix || []),
...(categories.example || [])
)
let first = true
for (const check of info.checks) {
if (first) {
children.push({
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Examples'}]
})
first = false
}
/** @type {{config: unknown}} */
const {config} = JSON.parse(check.configuration)
let clean = check.input
children.push({
type: 'heading',
depth: 5,
children: [{type: 'inlineCode', value: check.name}]
})
if (config !== true) {
children.push({
type: 'paragraph',
children: [
{type: 'text', value: 'When configured with '},
{type: 'inlineCode', value: inspect(config)},
{type: 'text', value: '.'}
]
})
}
if (check.input.trim() !== '') {
children.push({
type: 'heading',
depth: 6,
children: [{type: 'text', value: 'In'}]
})
if (check.gfm) {
hasGfm = true
children.push({
type: 'blockquote',
children: [
{
type: 'paragraph',
children: [
{type: 'text', value: '👉 '},
{
type: 'strong',
children: [{type: 'text', value: 'Note'}]
},
{type: 'text', value: ': this example uses GFM ('},
{
type: 'linkReference',
identifier: 'gfm',
referenceType: 'full',
children: [{type: 'inlineCode', value: 'remark-gfm'}]
},
{type: 'text', value: ').'}
]
}
]
})
}
let index = -1
while (++index < characters.length) {
const char = characters[index]
const next = clean.replace(char.in, char.out)
if (clean !== next) {
children.push({
type: 'blockquote',
children: [
{
type: 'paragraph',
children: [
{type: 'text', value: '👉 '},
{
type: 'strong',
children: [{type: 'text', value: 'Note'}]
},
{type: 'text', value: ': '},
{type: 'inlineCode', value: char.char},
{
type: 'text',
value: ' represents ' + char.name + '.'
}
]
}
]
})
clean = next
}
}
children.push({
type: 'code',
lang: 'markdown',
value: check.input
})
}
children.push({
type: 'heading',
depth: 6,
children: [{type: 'text', value: 'Out'}]
})
if (check.output.length === 0) {
children.push({
type: 'paragraph',
children: [{type: 'text', value: 'No messages.'}]
})
} else {
children.push({
type: 'code',
lang: 'text',
value: check.output.join('\n')
})
}
}
children.push(
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Compatibility'}]
},
{
type: 'paragraph',
children: [
{
type: 'text',
value:
'Projects maintained by the unified collective are compatible with all maintained\nversions of Node.js.\nAs of now, that is Node.js 12.20+, 14.14+, and 16.0+.\nOur projects sometimes work with older versions, but this is not guaranteed.'
}
]
},
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Contribute'}]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'See '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'contributing',
children: [{type: 'inlineCode', value: 'contributing.md'}]
},
{type: 'text', value: ' in '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'health',
children: [
{
type: 'inlineCode',
value: health.split('/').slice(-2).join('/')
}
]
},
{type: 'text', value: ' for ways\nto get started.\nSee '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'support',
children: [{type: 'inlineCode', value: 'support.md'}]
},
{type: 'text', value: ' for ways to get help.'}
]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'This project has a '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'coc',
children: [{type: 'text', value: 'code of conduct'}]
},
{
type: 'text',
value:
'.\nBy interacting with this repository, organization, or community you agree to\nabide by its terms.'
}
]
}
)
}
children.push(
{type: 'heading', depth: 2, children: [{type: 'text', value: 'License'}]},
{
type: 'paragraph',
children: [
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'license',
children: [{type: 'text', value: String(pack.license || '')}]
},
{type: 'text', value: ' © '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'author',
children: [
{type: 'text', value: String((author && author.name) || '')}
]
}
]
}
)
if (!info.deprecated) {
children.push(
{
type: 'definition',
identifier: 'build-badge',
url: 'https://github.com/' + slug + '/workflows/main/badge.svg'
},
{
type: 'definition',
identifier: 'build',
url: 'https://github.com/' + slug + '/actions'
},
{
type: 'definition',
identifier: 'coverage-badge',
url: 'https://img.shields.io/codecov/c/github/' + slug + '.svg'
},
{
type: 'definition',
identifier: 'coverage',
url: 'https://codecov.io/github/' + slug
},
{
type: 'definition',
identifier: 'downloads-badge',
url: 'https://img.shields.io/npm/dm/' + info.name + '.svg'
},
{
type: 'definition',
identifier: 'downloads',
url: 'https://www.npmjs.com/package/' + info.name
},
{
type: 'definition',
identifier: 'size-badge',
url: 'https://img.shields.io/bundlephobia/minzip/' + info.name + '.svg'
},
{
type: 'definition',
identifier: 'size',
url: 'https://bundlephobia.com/result?p=' + info.name
},
{
type: 'definition',
identifier: 'sponsors-badge',
url: 'https://opencollective.com/unified/sponsors/badge.svg'
},
{
type: 'definition',
identifier: 'backers-badge',
url: 'https://opencollective.com/unified/backers/badge.svg'
},
{
type: 'definition',
identifier: 'collective',
url: 'https://opencollective.com/unified'
},
{
type: 'definition',
identifier: 'chat-badge',
url: 'https://img.shields.io/badge/chat-discussions-success.svg'
},
{
type: 'definition',
identifier: 'chat',
url: 'https://github.com/remarkjs/remark/discussions'
},
{
type: 'definition',
identifier: 'unified',
url: 'https://github.com/unifiedjs/unified'
},
{
type: 'definition',
identifier: 'remark',
url: 'https://github.com/remarkjs/remark'
},
{
type: 'definition',
identifier: 'mono',
url: 'https://github.com/' + slug
},
{
type: 'definition',
identifier: 'esm',
url: 'https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c'
},
{
type: 'definition',
identifier: 'esmsh',
url: 'https://esm.sh'
},
{
type: 'definition',
identifier: 'npm',
url: 'https://docs.npmjs.com/cli/install'
},
{
type: 'definition',
identifier: 'health',
url: health
},
{
type: 'definition',
identifier: 'contributing',
url: hMain + '/contributing.md'
},
{
type: 'definition',
identifier: 'support',
url: hMain + '/support.md'
},
{
type: 'definition',
identifier: 'coc',
url: hMain + '/code-of-conduct.md'
}
)
}
children.push(
{
type: 'definition',
identifier: 'license',
url: main + '/license'
},
{
type: 'definition',
identifier: 'author',
url: String((author && author.url) || '')
}
)
if (hasGfm) {
children.push({
type: 'definition',
identifier: 'gfm',
url: 'https://github.com/remarkjs/remark-gfm'
})
}
await fs.writeFile(
new URL('readme.md', packageUrl),
toMarkdown({type: 'root', children}, {extensions: [gfmToMarkdown()]})
)
console.log('✓ wrote `readme.md` in `' + info.name + '`')
}

File diff suppressed because it is too large Load Diff

View File

@ -1,967 +0,0 @@
/**
* @typedef {import('mdast').BlockContent} BlockContent
* @typedef {import('mdast').DefinitionContent} DefinitionContent
* @typedef {import('mdast').TableContent} TableContent
* @typedef {import('type-fest').PackageJson} PackageJson
*/
/**
* @typedef {BlockContent | DefinitionContent} BlockAndDefinitionContent
*/
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import {inspect} from 'node:util'
import {unified} from 'unified'
import {remark} from 'remark'
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import {findAndReplace} from 'mdast-util-find-and-replace'
import {toString} from 'mdast-util-to-string'
import {slug as githubSlug} from 'github-slugger'
import parseAuthor from 'parse-author'
import {rules} from './util/rules.js'
import {rule} from './util/rule.js'
import {presets} from './util/presets.js'
import {repoUrl} from './util/repo-url.js'
import {characters} from './characters.js'
const own = {}.hasOwnProperty
const remote = repoUrl('package.json')
const root = path.join(process.cwd(), 'packages')
// eslint-disable-next-line complexity, unicorn/prefer-top-level-await
presets(root).then((presetObjects) => {
const allRules = rules(root)
let index = -1
while (++index < allRules.length) {
const basename = allRules[index]
const base = path.resolve(root, basename)
/** @type {PackageJson} */
const pack = JSON.parse(
String(fs.readFileSync(path.join(base, 'package.json')))
)
const version = (pack.version || '0').split('.')[0]
const info = rule(base)
const tests = info.tests
const author =
typeof pack.author === 'string' ? parseAuthor(pack.author) : pack.author
const camelcased = basename.replace(
/-(\w)/g,
(_, /** @type {string} */ $1) => $1.toUpperCase()
)
const org = remote.split('/').slice(0, -1).join('/')
const main = remote + '/blob/main'
const health = org + '/.github'
const hMain = health + '/blob/main'
const slug = remote.split('/').slice(-2).join('/')
let hasGfm = false
const descriptionTree = unified().use(remarkParse).parse(info.description)
const summaryTree = unified()
.use(remarkParse)
.parse(info.summary || '')
// Autolink `remark-lint`
unified()
.use(
/** @type {import('unified').Plugin<Array<void>, import('mdast').Root>} */
() => (tree) => {
findAndReplace(tree, [
/remark-lint/g,
() => {
return {
type: 'linkReference',
identifier: 'mono',
referenceType: 'full',
children: [{type: 'inlineCode', value: 'remark-lint'}]
}
}
])
}
)
.runSync(summaryTree)
const descriptionContent = /** @type {Array<BlockAndDefinitionContent>} */ (
descriptionTree.children
)
const summaryContent = /** @type {Array<BlockAndDefinitionContent>} */ (
summaryTree.children
)
if (basename !== pack.name) {
throw new Error(
'Expected package name (`' +
pack.name +
'`) to be the same as directory name (`' +
basename +
'`)'
)
}
/** @type {Record<string, Array<BlockAndDefinitionContent>>} */
const categories = {}
let category = 'Intro'
let contentIndex = -1
while (++contentIndex < descriptionContent.length) {
const node = descriptionContent[contentIndex]
if (node.type === 'heading' && node.depth === 2) {
category = githubSlug(toString(node))
}
if (!(category in categories)) {
categories[category] = []
}
categories[category].push(node)
}
const includes = presetObjects.filter(
(preset) => basename in preset.packages
)
/** @type {Array<BlockAndDefinitionContent>} */
const children = [
{type: 'html', value: '<!--This file is generated-->'},
{
type: 'heading',
depth: 1,
children: [{type: 'text', value: basename}]
}
]
if (info.deprecated) {
children.push(...descriptionContent)
} else {
children.push(
{
type: 'paragraph',
children: [
{
type: 'linkReference',
identifier: 'build',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'build-badge',
referenceType: 'full',
alt: 'Build'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'coverage',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'coverage-badge',
referenceType: 'full',
alt: 'Coverage'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'downloads',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'downloads-badge',
referenceType: 'full',
alt: 'Downloads'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'size',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'size-badge',
referenceType: 'full',
alt: 'Size'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'collective',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'sponsors-badge',
referenceType: 'full',
alt: 'Sponsors'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'collective',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'backers-badge',
referenceType: 'full',
alt: 'Backers'
}
]
},
{type: 'text', value: '\n'},
{
type: 'linkReference',
identifier: 'chat',
referenceType: 'full',
children: [
{
type: 'imageReference',
identifier: 'chat-badge',
referenceType: 'full',
alt: 'Chat'
}
]
}
]
},
...summaryContent,
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Contents'}]
},
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'What is this?'}]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'This package is a '},
{
type: 'linkReference',
identifier: 'unified',
referenceType: 'collapsed',
children: [{type: 'text', value: 'unified'}]
},
{type: 'text', value: ' ('},
{
type: 'linkReference',
identifier: 'remark',
referenceType: 'collapsed',
children: [{type: 'text', value: 'remark'}]
},
{
type: 'text',
value: ') plugin, specifically a '
},
{
type: 'inlineCode',
value: 'remark-lint'
},
{
type: 'text',
value: '\nrule.\nLint rules check markdown code style.'
}
]
},
...(categories['when-should-i-use-this'] || []),
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Presets'}]
}
)
if (includes.length === 0) {
children.push({
type: 'paragraph',
children: [
{
type: 'text',
value: 'This rule is not included in a preset maintained here.'
}
]
})
} else {
children.push(
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'This rule is included in the following presets:'
}
]
},
{
type: 'table',
align: [],
children: [
{
type: 'tableRow',
children: [
{
type: 'tableCell',
children: [{type: 'text', value: 'Preset'}]
},
{
type: 'tableCell',
children: [{type: 'text', value: 'Setting'}]
}
]
},
...includes.map((preset) => {
const option = preset.packages[basename]
/** @type {TableContent} */
const row = {
type: 'tableRow',
children: [
{
type: 'tableCell',
children: [
{
type: 'link',
url: remote + '/tree/main/packages/' + preset.name,
title: null,
children: [{type: 'inlineCode', value: preset.name}]
}
]
},
{
type: 'tableCell',
children: option
? [{type: 'inlineCode', value: inspect(option)}]
: []
}
]
}
return row
})
]
}
)
}
children.push(
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Install'}]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'This package is '},
{
type: 'linkReference',
identifier: 'esm',
referenceType: 'full',
children: [{type: 'text', value: 'ESM only'}]
},
{
type: 'text',
value:
'.\nIn Node.js (version 12.20+, 14.14+, or 16.0+), ' +
'install with '
},
{
type: 'linkReference',
identifier: 'npm',
referenceType: 'collapsed',
children: [{type: 'text', value: 'npm'}]
},
{type: 'text', value: ':'}
]
},
{type: 'code', lang: 'sh', value: 'npm install ' + basename},
{
type: 'paragraph',
children: [
{type: 'text', value: 'In Deno with '},
{
type: 'linkReference',
identifier: 'esmsh',
label: 'esmsh',
referenceType: 'full',
children: [{type: 'inlineCode', value: 'esm.sh'}]
},
{type: 'text', value: ':'}
]
},
{
type: 'code',
lang: 'js',
value:
'import ' +
camelcased +
" from 'https://esm.sh/" +
basename +
'@' +
version +
"'"
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'In browsers with '},
{
type: 'linkReference',
identifier: 'esmsh',
label: 'esmsh',
referenceType: 'full',
children: [{type: 'inlineCode', value: 'esm.sh'}]
},
{type: 'text', value: ':'}
]
},
{
type: 'code',
lang: 'html',
value:
'<script type="module">\n import ' +
camelcased +
" from 'https://esm.sh/" +
basename +
'@' +
version +
"?bundle'\n</script>"
},
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Use'}]
},
{
type: 'paragraph',
children: [{type: 'text', value: 'On the API:'}]
},
{
type: 'code',
lang: 'js',
value: [
"import {read} from 'to-vfile'",
"import {reporter} from 'vfile-reporter'",
"import {remark} from 'remark'",
"import remarkLint from 'remark-lint'",
'import ' + camelcased + " from '" + basename + "'",
'',
'main()',
'',
'async function main() {',
' const file = await remark()',
' .use(remarkLint)',
' .use(' + camelcased + ')',
" .process(await read('example.md'))",
'',
' console.error(reporter(file))',
'}'
].join('\n')
},
{
type: 'paragraph',
children: [{type: 'text', value: 'On the CLI:'}]
},
{
type: 'code',
lang: 'sh',
value: 'remark --use remark-lint --use ' + basename + ' example.md'
},
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'On the CLI in a config file (here a '
},
{
type: 'inlineCode',
value: 'package.json'
},
{
type: 'text',
value: '):'
}
]
},
{
type: 'code',
lang: 'diff',
value: [
' …',
' "remarkConfig": {',
' "plugins": [',
' …',
' "remark-lint",',
'+ "' + basename + '",',
' …',
' ]',
' }',
' …'
].join('\n')
}
)
if ('api' in categories) {
const [apiHeading, ...apiBody] = categories.api
children.push(
apiHeading,
{
type: 'paragraph',
children: [
{
type: 'text',
value:
'This package exports no identifiers.\nThe default export is '
},
{type: 'inlineCode', value: camelcased},
{type: 'text', value: '.'}
]
},
{
type: 'heading',
depth: 3,
children: [
{
type: 'inlineCode',
value: 'unified().use(' + camelcased + '[, config])'
}
]
},
{
type: 'paragraph',
children: [
{
type: 'text',
value:
'This rule supports standard configuration that all remark lint rules accept\n(such as '
},
{type: 'inlineCode', value: 'false'},
{type: 'text', value: ' to turn it off or '},
{type: 'inlineCode', value: '[1, options]'},
{type: 'text', value: ' to configure it).'}
]
},
...apiBody
)
}
children.push(
...(categories.recommendation || []),
...(categories.fix || []),
...(categories.example || [])
)
let first = true
/** @type {string} */
let configuration
for (configuration in tests) {
if (own.call(tests, configuration)) {
const fixtures = tests[configuration]
if (first) {
children.push({
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Examples'}]
})
first = false
}
/** @type {string} */
let fileName
for (fileName in fixtures) {
if (own.call(fixtures, fileName)) {
const fixture = fixtures[fileName]
/** @type {{config: unknown}} */
const {config} = JSON.parse(configuration)
let clean = fixture.input
children.push({
type: 'heading',
depth: 5,
children: [{type: 'inlineCode', value: fileName}]
})
if (config !== true) {
children.push({
type: 'paragraph',
children: [
{type: 'text', value: 'When configured with '},
{type: 'inlineCode', value: inspect(config)},
{type: 'text', value: '.'}
]
})
}
if (
fixture.input !== null &&
fixture.input !== undefined &&
fixture.input.trim() !== ''
) {
children.push({
type: 'heading',
depth: 6,
children: [{type: 'text', value: 'In'}]
})
if (fixture.gfm) {
hasGfm = true
children.push({
type: 'blockquote',
children: [
{
type: 'paragraph',
children: [
{type: 'text', value: '👉 '},
{
type: 'strong',
children: [{type: 'text', value: 'Note'}]
},
{type: 'text', value: ': this example uses GFM ('},
{
type: 'linkReference',
identifier: 'gfm',
referenceType: 'full',
children: [
{type: 'inlineCode', value: 'remark-gfm'}
]
},
{type: 'text', value: ').'}
]
}
]
})
}
let index = -1
while (++index < characters.length) {
const char = characters[index]
const next = clean.replace(char.in, char.out)
if (clean !== next) {
children.push({
type: 'blockquote',
children: [
{
type: 'paragraph',
children: [
{type: 'text', value: '👉 '},
{
type: 'strong',
children: [{type: 'text', value: 'Note'}]
},
{type: 'text', value: ': '},
{type: 'inlineCode', value: char.char},
{
type: 'text',
value: ' represents ' + char.name + '.'
}
]
}
]
})
clean = next
}
}
children.push({
type: 'code',
lang: 'markdown',
value: fixture.input
})
}
children.push({
type: 'heading',
depth: 6,
children: [{type: 'text', value: 'Out'}]
})
if (fixture.output.length === 0) {
children.push({
type: 'paragraph',
children: [{type: 'text', value: 'No messages.'}]
})
} else {
children.push({
type: 'code',
lang: 'text',
value: fixture.output.join('\n')
})
}
}
}
}
}
children.push(
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Compatibility'}]
},
{
type: 'paragraph',
children: [
{
type: 'text',
value:
'Projects maintained by the unified collective are compatible with all maintained\nversions of Node.js.\nAs of now, that is Node.js 12.20+, 14.14+, and 16.0+.\nOur projects sometimes work with older versions, but this is not guaranteed.'
}
]
},
{
type: 'heading',
depth: 2,
children: [{type: 'text', value: 'Contribute'}]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'See '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'contributing',
children: [{type: 'inlineCode', value: 'contributing.md'}]
},
{type: 'text', value: ' in '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'health',
children: [
{
type: 'inlineCode',
value: health.split('/').slice(-2).join('/')
}
]
},
{type: 'text', value: ' for ways\nto get started.\nSee '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'support',
children: [{type: 'inlineCode', value: 'support.md'}]
},
{type: 'text', value: ' for ways to get help.'}
]
},
{
type: 'paragraph',
children: [
{type: 'text', value: 'This project has a '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'coc',
children: [{type: 'text', value: 'code of conduct'}]
},
{
type: 'text',
value:
'.\nBy interacting with this repository, organization, or community you agree to\nabide by its terms.'
}
]
}
)
}
children.push(
{type: 'heading', depth: 2, children: [{type: 'text', value: 'License'}]},
{
type: 'paragraph',
children: [
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'license',
children: [{type: 'text', value: String(pack.license || '')}]
},
{type: 'text', value: ' © '},
{
type: 'linkReference',
referenceType: 'collapsed',
identifier: 'author',
children: [
{type: 'text', value: String((author && author.name) || '')}
]
}
]
}
)
if (!info.deprecated) {
children.push(
{
type: 'definition',
identifier: 'build-badge',
url: 'https://github.com/' + slug + '/workflows/main/badge.svg'
},
{
type: 'definition',
identifier: 'build',
url: 'https://github.com/' + slug + '/actions'
},
{
type: 'definition',
identifier: 'coverage-badge',
url: 'https://img.shields.io/codecov/c/github/' + slug + '.svg'
},
{
type: 'definition',
identifier: 'coverage',
url: 'https://codecov.io/github/' + slug
},
{
type: 'definition',
identifier: 'downloads-badge',
url: 'https://img.shields.io/npm/dm/' + basename + '.svg'
},
{
type: 'definition',
identifier: 'downloads',
url: 'https://www.npmjs.com/package/' + basename
},
{
type: 'definition',
identifier: 'size-badge',
url: 'https://img.shields.io/bundlephobia/minzip/' + basename + '.svg'
},
{
type: 'definition',
identifier: 'size',
url: 'https://bundlephobia.com/result?p=' + basename
},
{
type: 'definition',
identifier: 'sponsors-badge',
url: 'https://opencollective.com/unified/sponsors/badge.svg'
},
{
type: 'definition',
identifier: 'backers-badge',
url: 'https://opencollective.com/unified/backers/badge.svg'
},
{
type: 'definition',
identifier: 'collective',
url: 'https://opencollective.com/unified'
},
{
type: 'definition',
identifier: 'chat-badge',
url: 'https://img.shields.io/badge/chat-discussions-success.svg'
},
{
type: 'definition',
identifier: 'chat',
url: 'https://github.com/remarkjs/remark/discussions'
},
{
type: 'definition',
identifier: 'unified',
url: 'https://github.com/unifiedjs/unified'
},
{
type: 'definition',
identifier: 'remark',
url: 'https://github.com/remarkjs/remark'
},
{
type: 'definition',
identifier: 'mono',
url: 'https://github.com/' + slug
},
{
type: 'definition',
identifier: 'esm',
url: 'https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c'
},
{
type: 'definition',
identifier: 'esmsh',
url: 'https://esm.sh'
},
{
type: 'definition',
identifier: 'npm',
url: 'https://docs.npmjs.com/cli/install'
},
{
type: 'definition',
identifier: 'health',
url: health
},
{
type: 'definition',
identifier: 'contributing',
url: hMain + '/contributing.md'
},
{
type: 'definition',
identifier: 'support',
url: hMain + '/support.md'
},
{
type: 'definition',
identifier: 'coc',
url: hMain + '/code-of-conduct.md'
}
)
}
children.push(
{
type: 'definition',
identifier: 'license',
url: main + '/license'
},
{
type: 'definition',
identifier: 'author',
url: String((author && author.url) || '')
}
)
if (hasGfm) {
children.push({
type: 'definition',
identifier: 'gfm',
url: 'https://github.com/remarkjs/remark-gfm'
})
}
fs.writeFileSync(
path.join(base, 'readme.md'),
remark().use(remarkGfm).stringify({type: 'root', children})
)
console.log('✓ wrote `readme.md` in `' + basename + '`')
}
})

253
script/info.js Normal file
View File

@ -0,0 +1,253 @@
/**
* @typedef {import('unified').Preset} Preset
*/
/**
* @typedef Check
* Check.
* @property {string} configuration
* Configuration.
* @property {boolean} gfm
* Whether to use GFM.
* @property {string} input
* Input.
* @property {string} name
* Name.
* @property {Array<string>} output
* Output.
* @property {boolean} positionless
* Whether this check also applies without positions.
*
* @typedef ExampleInfo
* Example.
* @property {unknown} [config]
* Configuration.
* @property {boolean} [gfm]
* Whether to use GFM.
* @property {'input' | 'output'} [label]
* Label.
* @property {boolean} [positionless]
* Whether this check also applies without positions.
* @property {string} name
* Name.
*
* @typedef PluginInfo
* Plugin.
* @property {boolean} deprecated
* Whether the plugin is deprecated.
* @property {string} description
* Description.
* @property {string} name
* Name.
* @property {string} ruleId
* Rule ID.
* @property {string | undefined} summary
* Summary.
* @property {Array<Check>} checks
* Checks.
*
* @typedef PresetInfo
* Preset.
* @property {string} name
* Name.
* @property {Array<[string, unknown]>} plugins
* Plugins and their configuration.
*/
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import {parse} from 'comment-parser'
import strip from 'strip-indent'
export const packagesUrl = new URL('../packages/', import.meta.url)
/**
* @type {Array<PluginInfo>}
* Plugins.
*/
export const plugins = []
/**
* @type {Array<PresetInfo>}
* Presets.
*/
export const presets = []
const names = await fs.readdir(packagesUrl)
for (const name of names) {
if (name.startsWith('remark-lint-')) {
await addPlugin(name)
}
if (name.startsWith('remark-preset-lint-')) {
await addPreset(name)
}
}
/**
* @param {string} name
* Plugin name.
* @returns {Promise<undefined>}
* Nothing.
*/
async function addPlugin(name) {
const ruleId = name.slice('remark-lint-'.length)
const code = await fs.readFile(
new URL(name + '/index.js', packagesUrl),
'utf8'
)
const fileInfo = parse(code, {spacing: 'preserve'})[0]
const tags = fileInfo.tags
const deprecatedTag = tags.find(function (d) {
return d.tag === 'deprecated'
})
const moduleTag = tags.find(function (d) {
return d.tag === 'module'
})
const summaryTag = tags.find(function (d) {
return d.tag === 'summary'
})
assert(moduleTag, 'expected `@module` in JSDoc')
assert.equal(moduleTag.name, ruleId, 'expected correct `@module`')
let description = deprecatedTag
? deprecatedTag.description
: fileInfo.description
assert(description, 'expected description (or `@deprecated`)')
description = strip(description)
/** @type {PluginInfo} */
const result = {
deprecated: Boolean(deprecatedTag),
description: description.trim(),
name,
ruleId,
summary: summaryTag ? strip(summaryTag.description).trim() : undefined,
checks: []
}
const examples = tags
.filter(function (d) {
return d.tag === 'example'
})
.map(function (d) {
return d.description.replace(/^\r?\n|\r?\n$/g, '')
})
let index = -1
while (++index < examples.length) {
const lines = examples[index].split('\n')
/** @type {ExampleInfo} */
let info
try {
info = JSON.parse(lines[0])
lines.splice(0, 1)
/* c8 ignore next 4 */
} catch (error) {
const cause = /** @type {Error} */ (error)
throw new Error('Could not parse example in `' + ruleId + '`', {cause})
}
const exampleValue = strip(lines.join('\n').replace(/^\r?\n/g, ''))
const configuration = JSON.stringify({config: info.config || true})
const name = info.name
if (!info.label) {
result.checks.push({
configuration,
name,
positionless: info.positionless || false,
gfm: info.gfm || false,
input: exampleValue,
output: []
})
continue
}
assert(info.label === 'input' || info.label === 'output')
let found = result.checks.find(function (d) {
return d.configuration === configuration && d.name === name
})
if (!found) {
found = {
configuration,
name,
positionless: info.positionless || false,
gfm: info.gfm || false,
input: '',
output: []
}
result.checks.push(found)
}
if (info.label === 'input') {
found.input = exampleValue
} else {
found.output = exampleValue.split('\n')
}
}
plugins.push(result)
}
/**
* @param {string} name
* Preset name.
* @returns {Promise<undefined>}
* Nothing.
*/
async function addPreset(name) {
/** @type {{default: Preset}} */
const mod = await import(new URL(name + '/index.js', packagesUrl).href)
const plugins = mod.default.plugins
assert(plugins, 'expected plugins in preset')
/** @type {PresetInfo} */
const presetInfo = {name, plugins: []}
let index = -1
while (++index < plugins.length) {
const plugin = plugins[index]
/** @type {import('unified').Plugin<[unknown]>} */
let fn
/** @type {unknown} */
let option
if (Array.isArray(plugin)) {
;[fn, option] = /** @type {import('unified').PluginTuple<[unknown]>} */ (
plugin
)
} else {
assert(typeof plugin === 'function')
fn = plugin
}
// @ts-expect-error: `displayName`s are fine.
const name = /** @type {string} */ (fn.displayName || fn.name)
const pluginName = name
.replace(
/[:-](\w)/g,
function (/** @type {string} */ _, /** @type {string} */ $1) {
return $1.toUpperCase()
}
)
.replace(/[A-Z]/g, function (/** @type {string} */ $0) {
return '-' + $0.toLowerCase()
})
presetInfo.plugins.push([pluginName, option])
}
presets.push(presetInfo)
}

View File

@ -0,0 +1,85 @@
/**
* @typedef {import('mdast').List} List
* @typedef {import('mdast').ListItem} ListItem
* @typedef {import('mdast').Root} Root
*
* @typedef {import('type-fest').PackageJson} PackageJson
*/
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import {zone} from 'mdast-zone'
import {packagesUrl, plugins} from '../info.js'
/**
* @type {Array<ListItem | undefined>}
* List items.
*/
const items = await Promise.all(
plugins.map(async function (info) {
const packageUrl = new URL(info.name + '/', packagesUrl)
/** @type {PackageJson} */
const pack = JSON.parse(
String(await fs.readFile(new URL('package.json', packageUrl)))
)
const description = String(pack.description || '').replace(
/^remark-lint rule to ?/i,
''
)
if (/^deprecated/i.test(description)) return
assert(pack.repository && typeof pack.repository === 'object')
assert(pack.repository.directory)
return {
type: 'listItem',
spread: false,
children: [
{
type: 'paragraph',
children: [
{
type: 'link',
url:
pack.repository.url + '/tree/main/' + pack.repository.directory,
children: [{type: 'inlineCode', value: info.name}]
},
{type: 'text', value: ' — ' + description}
]
}
]
}
})
)
/** @type {List} */
const list = {
type: 'list',
ordered: false,
spread: false,
// @ts-expect-error: filter is correct.
children: items.filter(Boolean)
}
/**
* List rules.
*
* @returns
* Transform.
*/
export default function remarkListOfRules() {
/**
* Transform.
*
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return function (tree) {
zone(tree, 'rules', function (start, _, end) {
return [start, structuredClone(list), end]
})
}
}

View File

@ -2,65 +2,77 @@
* @typedef {import('mdast').List} List
* @typedef {import('mdast').ListItem} ListItem
* @typedef {import('mdast').Root} Root
*
* @typedef {import('type-fest').PackageJson} PackageJson
*/
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import {zone} from 'mdast-zone'
import {presets} from '../util/presets.js'
import {repoUrl} from '../util/repo-url.js'
import {packagesUrl, presets} from '../info.js'
const root = path.join(process.cwd(), 'packages')
/**
* @type {Array<ListItem>}
* List items.
*/
const items = await Promise.all(
presets.map(async function (info) {
const packageUrl = new URL(info.name + '/', packagesUrl)
/** @type {PackageJson} */
const pack = JSON.parse(
await fs.readFile(new URL('package.json', packageUrl), 'utf8')
)
/** @type {import('unified').Plugin<Array<void>, Root>} */
export default function listOfPresets() {
const presetPromise = presets(root)
const description = String(pack.description || '').replace(
/^remark preset to configure remark-lint with ?/i,
''
)
return async (tree) => {
const presetObjects = await presetPromise
assert(pack.repository && typeof pack.repository === 'object')
assert(pack.repository.directory)
zone(tree, 'presets', (start, _, end) => {
/** @type {List} */
const list = {
type: 'list',
ordered: false,
spread: false,
children: presetObjects.map(({name}) => {
/** @type {PackageJson} */
const pack = JSON.parse(
String(fs.readFileSync(path.join(root, name, 'package.json')))
)
const description = String(pack.description || '').replace(
/^remark preset to configure remark-lint with ?/i,
''
)
return {
type: 'listItem',
spread: false,
children: [
{
type: 'paragraph',
children: [
{
type: 'link',
url:
pack.repository.url + '/tree/main/' + pack.repository.directory,
children: [{type: 'inlineCode', value: info.name}]
},
{type: 'text', value: ' — ' + description}
]
}
]
}
})
)
/** @type {ListItem} */
const item = {
type: 'listItem',
spread: false,
children: [
{
type: 'paragraph',
children: [
{
type: 'link',
url: repoUrl(pack),
children: [{type: 'inlineCode', value: name}]
},
{type: 'text', value: ' — ' + description}
]
}
]
}
/** @type {List} */
const list = {type: 'list', ordered: false, spread: false, children: items}
return item
})
}
return [start, list, end]
/**
* List presets.
*
* @returns
* Transform.
*/
export default function remarkListOfPresets() {
/**
* Transform.
*
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return function (tree) {
zone(tree, 'presets', function (start, _, end) {
return [start, structuredClone(list), end]
})
}
}

View File

@ -1,67 +0,0 @@
/**
* @typedef {import('type-fest').PackageJson} PackageJson
* @typedef {import('mdast').ListItem} ListItem
* @typedef {import('mdast').List} List
* @typedef {import('mdast').Root} Root
*/
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import {zone} from 'mdast-zone'
import {rules} from '../util/rules.js'
import {repoUrl} from '../util/repo-url.js'
const root = path.join(process.cwd(), 'packages')
/** @type {import('unified').Plugin<Array<void>, Root>} */
export default function listOfRules() {
return (tree) => {
zone(tree, 'rules', (start, _, end) => {
/** @type {List} */
const list = {
type: 'list',
ordered: false,
spread: false,
children: rules(root)
.map((basename) => {
/** @type {PackageJson} */
const pack = JSON.parse(
String(fs.readFileSync(path.join(root, basename, 'package.json')))
)
const description = String(pack.description || '').replace(
/^remark-lint rule to ?/i,
''
)
const deprecated = /^deprecated/i.test(description)
/** @type {ListItem} */
const item = {
type: 'listItem',
spread: false,
children: deprecated
? []
: [
{
type: 'paragraph',
children: [
{
type: 'link',
url: repoUrl(pack),
children: [{type: 'inlineCode', value: basename}]
},
{type: 'text', value: ' — ' + description}
]
}
]
}
return item
})
.filter((d) => d.children.length > 0)
}
return [start, list, end]
})
}
}

View File

@ -1,76 +0,0 @@
/**
* @typedef {import('unified').Plugin} Plugin
* @typedef {import('unified').Preset} Preset
*/
import {promises as fs} from 'node:fs'
import path from 'node:path'
import url from 'node:url'
/**
* @param {string} base
* @returns {Promise<Array<{name: string, packages: Record<string, unknown>}>>}
*/
export async function presets(base) {
const allFiles = await fs.readdir(base)
const files = allFiles.filter((basename) =>
/remark-preset-lint/.test(basename)
)
return Promise.all(
files.map(async (name) => {
const href = url.pathToFileURL(path.join(base, name, 'index.js')).href
// type-coverage:ignore-next-line
const presetMod = await import(href)
/** @type {Preset} */
// type-coverage:ignore-next-line
const preset = presetMod.default
const plugins = preset.plugins || []
/** @type {Record<string, unknown>} */
const packages = {}
let index = -1
while (++index < plugins.length) {
const plugin = plugins[index]
/** @type {Plugin} */
let fn
/** @type {unknown} */
let option
if (typeof plugin === 'function') {
fn = plugin
} else if (Array.isArray(plugin)) {
// Fine:
// type-coverage:ignore-next-line
fn = plugin[0]
// Fine:
// type-coverage:ignore-next-line
option = plugin[1]
} else {
throw new TypeError(
'Expected plugin, plugin tuple, not `' + plugin + '`'
)
}
/** @type {string} */
// @ts-expect-error: `displayName`s are fine.
const name = fn.displayName || fn.name
packages[
name
.replace(
/[:-](\w)/g,
(/** @type {string} */ _, /** @type {string} */ $1) =>
$1.toUpperCase()
)
.replace(
/[A-Z]/g,
(/** @type {string} */ $0) => '-' + $0.toLowerCase()
)
] = option
}
return {name, packages}
})
)
}

View File

@ -1,38 +0,0 @@
import fs from 'node:fs'
/**
* @typedef {import('type-fest').PackageJson} PackageJson
*/
/**
* @param {string | PackageJson} pathOrJson
* @returns {string}
*/
export function repoUrl(pathOrJson) {
const pkg =
typeof pathOrJson === 'string' ? readPackageJson(pathOrJson) : pathOrJson
if (
pkg.repository === undefined ||
typeof pkg.repository !== 'object' ||
typeof pkg.repository.url !== 'string'
) {
throw new TypeError(
`Expected \`string\` for \`repository.url\` in \`${pathOrJson}\``
)
}
if (pkg.repository.directory) {
return pkg.repository.url + `/tree/main/${pkg.repository.directory}`
}
return pkg.repository.url
}
/**
* @param {string} filePath
* @returns {PackageJson}
*/
function readPackageJson(filePath) {
return JSON.parse(String(fs.readFileSync(filePath)))
}

View File

@ -1,140 +0,0 @@
/**
* @typedef Rule
* @property {string} ruleId
* @property {string} description
* @property {string | undefined} summary
* @property {boolean} deprecated
* @property {Record<string, Checks>} tests
* @property {string} filePath
*
* @typedef {Record<string, Check>} Checks
*
* @typedef Check
* @property {string} input
* @property {Array<string>} output
* @property {boolean} gfm
* @property {boolean} positionless
*/
import fs from 'node:fs'
import path from 'node:path'
import {parse} from 'comment-parser'
import strip from 'strip-indent'
/**
* Get information for a rule at `filePath`.
*
* @param {string} filePath
* @returns {Rule}
*/
export function rule(filePath) {
const ruleId = path.basename(filePath).slice('remark-lint-'.length)
/** @type {Record<string, Checks>} */
const tests = {}
const code = fs.readFileSync(path.join(filePath, 'index.js'), 'utf8')
// Note: To do: `comment-parser` types are wrong.
/** @type {import('comment-parser/primitives').Block} */
const fileInfo = parse(code, {spacing: 'preserve'})[0]
const tags = fileInfo.tags
const moduleTag = tags.find((d) => d.tag === 'module')
const summaryTag = tags.find((d) => d.tag === 'summary')
const deprecatedTag = tags.find((d) => d.tag === 'deprecated')
/* c8 ignore next 3 */
if (!moduleTag) {
throw new Error('Expected `@module` in JSDoc')
}
const name = moduleTag.name
let description =
(deprecatedTag && deprecatedTag.description) || fileInfo.description
/* c8 ignore next 3 */
if (name !== ruleId) {
throw new Error(ruleId + ' has an incorrect `@module`: ' + name)
}
/* c8 ignore next 3 */
if (!description) {
throw new Error(ruleId + ' is missing a description or `@deprecated`')
}
description = strip(description)
/** @type {Rule} */
const result = {
ruleId,
description: description.trim(),
summary: summaryTag ? strip(summaryTag.description).trim() : undefined,
deprecated: Boolean(deprecatedTag),
tests,
filePath
}
const examples = tags
.filter((d) => d.tag === 'example')
.map((d) => d.description.replace(/^\r?\n|\r?\n$/g, ''))
let index = -1
while (++index < examples.length) {
const lines = examples[index].split('\n')
/** @type {{name: string, label?: 'input' | 'output', config?: unknown, positionless?: boolean, gfm?: boolean}} */
let info
try {
info = JSON.parse(lines[0])
lines.splice(0, 1)
/* c8 ignore next 6 */
} catch (error) {
const exception = /** @type Error */ (error)
throw new Error(
'Could not parse example in ' + ruleId + ':\n' + exception.stack
)
}
const exampleValue = strip(lines.join('\n').replace(/^\r?\n/g, ''))
const configuration = JSON.stringify({config: info.config || true})
const name = info.name
const context =
configuration in tests
? tests[configuration]
: (tests[configuration] = {})
if (!info.label) {
context[name] = {
positionless: info.positionless || false,
gfm: info.gfm || false,
input: exampleValue,
output: []
}
continue
}
/* c8 ignore next 9 */
if (info.label !== 'input' && info.label !== 'output') {
throw new Error(
'Expected `input` or `ouput` for `label` in ' +
ruleId +
', not `' +
info.label +
'`'
)
}
if (!context[name]) {
context[name] = {
positionless: info.positionless || false,
gfm: info.gfm || false,
input: '',
output: []
}
}
// @ts-expect-error: fine: array for output, string for rest.
context[name][info.label] =
info.label === 'output' ? exampleValue.split('\n') : exampleValue
}
return result
}

View File

@ -1,13 +0,0 @@
import fs from 'node:fs'
/**
* @param {string} filePath
* @returns {Array<string>}
*/
export function rules(filePath) {
return fs
.readdirSync(filePath)
.filter(
(basename) => /remark-lint/.test(basename) && basename !== 'remark-lint'
)
}

590
test.js
View File

@ -1,32 +1,33 @@
/**
* @typedef {import('unified').Plugin} Plugin
* @typedef {import('vfile-message').VFileMessage} VFileMessage
* @typedef {import('./script/util/rule.js').Check} Check
* @typedef {import('./script/util/rule.js').Rule} Rule
* @typedef {import('unified').PluggableList} PluggableList
* @typedef {import('unified').Plugin<[unknown]>} Plugin
*
* @typedef {import('./script/info.js').Check} Check
* @typedef {import('./script/info.js').PluginInfo} PluginInfo
*/
import assert from 'node:assert/strict'
import path from 'node:path'
import process from 'node:process'
import test from 'node:test'
import url from 'node:url'
import {toVFile} from 'to-vfile'
import {removePosition} from 'unist-util-remove-position'
import {remark} from 'remark'
import remarkGfm from 'remark-gfm'
import remarkLint from 'remark-lint'
import remarkLintFinalNewline from 'remark-lint-final-newline'
import remarkLintNoHeadingPunctuation from 'remark-lint-no-heading-punctuation'
import remarkLintNoMultipleToplevelHeadings from 'remark-lint-no-multiple-toplevel-headings'
import remarkLintNoUndefinedReferences from 'remark-lint-no-undefined-references'
import {lintRule} from 'unified-lint-rule'
import {rules} from './script/util/rules.js'
import {rule} from './script/util/rule.js'
import {removePosition} from 'unist-util-remove-position'
import {VFile} from 'vfile'
import {characters} from './script/characters.js'
import lint from './packages/remark-lint/index.js'
import noHeadingPunctuation from './packages/remark-lint-no-heading-punctuation/index.js'
import noMultipleToplevelHeadings from './packages/remark-lint-no-multiple-toplevel-headings/index.js'
import noUndefinedReferences from './packages/remark-lint-no-undefined-references/index.js'
import finalNewline from './packages/remark-lint-final-newline/index.js'
import {plugins} from './script/info.js'
const own = {}.hasOwnProperty
test('remark-lint', async function (t) {
await t.test('should expose the public api', async function () {
assert.deepEqual(Object.keys(await import('remark-lint')).sort(), [
'default'
])
})
test('core', async () => {
const doc = [
'# A heading',
'',
@ -37,53 +38,48 @@ test('core', async () => {
'# Another main heading.'
].join('\n')
let file = await remark()
.use(noHeadingPunctuation)
.use(noMultipleToplevelHeadings)
.use(lint)
.process(toVFile({path: 'virtual.md', value: doc}))
await t.test('should support `remark-lint` last', async function () {
const file = await remark()
.use(remarkLintNoHeadingPunctuation)
.use(remarkLintNoMultipleToplevelHeadings)
.use(remarkLint)
.process({path: 'virtual.md', value: doc})
assert.deepEqual(
asStrings(file.messages),
[
assert.deepEqual(file.messages.map(String), [
'virtual.md:3:1-3:24: Dont add a trailing `.` to headings',
'virtual.md:3:1-3:24: Dont use multiple top level headings (1:1)'
],
'should support `remark-lint` last'
)
])
})
file = await remark()
.use(lint)
.use(noHeadingPunctuation)
.use(noMultipleToplevelHeadings)
.process(toVFile({path: 'virtual.md', value: doc}))
await t.test('should support `remark-lint` first', async function () {
const file = await remark()
.use(remarkLint)
.use(remarkLintNoHeadingPunctuation)
.use(remarkLintNoMultipleToplevelHeadings)
.process({path: 'virtual.md', value: doc})
assert.deepEqual(
asStrings(file.messages),
[
assert.deepEqual(file.messages.map(String), [
'virtual.md:3:1-3:24: Dont add a trailing `.` to headings',
'virtual.md:3:1-3:24: Dont use multiple top level headings (1:1)'
],
'should support `remark-lint` first'
)
])
})
file = await remark().use(lint).process('.')
await t.test('should support no rules', async function () {
const file = await remark().use(remarkLint).process('.')
assert.deepEqual(asStrings(file.messages), [], 'should support no rules')
assert.deepEqual(file.messages, [])
})
file = await remark().use(finalNewline).process('')
await t.test('should support successful rules', async function () {
const file = await remark().use(remarkLintFinalNewline).process('')
assert.deepEqual(
asStrings(file.messages),
[],
'should support successful rules'
)
assert.deepEqual(file.messages, [])
})
file = await remark().use(finalNewline, [2]).process('.')
await t.test('should support a list with a severity', async function () {
const file = await remark().use(remarkLintFinalNewline, [2]).process('.')
assert.deepEqual(
file.messages.map((d) => JSON.parse(JSON.stringify(d))),
[
assert.deepEqual(file.messages.map(jsonClone), [
{
fatal: true,
message: 'Missing newline character at end of file',
@ -93,315 +89,269 @@ test('core', async () => {
source: 'remark-lint',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme'
}
],
'should support a list with a severity'
])
})
await t.test('should support a boolean (`true`)', async function () {
const file = await remark().use(remarkLintFinalNewline, true).process('.')
assert.deepEqual(file.messages.map(String), [
'1:1: Missing newline character at end of file'
])
})
await t.test('should support a boolean (`false`)', async function () {
const file = await remark().use(remarkLintFinalNewline, false).process('.')
assert.deepEqual(file.messages, [])
})
await t.test(
'should support a list with a boolean severity (true, for on)',
async function () {
const file = await remark()
.use(remarkLintFinalNewline, [true])
.process('.')
assert.deepEqual(file.messages.map(String), [
'1:1: Missing newline character at end of file'
])
}
)
file = await remark().use(finalNewline, true).process('.')
await t.test(
'should support a list with boolean severity (false, for off)',
async function () {
const file = await remark()
.use(remarkLintFinalNewline, [false])
.process('.')
assert.deepEqual(
asStrings(file.messages),
['1:1: Missing newline character at end of file'],
'should support a boolean (`true`)'
assert.deepEqual(file.messages, [])
}
)
file = await remark().use(finalNewline, false).process('.')
await t.test(
'should support a list with string severity (`error`)',
async function () {
const file = await remark()
.use(remarkLintFinalNewline, ['error'])
.process('.')
assert.deepEqual(
asStrings(file.messages),
[],
'should support a boolean (`false`)'
assert.deepEqual(file.messages.map(jsonClone), [
{
fatal: true,
message: 'Missing newline character at end of file',
name: '1:1',
reason: 'Missing newline character at end of file',
ruleId: 'final-newline',
source: 'remark-lint',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme'
}
])
}
)
file = await remark().use(finalNewline, [true]).process('.')
await t.test(
'should support a list with string severity (`on`)',
async function () {
const file = await remark()
.use(remarkLintFinalNewline, ['on'])
.process('.')
assert.deepEqual(
asStrings(file.messages),
['1:1: Missing newline character at end of file'],
'should support a list with a boolean severity (true, for on)'
assert.deepEqual(file.messages.map(jsonClone), [
{
fatal: false,
message: 'Missing newline character at end of file',
name: '1:1',
reason: 'Missing newline character at end of file',
ruleId: 'final-newline',
source: 'remark-lint',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme'
}
])
}
)
file = await remark().use(finalNewline, [false]).process('.')
await t.test(
'should support a list with string severity (`warn`)',
async function () {
const file = await remark()
.use(remarkLintFinalNewline, ['warn'])
.process('.')
assert.deepEqual(
asStrings(file.messages),
[],
'should support a list with boolean severity (false, for off)'
assert.deepEqual(file.messages.map(jsonClone), [
{
fatal: false,
message: 'Missing newline character at end of file',
name: '1:1',
reason: 'Missing newline character at end of file',
ruleId: 'final-newline',
source: 'remark-lint',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme'
}
])
}
)
file = await remark().use(finalNewline, ['error']).process('.')
await t.test(
'should support a list with string severity (`off`)',
async function () {
const file = await remark()
.use(remarkLintFinalNewline, ['off'])
.process('.')
assert.deepEqual(
file.messages.map((d) => JSON.parse(JSON.stringify(d))),
[
{
fatal: true,
message: 'Missing newline character at end of file',
name: '1:1',
reason: 'Missing newline character at end of file',
ruleId: 'final-newline',
source: 'remark-lint',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme'
}
],
'should support a list with string severity (`error`)'
assert.deepEqual(file.messages, [])
}
)
file = await remark().use(finalNewline, ['on']).process('.')
assert.deepEqual(
file.messages.map((d) => JSON.parse(JSON.stringify(d))),
[
{
fatal: false,
message: 'Missing newline character at end of file',
name: '1:1',
reason: 'Missing newline character at end of file',
ruleId: 'final-newline',
source: 'remark-lint',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme'
}
],
'should support a list with string severity (`on`)'
await t.test(
'should fail on incorrect severities (too high)',
async function () {
assert.throws(function () {
remark().use(remarkLintFinalNewline, [3]).freeze()
}, /^Error: Incorrect severity `3` for `final-newline`, expected 0, 1, or 2$/)
}
)
file = await remark().use(finalNewline, ['warn']).process('.')
assert.deepEqual(
file.messages.map((d) => JSON.parse(JSON.stringify(d))),
[
{
fatal: false,
message: 'Missing newline character at end of file',
name: '1:1',
reason: 'Missing newline character at end of file',
ruleId: 'final-newline',
source: 'remark-lint',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme'
}
],
'should support a list with string severity (`warn`)'
await t.test(
'should fail on incorrect severities (too low)',
async function () {
assert.throws(function () {
remark().use(remarkLintFinalNewline, [-1]).freeze()
}, /^Error: Incorrect severity `-1` for `final-newline`, expected 0, 1, or 2$/)
}
)
file = await remark().use(finalNewline, ['off']).process('.')
await t.test(
'should support regex as options (remark-lint-no-undefined-references)',
async function () {
const file = await remark()
.use(remarkLintNoUndefinedReferences, {allow: [/^b\./i]})
.process({
path: 'virtual.md',
value: ['[foo][b.c]', '', '[bar][b]'].join('\n')
})
assert.deepEqual(
asStrings(file.messages),
[],
'should support a list with string severity (`off`)'
assert.deepEqual(file.messages.map(String), [
'virtual.md:3:1-3:9: Found reference to undefined definition'
])
}
)
assert.throws(
() => {
remark().use(finalNewline, [3]).freeze()
},
/^Error: Incorrect severity `3` for `final-newline`, expected 0, 1, or 2$/,
'should fail on incorrect severities (too high)'
)
await t.test(
'should support meta as a string (unified-lint-rule)',
async function () {
const file = await remark()
.use(
lintRule('test:rule', function (_, file) {
file.message('Test message')
}),
['warn']
)
.process('.')
assert.throws(
() => {
remark().use(finalNewline, [-1]).freeze()
},
/^Error: Incorrect severity `-1` for `final-newline`, expected 0, 1, or 2$/,
'should fail on incorrect severities (too low)'
)
file = await remark()
.use(noUndefinedReferences, {allow: [/^b\./i]})
.process(
toVFile({
path: 'virtual.md',
value: ['[foo][b.c]', '', '[bar][b]'].join('\n')
})
)
assert.deepEqual(
asStrings(file.messages),
['virtual.md:3:1-3:9: Found reference to undefined definition'],
'no-undefined-references allow option should work with native regex'
)
file = await remark()
.use(
lintRule('test:rule', (tree, file) => {
file.message('Test message')
}),
['warn']
)
.process('.')
assert.deepEqual(
file.messages.map((d) => JSON.parse(JSON.stringify(d))),
[
{
fatal: false,
message: 'Test message',
name: '1:1',
reason: 'Test message',
ruleId: 'rule',
source: 'test'
}
],
'should support string meta'
assert.deepEqual(file.messages.map(jsonClone), [
{
fatal: false,
message: 'Test message',
name: '1:1',
reason: 'Test message',
ruleId: 'rule',
source: 'test'
}
])
}
)
})
test('rules', async (t) => {
const root = path.join(process.cwd(), 'packages')
const all = rules(root)
let index = -1
test('plugins', async function (t) {
for (const plugin of plugins) {
await t.test(plugin.name, async function () {
await assertPlugin(plugin)
})
}
})
while (++index < all.length) {
const basename = all[index]
const base = path.resolve(root, basename)
const info = rule(base)
const href = url.pathToFileURL(base).href + '/index.js'
/**
* @param {PluginInfo} info
* Info.
* @returns {Promise<undefined>}
* Nothing.
*/
async function assertPlugin(info) {
/** @type {{default: Plugin}} */
const pluginMod = await import(info.name)
const plugin = pluginMod.default
/** @type {{default: Plugin}} */
const pluginMod = await import(href)
const fn = pluginMod.default
for (const check of info.checks) {
await assertCheck(plugin, info, check)
}
}
if (Object.keys(info.tests).length === 0) {
assert.ok(true, info.ruleId + ': no tests')
} else {
await t.test(info.ruleId, async () => {
const tests = info.tests
/** @type {string} */
let configuration
/**
* @param {Plugin} plugin
* Plugin.
* @param {PluginInfo} info
* info.
* @param {Check} check
* Check.
* @returns {Promise<undefined>}
* Nothing.
*/
async function assertCheck(plugin, info, check) {
/** @type {{config: unknown}} */
const {config} = JSON.parse(check.configuration)
/** @type {PluggableList} */
const extras = check.gfm ? [remarkGfm] : []
let value = check.input
for (configuration in tests) {
if (own.call(tests, configuration)) {
const checks = tests[configuration]
/** @type {{config: unknown}} */
const {config} = JSON.parse(configuration)
for (const character of characters) {
value = value.replace(character.in, character.out)
}
/** @type {string} */
let name
const file = await remark()
.use(plugin, config)
.use(extras)
.process(new VFile({path: check.name, value}))
for (name in checks) {
if (own.call(checks, name)) {
const basename = name
const check = checks[name]
for (const message of file.messages) {
assert.equal(message.ruleId, info.ruleId)
assert.equal(
message.url,
'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-' +
info.ruleId +
'#readme'
)
}
await assertFixture(fn, info, check, basename, config)
}
}
}
assert.deepEqual(
file.messages.map(String).map(function (value) {
return value.slice(value.indexOf(':') + 1)
}),
check.output
)
if (!check.positionless) {
const file = await remark()
.use(function () {
return function (tree) {
removePosition(tree)
}
})
}
}
})
.use(plugin, config)
.use(extras)
.process(new VFile({path: check.name, value}))
/**
* @param {Plugin} rule
* @param {Rule} info
* @param {Check} fixture
* @param {string} basename
* @param {unknown} config
*/
/* eslint-disable-next-line max-params */
function assertFixture(rule, info, fixture, basename, config) {
const ruleId = info.ruleId
const file = toVFile(basename)
const expected = fixture.output
const positionless = fixture.positionless
// @ts-expect-error: to do: fix types.
let proc = remark().use(rule, config)
if (fixture.gfm) proc.use(remarkGfm)
file.value = preprocess(fixture.input || '')
try {
proc.runSync(proc.parse(file), file)
} catch (error) {
const exception = /** @type VFileMessage */ (error)
if (exception && exception.source !== 'remark-lint') {
throw exception
}
}
let index = -1
while (++index < file.messages.length) {
const message = file.messages[index]
if (message.ruleId !== ruleId) {
throw new Error(
'Expected `' +
ruleId +
'`, not `' +
message.ruleId +
'` as `ruleId` for ' +
message
)
}
const expectedUrl =
'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-' +
ruleId +
'#readme'
if (message.url !== expectedUrl) {
throw new Error(
'Expected `' +
expectedUrl +
'`, not `' +
message.url +
'` as `ruleId` for ' +
message
)
}
}
assert.deepEqual(
normalize(file.messages),
expected,
'should equal with position'
)
if (!positionless) {
file.messages = []
proc = remark()
.use(() => (tree) => removePosition(tree))
// @ts-expect-error: to do: fix types.
.use(rule, config)
if (fixture.gfm) proc.use(remarkGfm)
proc.processSync(file)
assert.deepEqual(
normalize(file.messages),
[],
'should equal without position'
)
assert.deepEqual(file.messages, [])
}
}
/**
* @param {Array<VFileMessage>} messages
* @returns {Array<string>}
* @param {unknown} d
* Value.
* @returns {unknown}
* Cloned value.
*/
function normalize(messages) {
return asStrings(messages).map((value) => value.slice(value.indexOf(':') + 1))
}
/**
* @param {Array<VFileMessage>} messages
* @returns {Array<string>}
*/
function asStrings(messages) {
return messages.map(String)
}
/**
* @param {string} value
* @returns {string}
*/
function preprocess(value) {
let index = -1
while (++index < characters.length) {
value = value.replace(characters[index].in, characters[index].out)
}
return value
function jsonClone(d) {
return JSON.parse(JSON.stringify(d))
}

View File

@ -6,7 +6,7 @@
"declarationMap": true,
"emitDeclarationOnly": true,
"exactOptionalPropertyTypes": true,
"lib": ["es2020"],
"lib": ["es2022"],
"module": "node16",
"strict": true,
"target": "es2020"