mirror of
https://github.com/remarkjs/remark-lint.git
synced 2024-08-17 15:50:25 +03:00
Add better messages, rewrite and improve rules
This commit is contained in:
parent
ccea69188b
commit
45aeac273a
@ -133,6 +133,7 @@
|
|||||||
"remark-cli": "^12.0.0",
|
"remark-cli": "^12.0.0",
|
||||||
"remark-comment-config": "^8.0.0",
|
"remark-comment-config": "^8.0.0",
|
||||||
"remark-directive": "^3.0.0",
|
"remark-directive": "^3.0.0",
|
||||||
|
"remark-frontmatter": "^5.0.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-github": "^12.0.0",
|
"remark-github": "^12.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
|
@ -65,41 +65,52 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": 4}
|
|
||||||
*
|
|
||||||
* > Hello
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* > World
|
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": 2}
|
|
||||||
*
|
|
||||||
* > Hello
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* > World
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"config": 2, "name": "ok-2.md"}
|
||||||
*
|
*
|
||||||
* > Hello
|
* > Mercury.
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Venus.
|
||||||
*
|
*
|
||||||
* > World
|
* > Earth.
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* > World
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"config": 4, "name": "ok-4.md"}
|
||||||
*
|
*
|
||||||
* 5:5: Remove 1 space between block quote and content
|
* > Mercury.
|
||||||
* 9:3: Add 1 space between block quote and content
|
*
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* > Earth.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* { "name": "ok-tab.md"}
|
||||||
|
*
|
||||||
|
* >␉Mercury.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* > Mercury.
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* > Earth.
|
||||||
|
*
|
||||||
|
* Mars.
|
||||||
|
*
|
||||||
|
* > Jupiter
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 5:5: Unexpected `4` spaces between block quote marker and content, expected `3` spaces, remove `1` space
|
||||||
|
* 9:3: Unexpected `2` spaces between block quote marker and content, expected `3` spaces, add `1` space
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "🌍", "label": "output", "name": "not-ok-options.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:1: Unexpected value `🌍` for `options`, expected `number` or `'consistent'`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,7 +125,7 @@
|
|||||||
import pluralize from 'pluralize'
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
|
||||||
const remarkLintBlockquoteIndentation = lintRule(
|
const remarkLintBlockquoteIndentation = lintRule(
|
||||||
{
|
{
|
||||||
@ -130,33 +141,53 @@ const remarkLintBlockquoteIndentation = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
let option = options || 'consistent'
|
/** @type {number | undefined} */
|
||||||
|
let expected
|
||||||
|
|
||||||
visit(tree, 'blockquote', function (node) {
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
|
// Empty.
|
||||||
|
} else if (typeof options === 'number') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' +
|
||||||
|
options +
|
||||||
|
"` for `options`, expected `number` or `'consistent'`"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
visitParents(tree, 'blockquote', function (node, parents) {
|
||||||
const start = pointStart(node)
|
const start = pointStart(node)
|
||||||
const head = pointStart(node.children[0])
|
const headStart = pointStart(node.children[0])
|
||||||
|
|
||||||
if (head && start) {
|
if (headStart && start) {
|
||||||
const count = head.column - start.column
|
const actual = headStart.column - start.column
|
||||||
|
|
||||||
if (option === 'consistent') {
|
if (expected) {
|
||||||
option = count
|
const difference = expected - actual
|
||||||
} else {
|
const differenceAbsolute = Math.abs(difference)
|
||||||
const diff = option - count
|
|
||||||
|
|
||||||
if (diff !== 0) {
|
|
||||||
const abs = Math.abs(diff)
|
|
||||||
|
|
||||||
|
if (difference !== 0) {
|
||||||
file.message(
|
file.message(
|
||||||
(diff > 0 ? 'Add' : 'Remove') +
|
'Unexpected `' +
|
||||||
' ' +
|
actual +
|
||||||
abs +
|
'` ' +
|
||||||
' ' +
|
pluralize('space', actual) +
|
||||||
pluralize('space', abs) +
|
' between block quote marker and content, expected `' +
|
||||||
' between block quote and content',
|
expected +
|
||||||
head
|
'` ' +
|
||||||
|
pluralize('space', expected) +
|
||||||
|
', ' +
|
||||||
|
(difference > 0 ? 'add' : 'remove') +
|
||||||
|
' `' +
|
||||||
|
differenceAbsolute +
|
||||||
|
'` ' +
|
||||||
|
pluralize('space', differenceAbsolute),
|
||||||
|
{ancestors: [...parents, node], place: headStart}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
expected = actual
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -171,36 +171,48 @@ Due to this, it’s recommended to configure this rule with `2`.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-2.md`
|
||||||
|
|
||||||
When configured with `4`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
> Hello
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
|
|
||||||
> World
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
No messages.
|
|
||||||
|
|
||||||
##### `ok.md`
|
|
||||||
|
|
||||||
When configured with `2`.
|
When configured with `2`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
> Hello
|
> Mercury.
|
||||||
|
|
||||||
Paragraph.
|
Venus.
|
||||||
|
|
||||||
> World
|
> Earth.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `ok-4.md`
|
||||||
|
|
||||||
|
When configured with `4`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> Mercury.
|
||||||
|
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
> Earth.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `ok-tab.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
>␉Mercury.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -212,22 +224,32 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
> Hello
|
> Mercury.
|
||||||
|
|
||||||
Paragraph.
|
Venus.
|
||||||
|
|
||||||
> World
|
> Earth.
|
||||||
|
|
||||||
Paragraph.
|
Mars.
|
||||||
|
|
||||||
> World
|
> Jupiter
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5:5: Remove 1 space between block quote and content
|
5:5: Unexpected `4` spaces between block quote marker and content, expected `3` spaces, remove `1` space
|
||||||
9:3: Add 1 space between block quote and content
|
9:3: Unexpected `2` spaces between block quote marker and content, expected `3` spaces, add `1` space
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok-options.md`
|
||||||
|
|
||||||
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options`, expected `number` or `'consistent'`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
*
|
*
|
||||||
* You can use this package to check that the style of GFM tasklists is
|
* You can use this package to check that the style of GFM tasklists is
|
||||||
* consistent.
|
* consistent.
|
||||||
|
* Task lists are a GFM feature enabled with
|
||||||
|
* [`remark-gfm`][github-remark-gfm].
|
||||||
*
|
*
|
||||||
* ## API
|
* ## API
|
||||||
*
|
*
|
||||||
@ -63,6 +65,7 @@
|
|||||||
* [api-options]: #options
|
* [api-options]: #options
|
||||||
* [api-remark-lint-checkbox-character-style]: #unifieduseremarklintcheckboxcharacterstyle-options
|
* [api-remark-lint-checkbox-character-style]: #unifieduseremarklintcheckboxcharacterstyle-options
|
||||||
* [api-styles]: #styles
|
* [api-styles]: #styles
|
||||||
|
* [github-remark-gfm]: https://github.com/remarkjs/remark-gfm
|
||||||
* [github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
|
* [github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
|
||||||
* [github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
* [github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
||||||
*
|
*
|
||||||
@ -70,55 +73,60 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": {"checked": "x"}, "gfm": true}
|
|
||||||
*
|
|
||||||
* - [x] List item
|
|
||||||
* - [x] List item
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": {"checked": "X"}, "gfm": true}
|
* {"config": {"checked": "x"}, "gfm": true, "name": "ok-x.md"}
|
||||||
*
|
*
|
||||||
* - [X] List item
|
* - [x] Mercury.
|
||||||
* - [X] List item
|
* - [x] Venus.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": {"unchecked": " "}, "gfm": true}
|
* {"config": {"checked": "X"}, "gfm": true, "name": "ok-x-upper.md"}
|
||||||
*
|
*
|
||||||
* - [ ] List item
|
* - [X] Mercury.
|
||||||
* - [ ] List item
|
* - [X] Venus.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": {"unchecked": " "}, "gfm": true, "name": "ok-space.md"}
|
||||||
|
*
|
||||||
|
* - [ ] Mercury.
|
||||||
|
* - [ ] Venus.
|
||||||
* - [ ]␠␠
|
* - [ ]␠␠
|
||||||
* - [ ]
|
* - [ ]
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": {"unchecked": "\t"}, "gfm": true}
|
* {"config": {"unchecked": "\t"}, "gfm": true, "name": "ok-tab.md"}
|
||||||
*
|
*
|
||||||
* - [␉] List item
|
* - [␉] Mercury.
|
||||||
* - [␉] List item
|
* - [␉] Venus.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input", "gfm": true}
|
* {"label": "input", "gfm": true, "name": "not-ok-default.md"}
|
||||||
*
|
*
|
||||||
* - [x] List item
|
* - [x] Mercury.
|
||||||
* - [X] List item
|
* - [X] Venus.
|
||||||
* - [ ] List item
|
* - [ ] Earth.
|
||||||
* - [␉] List item
|
* - [␉] Mars.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "gfm": true, "name": "not-ok-default.md"}
|
||||||
|
*
|
||||||
|
* 2:5: Unexpected checked checkbox value `X`, expected `x`
|
||||||
|
* 4:5: Unexpected unchecked checkbox value `\t`, expected ` `
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output", "gfm": true}
|
* {"config": "🌍", "label": "output", "name": "not-ok-option.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 2:5: Checked checkboxes should use `x` as a marker
|
* 1:1: Unexpected value `🌍` for `options`, expected an object or `'consistent'`
|
||||||
* 4:5: Unchecked checkboxes should use ` ` as a marker
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": {"unchecked": "💩"}, "name": "not-ok.md", "label": "output", "positionless": true, "gfm": true}
|
* {"config": {"unchecked": "🌍"}, "label": "output", "name": "not-ok-option-unchecked.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect unchecked checkbox marker `💩`: use either `'\t'`, or `' '`
|
* 1:1: Unexpected value `🌍` for `options.unchecked`, expected `'\t'`, `' '`, or `'consistent'`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": {"checked": "💩"}, "name": "not-ok.md", "label": "output", "positionless": true, "gfm": true}
|
* {"config": {"checked": "🌍"}, "label": "output", "name": "not-ok-option-checked.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect checked checkbox marker `💩`: use either `'x'`, or `'X'`
|
* 1:1: Unexpected value `🌍` for `options.checked`, expected `'X'`, `'x'`, or `'consistent'`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,7 +147,8 @@
|
|||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const remarkLintCheckboxCharacterStyle = lintRule(
|
const remarkLintCheckboxCharacterStyle = lintRule(
|
||||||
{
|
{
|
||||||
@ -156,77 +165,115 @@ const remarkLintCheckboxCharacterStyle = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
/** @type {'X' | 'x' | 'consistent'} */
|
/** @type {'X' | 'x' | undefined} */
|
||||||
let checked = 'consistent'
|
let checkedExpected
|
||||||
/** @type {'\x09' | ' ' | 'consistent'} */
|
/** @type {VFileMessage | undefined} */
|
||||||
let unchecked = 'consistent'
|
let checkedConsistentCause
|
||||||
|
/** @type {'\t' | ' ' | undefined} */
|
||||||
|
let uncheckedExpected
|
||||||
|
/** @type {VFileMessage | undefined} */
|
||||||
|
let uncheckedConsistentCause
|
||||||
|
|
||||||
if (options && typeof options === 'object') {
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
checked = options.checked || 'consistent'
|
// Empty.
|
||||||
unchecked = options.unchecked || 'consistent'
|
} else if (typeof options === 'object') {
|
||||||
}
|
if (options.checked === 'X' || options.checked === 'x') {
|
||||||
|
checkedExpected = options.checked
|
||||||
|
} else if (options.checked && options.checked !== 'consistent') {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' +
|
||||||
|
options.checked +
|
||||||
|
"` for `options.checked`, expected `'X'`, `'x'`, or `'consistent'`"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (unchecked !== 'consistent' && unchecked !== ' ' && unchecked !== '\t') {
|
if (options.unchecked === '\t' || options.unchecked === ' ') {
|
||||||
|
uncheckedExpected = options.unchecked
|
||||||
|
} else if (options.unchecked && options.unchecked !== 'consistent') {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' +
|
||||||
|
options.unchecked +
|
||||||
|
"` for `options.unchecked`, expected `'\\t'`, `' '`, or `'consistent'`"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
file.fail(
|
file.fail(
|
||||||
'Incorrect unchecked checkbox marker `' +
|
'Unexpected value `' +
|
||||||
unchecked +
|
options +
|
||||||
"`: use either `'\\t'`, or `' '`"
|
"` for `options`, expected an object or `'consistent'`"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checked !== 'consistent' && checked !== 'x' && checked !== 'X') {
|
visitParents(tree, 'listItem', function (node, parents) {
|
||||||
file.fail(
|
|
||||||
'Incorrect checked checkbox marker `' +
|
|
||||||
checked +
|
|
||||||
"`: use either `'x'`, or `'X'`"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(tree, 'listItem', function (node) {
|
|
||||||
const head = node.children[0]
|
const head = node.children[0]
|
||||||
const point = pointStart(head)
|
const headStart = pointStart(head)
|
||||||
|
|
||||||
// Exit early for items without checkbox.
|
// Exit early for items without checkbox.
|
||||||
// A list item cannot be checked and empty, according to GFM.
|
// A list item cannot be checked and empty, according to GFM.
|
||||||
if (
|
if (
|
||||||
!point ||
|
|
||||||
!head ||
|
!head ||
|
||||||
|
!headStart ||
|
||||||
typeof node.checked !== 'boolean' ||
|
typeof node.checked !== 'boolean' ||
|
||||||
typeof point.offset !== 'number'
|
typeof headStart.offset !== 'number'
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move back to before `] `.
|
// Move back to before `] `.
|
||||||
point.offset -= 2
|
headStart.offset -= 2
|
||||||
point.column -= 2
|
headStart.column -= 2
|
||||||
|
|
||||||
// Assume we start with a checkbox, because well, `checked` is set.
|
// Assume we start with a checkbox, because well, `checked` is set.
|
||||||
const match = /\[([\t Xx])]/.exec(
|
const match = /\[([\t Xx])]/.exec(
|
||||||
value.slice(point.offset - 2, point.offset + 1)
|
value.slice(headStart.offset - 2, headStart.offset + 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
/* c8 ignore next 2 -- failsafe so we don’t crash if there actually isn’t
|
/* c8 ignore next 2 -- failsafe so we don’t crash if there actually isn’t
|
||||||
* a checkbox. */
|
* a checkbox. */
|
||||||
if (!match) return
|
if (!match) return
|
||||||
|
|
||||||
const style = node.checked ? checked : unchecked
|
const actual = match[1]
|
||||||
|
const actualDisplay = actual === '\t' ? '\\t' : actual
|
||||||
|
const expected = node.checked ? checkedExpected : uncheckedExpected
|
||||||
|
const expectedDisplay = expected === '\t' ? '\\t' : expected
|
||||||
|
|
||||||
|
if (!expected) {
|
||||||
|
const cause = new VFileMessage(
|
||||||
|
(node.checked ? 'C' : 'Unc') +
|
||||||
|
"hecked checkbox style `'" +
|
||||||
|
actualDisplay +
|
||||||
|
"'` first defined for `'consistent'` here",
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
place: headStart,
|
||||||
|
ruleId: 'checkbox-character-style',
|
||||||
|
source: 'remark-lint'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (style === 'consistent') {
|
|
||||||
if (node.checked) {
|
if (node.checked) {
|
||||||
// @ts-expect-error: valid marker.
|
checkedExpected = /** @type {'X' | 'x'} */ (actual)
|
||||||
checked = match[1]
|
checkedConsistentCause = cause
|
||||||
} else {
|
} else {
|
||||||
// @ts-expect-error: valid marker.
|
uncheckedExpected = /** @type {'\t' | ' '} */ (actual)
|
||||||
unchecked = match[1]
|
uncheckedConsistentCause = cause
|
||||||
}
|
}
|
||||||
} else if (match[1] !== style) {
|
} else if (actual !== expected) {
|
||||||
file.message(
|
file.message(
|
||||||
(node.checked ? 'Checked' : 'Unchecked') +
|
'Unexpected ' +
|
||||||
' checkboxes should use `' +
|
(node.checked ? '' : 'un') +
|
||||||
style +
|
'checked checkbox value `' +
|
||||||
'` as a marker',
|
actualDisplay +
|
||||||
point
|
'`, expected `' +
|
||||||
|
expectedDisplay +
|
||||||
|
'`',
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
cause: node.checked
|
||||||
|
? checkedConsistentCause
|
||||||
|
: uncheckedConsistentCause,
|
||||||
|
place: headStart
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,8 @@
|
|||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -39,6 +39,8 @@ This package checks the character used in checkboxes.
|
|||||||
|
|
||||||
You can use this package to check that the style of GFM tasklists is
|
You can use this package to check that the style of GFM tasklists is
|
||||||
consistent.
|
consistent.
|
||||||
|
Task lists are a GFM feature enabled with
|
||||||
|
[`remark-gfm`][github-remark-gfm].
|
||||||
|
|
||||||
## Presets
|
## Presets
|
||||||
|
|
||||||
@ -176,7 +178,7 @@ using `'x'` (lowercase X) and unchecked checkboxes using `'␠'` (a space).
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-x.md`
|
||||||
|
|
||||||
When configured with `{ checked: 'x' }`.
|
When configured with `{ checked: 'x' }`.
|
||||||
|
|
||||||
@ -186,15 +188,15 @@ When configured with `{ checked: 'x' }`.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- [x] List item
|
- [x] Mercury.
|
||||||
- [x] List item
|
- [x] Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-x-upper.md`
|
||||||
|
|
||||||
When configured with `{ checked: 'X' }`.
|
When configured with `{ checked: 'X' }`.
|
||||||
|
|
||||||
@ -204,15 +206,15 @@ When configured with `{ checked: 'X' }`.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- [X] List item
|
- [X] Mercury.
|
||||||
- [X] List item
|
- [X] Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-space.md`
|
||||||
|
|
||||||
When configured with `{ unchecked: ' ' }`.
|
When configured with `{ unchecked: ' ' }`.
|
||||||
|
|
||||||
@ -222,8 +224,8 @@ When configured with `{ unchecked: ' ' }`.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- [ ] List item
|
- [ ] Mercury.
|
||||||
- [ ] List item
|
- [ ] Venus.
|
||||||
- [ ]␠␠
|
- [ ]␠␠
|
||||||
- [ ]
|
- [ ]
|
||||||
```
|
```
|
||||||
@ -232,7 +234,7 @@ When configured with `{ unchecked: ' ' }`.
|
|||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-tab.md`
|
||||||
|
|
||||||
When configured with `{ unchecked: '\t' }`.
|
When configured with `{ unchecked: '\t' }`.
|
||||||
|
|
||||||
@ -242,15 +244,15 @@ When configured with `{ unchecked: '\t' }`.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- [␉] List item
|
- [␉] Mercury.
|
||||||
- [␉] List item
|
- [␉] Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-default.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
@ -258,37 +260,47 @@ No messages.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- [x] List item
|
- [x] Mercury.
|
||||||
- [X] List item
|
- [X] Venus.
|
||||||
- [ ] List item
|
- [ ] Earth.
|
||||||
- [␉] List item
|
- [␉] Mars.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:5: Checked checkboxes should use `x` as a marker
|
2:5: Unexpected checked checkbox value `X`, expected `x`
|
||||||
4:5: Unchecked checkboxes should use ` ` as a marker
|
4:5: Unexpected unchecked checkbox value `\t`, expected ` `
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-option.md`
|
||||||
|
|
||||||
When configured with `{ unchecked: '💩' }`.
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect unchecked checkbox marker `💩`: use either `'\t'`, or `' '`
|
1:1: Unexpected value `🌍` for `options`, expected an object or `'consistent'`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-option-unchecked.md`
|
||||||
|
|
||||||
When configured with `{ checked: '💩' }`.
|
When configured with `{ unchecked: '🌍' }`.
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect checked checkbox marker `💩`: use either `'x'`, or `'X'`
|
1:1: Unexpected value `🌍` for `options.unchecked`, expected `'\t'`, `' '`, or `'consistent'`
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok-option-checked.md`
|
||||||
|
|
||||||
|
When configured with `{ checked: '🌍' }`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options.checked`, expected `'X'`, `'x'`, or `'consistent'`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -55,38 +55,48 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "gfm": true}
|
|
||||||
*
|
|
||||||
* - [ ] List item
|
|
||||||
* + [x] List Item
|
|
||||||
* * [X] List item
|
|
||||||
* - [ ] List item
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input", "gfm": true}
|
* {"gfm": true, "name": "ok.md"}
|
||||||
*
|
*
|
||||||
* - [ ] List item
|
* - [ ] Mercury.
|
||||||
* + [x] List item
|
* + [x] Venus.
|
||||||
* * [X] List item
|
* * [X] Earth.
|
||||||
* - [ ] List item
|
* - [ ] Mars.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output", "gfm": true}
|
* {"gfm": true, "label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 2:7-2:8: Checkboxes should be followed by a single character
|
* - [ ] Mercury.
|
||||||
* 3:7-3:9: Checkboxes should be followed by a single character
|
* + [x] Venus.
|
||||||
* 4:7-4:10: Checkboxes should be followed by a single character
|
* * [X] Earth.
|
||||||
|
* - [ ] Mars.
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 2:8: Unexpected `2` spaces between checkbox and content, expected `1` space, remove `1` space
|
||||||
|
* 3:9: Unexpected `3` spaces between checkbox and content, expected `1` space, remove `2` spaces
|
||||||
|
* 4:10: Unexpected `4` spaces between checkbox and content, expected `1` space, remove `3` spaces
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "input", "name": "tab.md"}
|
||||||
|
*
|
||||||
|
* - [ ]␉Mercury.
|
||||||
|
* + [x]␉␉Venus.
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "output", "name": "tab.md"}
|
||||||
|
*
|
||||||
|
* 2:8: Unexpected `2` spaces between checkbox and content, expected `1` space, remove `1` space
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {location} from 'vfile-location'
|
|
||||||
|
|
||||||
const remarkLintCheckboxContentIndent = lintRule(
|
const remarkLintCheckboxContentIndent = lintRule(
|
||||||
{
|
{
|
||||||
@ -101,45 +111,59 @@ const remarkLintCheckboxContentIndent = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
const loc = location(file)
|
|
||||||
|
|
||||||
visit(tree, 'listItem', function (node) {
|
visitParents(tree, 'listItem', function (node, parents) {
|
||||||
const head = node.children[0]
|
const head = node.children[0]
|
||||||
const point = pointStart(head)
|
const headStart = pointStart(head)
|
||||||
|
|
||||||
// Exit early for items without checkbox.
|
// Exit early for items without checkbox.
|
||||||
// A list item cannot be checked and empty, according to GFM.
|
// A list item cannot be checked and empty according to GFM.
|
||||||
if (
|
if (
|
||||||
!point ||
|
|
||||||
!head ||
|
!head ||
|
||||||
|
!headStart ||
|
||||||
typeof node.checked !== 'boolean' ||
|
typeof node.checked !== 'boolean' ||
|
||||||
typeof point.offset !== 'number'
|
typeof headStart.offset !== 'number'
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume we start with a checkbox, because well, `checked` is set.
|
// Assume we start with a checkbox as `checked` is set.
|
||||||
const match = /\[([\t xX])]/.exec(
|
const match = /\[([\t xX])]/.exec(
|
||||||
value.slice(point.offset - 4, point.offset + 1)
|
value.slice(headStart.offset - 4, headStart.offset + 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
/* c8 ignore next -- make sure we don’t crash if there actually isn’t a checkbox. */
|
/* c8 ignore next -- make sure we don’t crash if there actually isn’t a checkbox. */
|
||||||
if (!match) return
|
if (!match) return
|
||||||
|
|
||||||
// Move past checkbox.
|
// Move past checkbox.
|
||||||
const initial = point.offset
|
let final = headStart.offset
|
||||||
let final = initial
|
let code = value.charCodeAt(final)
|
||||||
|
|
||||||
while (/[\t ]/.test(value.charAt(final))) final++
|
while (code === 9 || code === 32) {
|
||||||
|
final++
|
||||||
|
code = value.charCodeAt(final)
|
||||||
|
}
|
||||||
|
|
||||||
if (final - initial > 0) {
|
const size = final - headStart.offset
|
||||||
const start = loc.toPoint(initial)
|
|
||||||
const end = loc.toPoint(final)
|
|
||||||
|
|
||||||
|
if (size) {
|
||||||
file.message(
|
file.message(
|
||||||
'Checkboxes should be followed by a single character',
|
'Unexpected `' +
|
||||||
/* c8 ignore next -- we get here if we have offsets. */
|
(size + 1) +
|
||||||
start && end ? {start, end} : undefined
|
'` ' +
|
||||||
|
pluralize('space', size + 1) +
|
||||||
|
' between checkbox and content, expected `1` space, remove `' +
|
||||||
|
size +
|
||||||
|
'` ' +
|
||||||
|
pluralize('space', size),
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
place: {
|
||||||
|
line: headStart.line,
|
||||||
|
column: headStart.column + size,
|
||||||
|
offset: headStart.offset + size
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -34,10 +34,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
"vfile-location": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
@ -49,7 +49,8 @@
|
|||||||
"xo": {
|
"xo": {
|
||||||
"prettier": true,
|
"prettier": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"capitalized-comments": "off"
|
"capitalized-comments": "off",
|
||||||
|
"unicorn/prefer-code-point": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,10 +163,10 @@ content after them with a single space between.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- [ ] List item
|
- [ ] Mercury.
|
||||||
+ [x] List Item
|
+ [x] Venus.
|
||||||
* [X] List item
|
* [X] Earth.
|
||||||
- [ ] List item
|
- [ ] Mars.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -181,18 +181,36 @@ No messages.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- [ ] List item
|
- [ ] Mercury.
|
||||||
+ [x] List item
|
+ [x] Venus.
|
||||||
* [X] List item
|
* [X] Earth.
|
||||||
- [ ] List item
|
- [ ] Mars.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:7-2:8: Checkboxes should be followed by a single character
|
2:8: Unexpected `2` spaces between checkbox and content, expected `1` space, remove `1` space
|
||||||
3:7-3:9: Checkboxes should be followed by a single character
|
3:9: Unexpected `3` spaces between checkbox and content, expected `1` space, remove `2` spaces
|
||||||
4:7-4:10: Checkboxes should be followed by a single character
|
4:10: Unexpected `4` spaces between checkbox and content, expected `1` space, remove `3` spaces
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `tab.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [ ]␉Mercury.
|
||||||
|
+ [x]␉␉Venus.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
2:8: Unexpected `2` spaces between checkbox and content, expected `1` space, remove `1` space
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -75,81 +75,79 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "indented", "name": "ok.md"}
|
* {"config": "indented", "name": "ok-indented.md"}
|
||||||
*
|
*
|
||||||
* alpha()
|
* venus()
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Mercury.
|
||||||
*
|
*
|
||||||
* bravo()
|
* earth()
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "indented", "name": "not-ok.md", "label": "input"}
|
* {"config": "fenced", "name": "ok-fenced.md"}
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* alpha()
|
* venus()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Mercury.
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* bravo()
|
* earth()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "indented", "name": "not-ok.md", "label": "output"}
|
* {"label": "input", "name": "not-ok-consistent.md"}
|
||||||
*
|
*
|
||||||
* 1:1-3:4: Code blocks should be indented
|
* venus()
|
||||||
* 7:1-9:4: Code blocks should be indented
|
|
||||||
*
|
*
|
||||||
* @example
|
* Mercury.
|
||||||
* {"config": "fenced", "name": "ok.md"}
|
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* alpha()
|
* earth()
|
||||||
* ```
|
* ```
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-consistent.md"}
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* 5:1-7:4: Unexpected fenced code block, expected indented code blocks
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* bravo()
|
|
||||||
* ```
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "fenced", "name": "not-ok-fenced.md", "label": "input"}
|
* {"config": "indented", "label": "input", "name": "not-ok-indented.md"}
|
||||||
*
|
|
||||||
* alpha()
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* bravo()
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"config": "fenced", "name": "not-ok-fenced.md", "label": "output"}
|
|
||||||
*
|
|
||||||
* 1:1-1:12: Code blocks should be fenced
|
|
||||||
* 5:1-5:12: Code blocks should be fenced
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok-consistent.md", "label": "input"}
|
|
||||||
*
|
|
||||||
* alpha()
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* bravo()
|
* venus()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* Mercury.
|
||||||
* {"name": "not-ok-consistent.md", "label": "output"}
|
|
||||||
*
|
*
|
||||||
* 5:1-7:4: Code blocks should be indented
|
* ```
|
||||||
|
* earth()
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* {"config": "indented", "label": "output", "name": "not-ok-indented.md"}
|
||||||
|
*
|
||||||
|
* 1:1-3:4: Unexpected fenced code block, expected indented code blocks
|
||||||
|
* 7:1-9:4: Unexpected fenced code block, expected indented code blocks
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "💩", "name": "not-ok-incorrect.md", "label": "output", "positionless": true}
|
* {"config": "fenced", "label": "input", "name": "not-ok-fenced.md"}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect code block style `💩`: use either `'consistent'`, `'fenced'`, or `'indented'`
|
* venus()
|
||||||
|
*
|
||||||
|
* Mercury.
|
||||||
|
*
|
||||||
|
* earth()
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "fenced", "label": "output", "name": "not-ok-fenced.md"}
|
||||||
|
*
|
||||||
|
* 1:1-1:12: Unexpected indented code block, expected fenced code blocks
|
||||||
|
* 5:1-5:12: Unexpected indented code block, expected fenced code blocks
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "🌍", "label": "output", "name": "not-ok-options.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:1: Unexpected value `🌍` for `options`, expected `'fenced'`, `'indented'`, or `'consistent'`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,7 +164,8 @@
|
|||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd, pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const remarkLintCodeBlockStyle = lintRule(
|
const remarkLintCodeBlockStyle = lintRule(
|
||||||
{
|
{
|
||||||
@ -182,22 +181,25 @@ const remarkLintCodeBlockStyle = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
let option = options || 'consistent'
|
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
|
/** @type {VFileMessage | undefined} */
|
||||||
|
let cause
|
||||||
|
/** @type {Style | undefined} */
|
||||||
|
let expected
|
||||||
|
|
||||||
if (
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
option !== 'consistent' &&
|
// Empty.
|
||||||
option !== 'indented' &&
|
} else if (options === 'indented' || options === 'fenced') {
|
||||||
option !== 'fenced'
|
expected = options
|
||||||
) {
|
} else {
|
||||||
file.fail(
|
file.fail(
|
||||||
'Incorrect code block style `' +
|
'Unexpected value `' +
|
||||||
option +
|
options +
|
||||||
"`: use either `'consistent'`, `'fenced'`, or `'indented'`"
|
"` for `options`, expected `'fenced'`, `'indented'`, or `'consistent'`"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(tree, 'code', function (node) {
|
visitParents(tree, 'code', function (node, parents) {
|
||||||
const end = pointEnd(node)
|
const end = pointEnd(node)
|
||||||
const start = pointStart(node)
|
const start = pointStart(node)
|
||||||
|
|
||||||
@ -210,16 +212,35 @@ const remarkLintCodeBlockStyle = lintRule(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const current =
|
const actual =
|
||||||
node.lang ||
|
node.lang || /^ {0,3}([`~])/.test(value.slice(start.offset, end.offset))
|
||||||
/^\s*([~`])\1{2,}/.test(value.slice(start.offset, end.offset))
|
|
||||||
? 'fenced'
|
? 'fenced'
|
||||||
: 'indented'
|
: 'indented'
|
||||||
|
|
||||||
if (option === 'consistent') {
|
if (expected) {
|
||||||
option = current
|
if (expected !== actual) {
|
||||||
} else if (option !== current) {
|
file.message(
|
||||||
file.message('Code blocks should be ' + option, node)
|
'Unexpected ' +
|
||||||
|
actual +
|
||||||
|
' code block, expected ' +
|
||||||
|
expected +
|
||||||
|
' code blocks',
|
||||||
|
{ancestors: [...parents, node], cause, place: {start, end}}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expected = actual
|
||||||
|
cause = new VFileMessage(
|
||||||
|
"Code block style `'" +
|
||||||
|
actual +
|
||||||
|
"'` first defined for `'consistent'` here",
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
place: {start, end},
|
||||||
|
source: 'remark-lint',
|
||||||
|
ruleId: 'code-block-style'
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,8 @@
|
|||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -180,50 +180,25 @@ language and as indented code otherwise.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-indented.md`
|
||||||
|
|
||||||
When configured with `'indented'`.
|
When configured with `'indented'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
alpha()
|
venus()
|
||||||
|
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
bravo()
|
earth()
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `ok-fenced.md`
|
||||||
|
|
||||||
When configured with `'indented'`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
```
|
|
||||||
alpha()
|
|
||||||
```
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
|
|
||||||
```
|
|
||||||
bravo()
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:1-3:4: Code blocks should be indented
|
|
||||||
7:1-9:4: Code blocks should be indented
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `ok.md`
|
|
||||||
|
|
||||||
When configured with `'fenced'`.
|
When configured with `'fenced'`.
|
||||||
|
|
||||||
@ -231,13 +206,13 @@ When configured with `'fenced'`.
|
|||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```
|
```
|
||||||
alpha()
|
venus()
|
||||||
```
|
```
|
||||||
|
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
```
|
```
|
||||||
bravo()
|
earth()
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
@ -245,6 +220,51 @@ bravo()
|
|||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
|
##### `not-ok-consistent.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
venus()
|
||||||
|
|
||||||
|
Mercury.
|
||||||
|
|
||||||
|
```
|
||||||
|
earth()
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
5:1-7:4: Unexpected fenced code block, expected indented code blocks
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok-indented.md`
|
||||||
|
|
||||||
|
When configured with `'indented'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```
|
||||||
|
venus()
|
||||||
|
```
|
||||||
|
|
||||||
|
Mercury.
|
||||||
|
|
||||||
|
```
|
||||||
|
earth()
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1-3:4: Unexpected fenced code block, expected indented code blocks
|
||||||
|
7:1-9:4: Unexpected fenced code block, expected indented code blocks
|
||||||
|
```
|
||||||
|
|
||||||
##### `not-ok-fenced.md`
|
##### `not-ok-fenced.md`
|
||||||
|
|
||||||
When configured with `'fenced'`.
|
When configured with `'fenced'`.
|
||||||
@ -252,48 +272,28 @@ When configured with `'fenced'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
alpha()
|
venus()
|
||||||
|
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
bravo()
|
earth()
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:12: Code blocks should be fenced
|
1:1-1:12: Unexpected indented code block, expected fenced code blocks
|
||||||
5:1-5:12: Code blocks should be fenced
|
5:1-5:12: Unexpected indented code block, expected fenced code blocks
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok-consistent.md`
|
##### `not-ok-options.md`
|
||||||
|
|
||||||
###### In
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
````markdown
|
|
||||||
alpha()
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
|
|
||||||
```
|
|
||||||
bravo()
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5:1-7:4: Code blocks should be indented
|
1:1: Unexpected value `🌍` for `options`, expected `'fenced'`, `'indented'`, or `'consistent'`
|
||||||
```
|
|
||||||
|
|
||||||
##### `not-ok-incorrect.md`
|
|
||||||
|
|
||||||
When configured with `'💩'`.
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:1: Incorrect code block style `💩`: use either `'consistent'`, `'fenced'`, or `'indented'`
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -37,30 +37,31 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* [example]: http://example.com "Example Domain"
|
* [mercury]: http://example.com "Mercury"
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
|
||||||
* [Example]: http://example.com "Example Domain"
|
|
||||||
*
|
*
|
||||||
|
* [Mercury]: http://example.com "Mercury"
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:47: Do not use uppercase characters in definition labels
|
* 1:1-1:40: Unexpected uppercase characters in definition label, expected lowercase
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"gfm": true, "label": "input", "name": "gfm.md"}
|
* {"gfm": true, "label": "input", "name": "gfm.md"}
|
||||||
*
|
*
|
||||||
* [^X]: Footnote definitions (from GFM) are checked too.
|
* [^Mercury]:
|
||||||
*
|
* **Mercury** is the first planet from the Sun and the smallest
|
||||||
|
* in the Solar System.
|
||||||
* @example
|
* @example
|
||||||
* {"gfm": true, "label": "output", "name": "gfm.md"}
|
* {"gfm": true, "label": "output", "name": "gfm.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:55: Do not use uppercase characters in definition labels
|
* 1:1-3:25: Unexpected uppercase characters in footnote definition label, expected lowercase
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,10 +69,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const label = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/
|
|
||||||
|
|
||||||
const remarkLintDefinitionCase = lintRule(
|
const remarkLintDefinitionCase = lintRule(
|
||||||
{
|
{
|
||||||
@ -85,28 +83,19 @@ const remarkLintDefinitionCase = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
const value = String(file)
|
visitParents(tree, function (node, parents) {
|
||||||
|
if (
|
||||||
visit(tree, function (node) {
|
(node.type === 'definition' || node.type === 'footnoteDefinition') &&
|
||||||
if (node.type === 'definition' || node.type === 'footnoteDefinition') {
|
node.position &&
|
||||||
const end = pointEnd(node)
|
node.label &&
|
||||||
const start = pointStart(node)
|
node.label !== node.label.toLowerCase()
|
||||||
|
) {
|
||||||
if (
|
file.message(
|
||||||
end &&
|
'Unexpected uppercase characters in ' +
|
||||||
start &&
|
(node.type === 'definition' ? '' : 'footnote ') +
|
||||||
typeof end.offset === 'number' &&
|
'definition label, expected lowercase',
|
||||||
typeof start.offset === 'number'
|
{ancestors: [...parents, node], place: node.position}
|
||||||
) {
|
)
|
||||||
const match = value.slice(start.offset, end.offset).match(label)
|
|
||||||
|
|
||||||
if (match && match[1] !== match[1].toLowerCase()) {
|
|
||||||
file.message(
|
|
||||||
'Do not use uppercase characters in definition labels',
|
|
||||||
node
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -146,7 +146,7 @@ Due to this, it’s recommended to use lowercase and turn this rule on.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[example]: http://example.com "Example Domain"
|
[mercury]: http://example.com "Mercury"
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -158,13 +158,13 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example]: http://example.com "Example Domain"
|
[Mercury]: http://example.com "Mercury"
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:47: Do not use uppercase characters in definition labels
|
1:1-1:40: Unexpected uppercase characters in definition label, expected lowercase
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `gfm.md`
|
##### `gfm.md`
|
||||||
@ -175,13 +175,15 @@ No messages.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[^X]: Footnote definitions (from GFM) are checked too.
|
[^Mercury]:
|
||||||
|
**Mercury** is the first planet from the Sun and the smallest
|
||||||
|
in the Solar System.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:55: Do not use uppercase characters in definition labels
|
1:1-3:25: Unexpected uppercase characters in footnote definition label, expected lowercase
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -41,31 +41,41 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* [example domain]: http://example.com "Example Domain"
|
* [planet mercury]: http://example.com
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok-consecutive.md"}
|
||||||
*
|
*
|
||||||
* [example␠␠␠␠domain]: http://example.com "Example Domain"
|
* [planet␠␠␠␠mercury]: http://example.com
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-consecutive.md"}
|
||||||
|
*
|
||||||
|
* 1:1-1:40: Unexpected `4` consecutive spaces in definition label, expected `1` space, remove `3` spaces
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "input", "name": "not-ok-non-space.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:57: Do not use consecutive whitespace in definition labels
|
* [pla␉net␊mer␍cury]: http://e.com
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-non-space.md"}
|
||||||
|
*
|
||||||
|
* 1:1-3:20: Unexpected non-space whitespace character `\t` in definition label, expected `1` space, replace it
|
||||||
|
* 1:1-3:20: Unexpected non-space whitespace character `\n` in definition label, expected `1` space, replace it
|
||||||
|
* 1:1-3:20: Unexpected non-space whitespace character `\r` in definition label, expected `1` space, replace it
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {longestStreak} from 'longest-streak'
|
||||||
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart, pointEnd} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const label = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/
|
|
||||||
|
|
||||||
const remarkLintDefinitionSpacing = lintRule(
|
const remarkLintDefinitionSpacing = lintRule(
|
||||||
{
|
{
|
||||||
@ -79,27 +89,35 @@ const remarkLintDefinitionSpacing = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
const value = String(file)
|
visitParents(tree, function (node, parents) {
|
||||||
|
if (node.type === 'definition' && node.position && node.label) {
|
||||||
|
const size = longestStreak(node.label, ' ')
|
||||||
|
|
||||||
visit(tree, function (node) {
|
if (size > 1) {
|
||||||
if (node.type === 'definition') {
|
file.message(
|
||||||
const end = pointEnd(node)
|
'Unexpected `' +
|
||||||
const start = pointStart(node)
|
size +
|
||||||
|
'` consecutive spaces in definition label, expected `1` space, remove `' +
|
||||||
|
(size - 1) +
|
||||||
|
'` ' +
|
||||||
|
pluralize('space', size - 1),
|
||||||
|
{ancestors: [...parents, node], place: node.position}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
/** @type {Array<string>} */
|
||||||
end &&
|
const disallowed = []
|
||||||
start &&
|
if (node.label.includes('\t')) disallowed.push('\\t')
|
||||||
typeof end.offset === 'number' &&
|
if (node.label.includes('\n')) disallowed.push('\\n')
|
||||||
typeof start.offset === 'number'
|
if (node.label.includes('\r')) disallowed.push('\\r')
|
||||||
) {
|
|
||||||
const match = value.slice(start.offset, end.offset).match(label)
|
|
||||||
|
|
||||||
if (match && /[ \t\n]{2,}/.test(match[1])) {
|
for (const disallow of disallowed) {
|
||||||
file.message(
|
file.message(
|
||||||
'Do not use consecutive whitespace in definition labels',
|
'Unexpected non-space whitespace character `' +
|
||||||
node
|
disallow +
|
||||||
)
|
'` in definition label, expected `1` space, replace it',
|
||||||
}
|
{ancestors: [...parents, node], place: node.position}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -32,9 +32,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"longest-streak": "^3.0.0",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -150,25 +150,41 @@ Due to this, it’s recommended to use one space and turn this rule on.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[example domain]: http://example.com "Example Domain"
|
[planet mercury]: http://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-consecutive.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[example␠␠␠␠domain]: http://example.com "Example Domain"
|
[planet␠␠␠␠mercury]: http://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:57: Do not use consecutive whitespace in definition labels
|
1:1-1:40: Unexpected `4` consecutive spaces in definition label, expected `1` space, remove `3` spaces
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok-non-space.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[pla␉net␊mer␍cury]: http://e.com
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1-3:20: Unexpected non-space whitespace character `\t` in definition label, expected `1` space, replace it
|
||||||
|
1:1-3:20: Unexpected non-space whitespace character `\n` in definition label, expected `1` space, replace it
|
||||||
|
1:1-3:20: Unexpected non-space whitespace character `\r` in definition label, expected `1` space, replace it
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -77,51 +77,51 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"config": "*", "name": "ok.md"}
|
|
||||||
*
|
|
||||||
* *foo*
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "*", "name": "not-ok.md", "label": "input"}
|
* {"config": "*", "name": "ok-asterisk.md"}
|
||||||
*
|
*
|
||||||
* _foo_
|
* *Mercury*.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "*", "name": "not-ok.md", "label": "output"}
|
* {"config": "*", "label": "input", "name": "not-ok-asterisk.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:6: Emphasis should use `*` as a marker
|
* _Mercury_.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "_", "name": "ok.md"}
|
* {"config": "*", "label": "output", "name": "not-ok-asterisk.md"}
|
||||||
*
|
*
|
||||||
* _foo_
|
* 1:1-1:10: Unexpected emphasis marker `_`, expected `*`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "_", "name": "not-ok.md", "label": "input"}
|
* {"config": "_", "name": "ok-underscore.md"}
|
||||||
*
|
*
|
||||||
* *foo*
|
* _Mercury_.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "_", "name": "not-ok.md", "label": "output"}
|
* {"config": "_", "label": "input", "name": "not-ok-underscore.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:6: Emphasis should use `_` as a marker
|
* *Mercury*.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"config": "_", "label": "output", "name": "not-ok-underscore.md"}
|
||||||
*
|
*
|
||||||
* *foo*
|
* 1:1-1:10: Unexpected emphasis marker `*`, expected `_`
|
||||||
* _bar_
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "input", "name": "not-ok-consistent.md"}
|
||||||
*
|
*
|
||||||
* 2:1-2:6: Emphasis should use `*` as a marker
|
* *Mercury* and _Venus_.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "💩", "name": "not-ok.md", "label": "output", "positionless": true}
|
* {"label": "output", "name": "not-ok-consistent.md"}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect emphasis marker `💩`: use either `'consistent'`, `'*'`, or `'_'`
|
* 1:15-1:22: Unexpected emphasis marker `_`, expected `*`
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "🌍", "label": "output", "name": "not-ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:1: Unexpected value `🌍` for `options`, expected `'*'`, `'_'`, or `'consistent'`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -138,7 +138,8 @@
|
|||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const remarkLintEmphasisMarker = lintRule(
|
const remarkLintEmphasisMarker = lintRule(
|
||||||
{
|
{
|
||||||
@ -155,26 +156,56 @@ const remarkLintEmphasisMarker = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
let option = options || 'consistent'
|
/** @type {VFileMessage | undefined} */
|
||||||
|
let cause
|
||||||
|
/** @type {Marker | undefined} */
|
||||||
|
let expected
|
||||||
|
|
||||||
if (option !== '*' && option !== '_' && option !== 'consistent') {
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
|
// Empty.
|
||||||
|
} else if (options === '*' || options === '_') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
file.fail(
|
file.fail(
|
||||||
'Incorrect emphasis marker `' +
|
'Unexpected value `' +
|
||||||
option +
|
options +
|
||||||
"`: use either `'consistent'`, `'*'`, or `'_'`"
|
"` for `options`, expected `'*'`, `'_'`, or `'consistent'`"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(tree, 'emphasis', function (node) {
|
visitParents(tree, 'emphasis', function (node, parents) {
|
||||||
const start = pointStart(node)
|
const start = pointStart(node)
|
||||||
|
|
||||||
if (start && typeof start.offset === 'number') {
|
if (start && typeof start.offset === 'number') {
|
||||||
const marker = /** @type {Marker} */ (value.charAt(start.offset))
|
const actual = value.charAt(start.offset)
|
||||||
|
|
||||||
if (option === 'consistent') {
|
/* c8 ignore next -- should not happen. */
|
||||||
option = marker
|
if (actual !== '*' && actual !== '_') return
|
||||||
} else if (marker !== option) {
|
|
||||||
file.message('Emphasis should use `' + option + '` as a marker', node)
|
if (expected) {
|
||||||
|
if (actual !== expected) {
|
||||||
|
file.message(
|
||||||
|
'Unexpected emphasis marker `' +
|
||||||
|
actual +
|
||||||
|
'`, expected `' +
|
||||||
|
expected +
|
||||||
|
'`',
|
||||||
|
{ancestors: [...parents, node], cause, place: node.position}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expected = actual
|
||||||
|
cause = new VFileMessage(
|
||||||
|
"Emphasis marker style `'" +
|
||||||
|
actual +
|
||||||
|
"'` first defined for `'consistent'` here",
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
place: node.position,
|
||||||
|
ruleId: 'emphasis-marker',
|
||||||
|
source: 'remark-lint'
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -34,7 +34,8 @@
|
|||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -184,89 +184,88 @@ Pass `emphasis: '_'` to always use underscores.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-asterisk.md`
|
||||||
|
|
||||||
When configured with `'*'`.
|
When configured with `'*'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
*foo*
|
*Mercury*.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-asterisk.md`
|
||||||
|
|
||||||
When configured with `'*'`.
|
When configured with `'*'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
_foo_
|
_Mercury_.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:6: Emphasis should use `*` as a marker
|
1:1-1:10: Unexpected emphasis marker `_`, expected `*`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-underscore.md`
|
||||||
|
|
||||||
When configured with `'_'`.
|
When configured with `'_'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
_foo_
|
_Mercury_.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-underscore.md`
|
||||||
|
|
||||||
When configured with `'_'`.
|
When configured with `'_'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
*foo*
|
*Mercury*.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:6: Emphasis should use `_` as a marker
|
1:1-1:10: Unexpected emphasis marker `*`, expected `_`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-consistent.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
*foo*
|
*Mercury* and _Venus_.
|
||||||
_bar_
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:1-2:6: Emphasis should use `*` as a marker
|
1:15-1:22: Unexpected emphasis marker `_`, expected `*`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
When configured with `'💩'`.
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect emphasis marker `💩`: use either `'consistent'`, `'*'`, or `'_'`
|
1:1: Unexpected value `🌍` for `options`, expected `'*'`, `'_'`, or `'consistent'`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -59,66 +59,79 @@
|
|||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* ```alpha
|
* ```markdown
|
||||||
* bravo()
|
* # Mercury
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* alpha()
|
* mercury()
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 1:1-3:4: Unexpected missing fenced code language flag in info string, expected keyword
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": {"allowEmpty": true}, "name": "ok-allow-empty.md"}
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* mercury()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"config": {"allowEmpty": false}, "label": "input", "name": "not-ok-allow-empty.md"}
|
||||||
*
|
|
||||||
* 1:1-3:4: Missing code language flag
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": {"allowEmpty": true}}
|
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* alpha()
|
* mercury()
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* {"config": {"allowEmpty": false}, "label": "output", "name": "not-ok-allow-empty.md"}
|
||||||
|
*
|
||||||
|
* 1:1-3:4: Unexpected missing fenced code language flag in info string, expected keyword
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": ["markdown"], "name": "ok-array.md"}
|
||||||
|
*
|
||||||
|
* ```markdown
|
||||||
|
* # Mercury
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": {"allowEmpty": false}, "label": "input"}
|
* {"config": {"flags":["markdown"]}, "name": "ok-options.md"}
|
||||||
*
|
*
|
||||||
* ```
|
* ```markdown
|
||||||
* alpha()
|
* # Mercury
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": {"allowEmpty": false}, "label": "output"}
|
* {"config": ["markdown"], "label": "input", "name": "not-ok-array.md"}
|
||||||
*
|
*
|
||||||
* 1:1-3:4: Missing code language flag
|
* ```javascript
|
||||||
*
|
* mercury()
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": ["alpha"]}
|
|
||||||
*
|
|
||||||
* ```alpha
|
|
||||||
* bravo()
|
|
||||||
* ```
|
* ```
|
||||||
|
* @example
|
||||||
|
* {"config": ["markdown"], "label": "output", "name": "not-ok-array.md"}
|
||||||
|
*
|
||||||
|
* 1:1-3:4: Unexpected fenced code language flag `javascript` in info string, expected `markdown`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": {"flags":["alpha"]}}
|
* {"config": ["javascript", "markdown", "mdx", "typescript"], "label": "input", "name": "not-ok-long-array.md"}
|
||||||
*
|
*
|
||||||
* ```alpha
|
* ```html
|
||||||
* bravo()
|
* <h1>Mercury</h1>
|
||||||
* ```
|
* ```
|
||||||
|
* @example
|
||||||
|
* {"config": ["javascript", "markdown", "mdx", "typescript"], "label": "output", "name": "not-ok-long-array.md"}
|
||||||
|
*
|
||||||
|
* 1:1-3:4: Unexpected fenced code language flag `html` in info string, expected `javascript`, `markdown`, `mdx`, …
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": ["charlie"], "label": "input"}
|
* {"config": "🌍", "label": "output", "name": "not-ok-options.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* ```alpha
|
* 1:1: Unexpected value `🌍` for `options`, expected array or object
|
||||||
* bravo()
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok.md", "config": ["charlie"], "label": "output"}
|
|
||||||
*
|
|
||||||
* 1:1-3:4: Incorrect code language flag
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,13 +148,15 @@
|
|||||||
* other flags will result in a warning (optional).
|
* other flags will result in a warning (optional).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {quotation} from 'quotation'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd, pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
|
||||||
const fence = /^ {0,3}([~`])\1{2,}/
|
const fence = /^ {0,3}([~`])\1{2,}/
|
||||||
/** @type {ReadonlyArray<string>} */
|
|
||||||
const emptyFlags = []
|
const listFormat = new Intl.ListFormat('en', {type: 'disjunction'})
|
||||||
|
const listFormatUnit = new Intl.ListFormat('en', {type: 'unit'})
|
||||||
|
|
||||||
const remarkLintFencedCodeFlag = lintRule(
|
const remarkLintFencedCodeFlag = lintRule(
|
||||||
{
|
{
|
||||||
@ -159,24 +174,45 @@ const remarkLintFencedCodeFlag = lintRule(
|
|||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
let allowEmpty = false
|
let allowEmpty = false
|
||||||
let allowed = emptyFlags
|
/** @type {ReadonlyArray<string> | undefined} */
|
||||||
|
let allowed
|
||||||
|
|
||||||
if (options && typeof options === 'object') {
|
if (options === null || options === undefined) {
|
||||||
|
// Empty.
|
||||||
|
} else if (typeof options === 'object') {
|
||||||
// Note: casts because `isArray` and `readonly` don’t mix.
|
// Note: casts because `isArray` and `readonly` don’t mix.
|
||||||
if (Array.isArray(options)) {
|
if (Array.isArray(options)) {
|
||||||
const flags = /** @type {ReadonlyArray<string>} */ (options)
|
const flags = /** @type {ReadonlyArray<string>} */ (options)
|
||||||
allowed = flags
|
allowed = flags
|
||||||
} else {
|
} else {
|
||||||
const settings = /** @type {Options} */ (options)
|
const settings = /** @type {Options} */ (options)
|
||||||
allowEmpty = Boolean(settings.allowEmpty)
|
allowEmpty = settings.allowEmpty === true
|
||||||
|
|
||||||
if (settings.flags) {
|
if (settings.flags) {
|
||||||
allowed = settings.flags
|
allowed = settings.flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' +
|
||||||
|
options +
|
||||||
|
'` for `options`, expected array or object'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(tree, 'code', function (node) {
|
/** @type {string} */
|
||||||
|
let allowedDisplay
|
||||||
|
|
||||||
|
if (allowed) {
|
||||||
|
allowedDisplay =
|
||||||
|
allowed.length > 3
|
||||||
|
? listFormatUnit.format([...quotation(allowed.slice(0, 3), '`'), '…'])
|
||||||
|
: listFormat.format(quotation(allowed, '`'))
|
||||||
|
} else {
|
||||||
|
allowedDisplay = 'keyword'
|
||||||
|
}
|
||||||
|
|
||||||
|
visitParents(tree, 'code', function (node, parents) {
|
||||||
const end = pointEnd(node)
|
const end = pointEnd(node)
|
||||||
const start = pointStart(node)
|
const start = pointStart(node)
|
||||||
|
|
||||||
@ -187,14 +223,24 @@ const remarkLintFencedCodeFlag = lintRule(
|
|||||||
typeof start.offset === 'number'
|
typeof start.offset === 'number'
|
||||||
) {
|
) {
|
||||||
if (node.lang) {
|
if (node.lang) {
|
||||||
if (allowed.length > 0 && !allowed.includes(node.lang)) {
|
if (allowed && !allowed.includes(node.lang)) {
|
||||||
file.message('Incorrect code language flag', node)
|
file.message(
|
||||||
|
'Unexpected fenced code language flag `' +
|
||||||
|
node.lang +
|
||||||
|
'` in info string, expected ' +
|
||||||
|
allowedDisplay,
|
||||||
|
{ancestors: [...parents, node], place: node.position}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!allowEmpty) {
|
||||||
const slice = value.slice(start.offset, end.offset)
|
const slice = value.slice(start.offset, end.offset)
|
||||||
|
|
||||||
if (!allowEmpty && fence.test(slice)) {
|
if (fence.test(slice)) {
|
||||||
file.message('Missing code language flag', node)
|
file.message(
|
||||||
|
'Unexpected missing fenced code language flag in info string, expected ' +
|
||||||
|
allowedDisplay,
|
||||||
|
{ancestors: [...parents, node], place: node.position}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"quotation": "^2.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -165,8 +165,8 @@ It’s recommended to instead use a certain flag for plain text (such as
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```alpha
|
```markdown
|
||||||
bravo()
|
# Mercury
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
@ -180,17 +180,17 @@ No messages.
|
|||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```
|
```
|
||||||
alpha()
|
mercury()
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-3:4: Missing code language flag
|
1:1-3:4: Unexpected missing fenced code language flag in info string, expected keyword
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-allow-empty.md`
|
||||||
|
|
||||||
When configured with `{ allowEmpty: true }`.
|
When configured with `{ allowEmpty: true }`.
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ When configured with `{ allowEmpty: true }`.
|
|||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```
|
```
|
||||||
alpha()
|
mercury()
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ alpha()
|
|||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-allow-empty.md`
|
||||||
|
|
||||||
When configured with `{ allowEmpty: false }`.
|
When configured with `{ allowEmpty: false }`.
|
||||||
|
|
||||||
@ -214,25 +214,25 @@ When configured with `{ allowEmpty: false }`.
|
|||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```
|
```
|
||||||
alpha()
|
mercury()
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-3:4: Missing code language flag
|
1:1-3:4: Unexpected missing fenced code language flag in info string, expected keyword
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-array.md`
|
||||||
|
|
||||||
When configured with `[ 'alpha' ]`.
|
When configured with `[ 'markdown' ]`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```alpha
|
```markdown
|
||||||
bravo()
|
# Mercury
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
@ -240,15 +240,15 @@ bravo()
|
|||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-options.md`
|
||||||
|
|
||||||
When configured with `{ flags: [ 'alpha' ] }`.
|
When configured with `{ flags: [ 'markdown' ] }`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```alpha
|
```markdown
|
||||||
bravo()
|
# Mercury
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
@ -256,22 +256,50 @@ bravo()
|
|||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-array.md`
|
||||||
|
|
||||||
When configured with `[ 'charlie' ]`.
|
When configured with `[ 'markdown' ]`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```alpha
|
```javascript
|
||||||
bravo()
|
mercury()
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-3:4: Incorrect code language flag
|
1:1-3:4: Unexpected fenced code language flag `javascript` in info string, expected `markdown`
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok-long-array.md`
|
||||||
|
|
||||||
|
When configured with `[ 'javascript', 'markdown', 'mdx', 'typescript' ]`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```html
|
||||||
|
<h1>Mercury</h1>
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1-3:4: Unexpected fenced code language flag `html` in info string, expected `javascript`, `markdown`, `mdx`, …
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok-options.md`
|
||||||
|
|
||||||
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options`, expected array or object
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -68,71 +68,70 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok-indented.md"}
|
||||||
*
|
*
|
||||||
* Indented code blocks are not affected by this rule:
|
* Indented code blocks are not affected by this rule:
|
||||||
*
|
*
|
||||||
* bravo()
|
* mercury()
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "`"}
|
* {"config": "`", "name": "ok-tick.md"}
|
||||||
*
|
*
|
||||||
* ```alpha
|
* ```javascript
|
||||||
* bravo()
|
* mercury()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* charlie()
|
* venus()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "~"}
|
* {"config": "~", "name": "ok-tilde.md"}
|
||||||
*
|
*
|
||||||
* ~~~alpha
|
* ~~~javascript
|
||||||
* bravo()
|
* mercury()
|
||||||
* ~~~
|
* ~~~
|
||||||
*
|
*
|
||||||
* ~~~
|
* ~~~
|
||||||
* charlie()
|
* venus()
|
||||||
* ~~~
|
* ~~~
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-consistent-tick.md", "label": "input"}
|
* {"label": "input", "name": "not-ok-consistent-tick.md"}
|
||||||
*
|
*
|
||||||
* ```alpha
|
* ```javascript
|
||||||
* bravo()
|
* mercury()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ~~~
|
* ~~~
|
||||||
* charlie()
|
* venus()
|
||||||
* ~~~
|
* ~~~
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-consistent-tick.md"}
|
||||||
|
*
|
||||||
|
* 5:1-7:4: Unexpected fenced code marker `~`, expected `` ` ``
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-consistent-tick.md", "label": "output"}
|
* {"label": "input", "name": "not-ok-consistent-tilde.md"}
|
||||||
*
|
*
|
||||||
* 5:1-7:4: Fenced code should use `` ` `` as a marker
|
* ~~~javascript
|
||||||
*
|
* mercury()
|
||||||
* @example
|
|
||||||
* {"name": "not-ok-consistent-tilde.md", "label": "input"}
|
|
||||||
*
|
|
||||||
* ~~~alpha
|
|
||||||
* bravo()
|
|
||||||
* ~~~
|
* ~~~
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* charlie()
|
* venus()
|
||||||
* ```
|
* ```
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-consistent-tilde.md"}
|
||||||
|
*
|
||||||
|
* 5:1-7:4: Unexpected fenced code marker `` ` ``, expected `~`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-consistent-tilde.md", "label": "output"}
|
* {"config": "🌍", "label": "output", "name": "not-ok-incorrect.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 5:1-7:4: Fenced code should use `~` as a marker
|
* 1:1: Unexpected value `🌍` for `options`, expected ``'`'``, `'~'`, or `'consistent'`
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok-incorrect.md", "config": "💩", "label": "output", "positionless": true}
|
|
||||||
*
|
|
||||||
* 1:1: Incorrect fenced code marker `💩`: use either `'consistent'`, `` '`' ``, or `'~'`
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,7 +148,8 @@
|
|||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const remarkLintFencedCodeMarker = lintRule(
|
const remarkLintFencedCodeMarker = lintRule(
|
||||||
{
|
{
|
||||||
@ -165,38 +165,59 @@ const remarkLintFencedCodeMarker = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
let option = options || 'consistent'
|
const value = String(file)
|
||||||
const contents = String(file)
|
/** @type {VFileMessage | undefined} */
|
||||||
|
let cause
|
||||||
|
/** @type {Marker | undefined} */
|
||||||
|
let expected
|
||||||
|
|
||||||
if (option !== 'consistent' && option !== '~' && option !== '`') {
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
|
// Empty.
|
||||||
|
} else if (options === '`' || options === '~') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
file.fail(
|
file.fail(
|
||||||
'Incorrect fenced code marker `' +
|
'Unexpected value `' +
|
||||||
option +
|
options +
|
||||||
"`: use either `'consistent'`, `` '`' ``, or `'~'`"
|
"` for `options`, expected ``'`'``, `'~'`, or `'consistent'`"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(tree, 'code', function (node) {
|
visitParents(tree, 'code', function (node, parents) {
|
||||||
const start = pointStart(node)
|
const start = pointStart(node)
|
||||||
|
|
||||||
if (start && typeof start.offset === 'number') {
|
if (start && typeof start.offset === 'number') {
|
||||||
const marker = contents
|
const actual = value
|
||||||
.slice(start.offset, start.offset + 4)
|
.slice(start.offset, start.offset + 4)
|
||||||
.replace(/^\s+/, '')
|
.replace(/^\s+/, '')
|
||||||
.charAt(0)
|
.charAt(0)
|
||||||
|
|
||||||
// Ignore unfenced code blocks.
|
// Ignore unfenced code blocks.
|
||||||
if (marker === '`' || marker === '~') {
|
if (actual !== '`' && actual !== '~') return
|
||||||
if (option === 'consistent') {
|
|
||||||
option = marker
|
if (expected) {
|
||||||
} else if (marker !== option) {
|
if (actual !== expected) {
|
||||||
file.message(
|
file.message(
|
||||||
'Fenced code should use `' +
|
'Unexpected fenced code marker ' +
|
||||||
(option === '~' ? option : '` ` `') +
|
(actual === '~' ? '`~`' : '`` ` ``') +
|
||||||
'` as a marker',
|
', expected ' +
|
||||||
node
|
(expected === '~' ? '`~`' : '`` ` ``'),
|
||||||
|
{ancestors: [...parents, node], cause, place: node.position}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
expected = actual
|
||||||
|
cause = new VFileMessage(
|
||||||
|
'Fenced code marker style ' +
|
||||||
|
(actual === '~' ? "`'~'`" : "``'`'``") +
|
||||||
|
" first defined for `'consistent'` here",
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
place: node.position,
|
||||||
|
ruleId: 'fenced-code-marker',
|
||||||
|
source: 'remark-lint'
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -35,7 +35,8 @@
|
|||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -175,33 +175,33 @@ Pass `fence: '~'` to always use tildes.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-indented.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Indented code blocks are not affected by this rule:
|
Indented code blocks are not affected by this rule:
|
||||||
|
|
||||||
bravo()
|
mercury()
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-tick.md`
|
||||||
|
|
||||||
When configured with ``'`'``.
|
When configured with ``'`'``.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```alpha
|
```javascript
|
||||||
bravo()
|
mercury()
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
charlie()
|
venus()
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
@ -209,19 +209,19 @@ charlie()
|
|||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-tilde.md`
|
||||||
|
|
||||||
When configured with `'~'`.
|
When configured with `'~'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
~~~alpha
|
~~~javascript
|
||||||
bravo()
|
mercury()
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
charlie()
|
venus()
|
||||||
~~~
|
~~~
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -234,19 +234,19 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```alpha
|
```javascript
|
||||||
bravo()
|
mercury()
|
||||||
```
|
```
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
charlie()
|
venus()
|
||||||
~~~
|
~~~
|
||||||
````
|
````
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5:1-7:4: Fenced code should use `` ` `` as a marker
|
5:1-7:4: Unexpected fenced code marker `~`, expected `` ` ``
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok-consistent-tilde.md`
|
##### `not-ok-consistent-tilde.md`
|
||||||
@ -254,29 +254,29 @@ charlie()
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
~~~alpha
|
~~~javascript
|
||||||
bravo()
|
mercury()
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
```
|
```
|
||||||
charlie()
|
venus()
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5:1-7:4: Fenced code should use `~` as a marker
|
5:1-7:4: Unexpected fenced code marker `` ` ``, expected `~`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok-incorrect.md`
|
##### `not-ok-incorrect.md`
|
||||||
|
|
||||||
When configured with `'💩'`.
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect fenced code marker `💩`: use either `'consistent'`, `` '`' ``, or `'~'`
|
1:1: Unexpected value `🌍` for `options`, expected ``'`'``, `'~'`, or `'consistent'`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "readme.md"}
|
* {"name": "readme.md"}
|
||||||
*
|
*
|
||||||
@ -74,18 +75,23 @@
|
|||||||
* @example
|
* @example
|
||||||
* {"config": {"allowExtensionless": false}, "label": "output", "name": "readme", "positionless": true}
|
* {"config": {"allowExtensionless": false}, "label": "output", "name": "readme", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect extension: use `mdx` or `md`
|
* 1:1: Unexpected missing file extension, expected `mdx` or `md`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"label": "output", "name": "readme.mkd", "positionless": true}
|
* {"label": "output", "name": "readme.mkd", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect extension: use `mdx` or `md`
|
* 1:1: Unexpected file extension `mkd`, expected `mdx` or `md`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "mkd", "name": "readme.mkd"}
|
* {"config": "mkd", "name": "readme.mkd"}
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": ["mkd"], "name": "readme.mkd"}
|
* {"config": ["markdown", "md", "mdown", "mdwn", "mdx", "mkd", "mkdn", "mkdown", "ron"], "label": "input", "name": "readme.css", "positionless": true}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": ["markdown", "md", "mdown", "mdwn", "mdx", "mkd", "mkdn", "mkdown", "ron"], "label": "output", "name": "readme.css"}
|
||||||
|
*
|
||||||
|
* 1:1: Unexpected file extension `css`, expected `markdown`, `md`, `mdown`, …
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,24 +99,25 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {ReadonlyArray<string> | string} Extensions
|
* @typedef {Array<string> | string} Extensions
|
||||||
* File extension(s).
|
* File extension(s).
|
||||||
*
|
*
|
||||||
* @typedef Options
|
* @typedef Options
|
||||||
* Configuration.
|
* Configuration.
|
||||||
* @property {boolean | null | undefined} [allowExtensionless=true]
|
* @property {boolean | null | undefined} [allowExtensionless=true]
|
||||||
* Allow no file extension such as `AUTHORS` or `LICENSE` (default: `true`).
|
* Allow no file extension such as `AUTHORS` or `LICENSE` (default: `true`).
|
||||||
* @property {Extensions | null | undefined} [extensions=['mdx', 'md']]
|
* @property {Readonly<Extensions> | null | undefined} [extensions=['mdx', 'md']]
|
||||||
* Allowed file extension(s) (default: `['mdx', 'md']`).
|
* Allowed file extension(s) (default: `['mdx', 'md']`).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
|
||||||
import {quotation} from 'quotation'
|
import {quotation} from 'quotation'
|
||||||
|
import {lintRule} from 'unified-lint-rule'
|
||||||
|
|
||||||
/** @type {ReadonlyArray<string>} */
|
/** @type {ReadonlyArray<string>} */
|
||||||
const defaultExtensions = ['mdx', 'md']
|
const defaultExtensions = ['mdx', 'md']
|
||||||
|
|
||||||
const listFormat = new Intl.ListFormat('en', {type: 'disjunction'})
|
const listFormat = new Intl.ListFormat('en', {type: 'disjunction'})
|
||||||
|
const listFormatUnit = new Intl.ListFormat('en', {type: 'unit'})
|
||||||
|
|
||||||
const remarkLintFileExtension = lintRule(
|
const remarkLintFileExtension = lintRule(
|
||||||
{
|
{
|
||||||
@ -126,7 +133,7 @@ const remarkLintFileExtension = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (_, file, options) {
|
function (_, file, options) {
|
||||||
let extensions = defaultExtensions
|
let expected = defaultExtensions
|
||||||
let allowExtensionless = true
|
let allowExtensionless = true
|
||||||
/** @type {Readonly<Extensions> | null | undefined} */
|
/** @type {Readonly<Extensions> | null | undefined} */
|
||||||
let extensionsValue
|
let extensionsValue
|
||||||
@ -147,18 +154,25 @@ const remarkLintFileExtension = lintRule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(extensionsValue)) {
|
if (Array.isArray(extensionsValue)) {
|
||||||
extensions = /** @type {ReadonlyArray<string>} */ (extensionsValue)
|
expected = /** @type {ReadonlyArray<string>} */ (extensionsValue)
|
||||||
} else if (typeof extensionsValue === 'string') {
|
} else if (typeof extensionsValue === 'string') {
|
||||||
extensions = [extensionsValue]
|
expected = [extensionsValue]
|
||||||
}
|
}
|
||||||
|
|
||||||
const extname = file.extname
|
const extname = file.extname
|
||||||
const extension = extname ? extname.slice(1) : undefined
|
const actual = extname ? extname.slice(1) : undefined
|
||||||
|
const expectedDisplay =
|
||||||
|
expected.length > 3
|
||||||
|
? listFormatUnit.format([...quotation(expected.slice(0, 3), '`'), '…'])
|
||||||
|
: listFormat.format(quotation(expected, '`'))
|
||||||
|
|
||||||
if (extension ? !extensions.includes(extension) : !allowExtensionless) {
|
if (actual ? !expected.includes(actual) : !allowExtensionless) {
|
||||||
file.message(
|
file.message(
|
||||||
'Incorrect extension: use ' +
|
(actual
|
||||||
listFormat.format(quotation(extensions, '`'))
|
? 'Unexpected file extension `' + actual + '`'
|
||||||
|
: 'Unexpected missing file extension') +
|
||||||
|
', expected ' +
|
||||||
|
expectedDisplay
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ When configured with `{ allowExtensionless: false }`.
|
|||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect extension: use `mdx` or `md`
|
1:1: Unexpected missing file extension, expected `mdx` or `md`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `readme.mkd`
|
##### `readme.mkd`
|
||||||
@ -201,7 +201,7 @@ When configured with `{ allowExtensionless: false }`.
|
|||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect extension: use `mdx` or `md`
|
1:1: Unexpected file extension `mkd`, expected `mdx` or `md`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `readme.mkd`
|
##### `readme.mkd`
|
||||||
@ -212,13 +212,21 @@ When configured with `'mkd'`.
|
|||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `readme.mkd`
|
##### `readme.css`
|
||||||
|
|
||||||
When configured with `[ 'mkd' ]`.
|
When configured with `[
|
||||||
|
'markdown', 'md',
|
||||||
|
'mdown', 'mdwn',
|
||||||
|
'mdx', 'mkd',
|
||||||
|
'mkdn', 'mkdown',
|
||||||
|
'ron'
|
||||||
|
]`.
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
```text
|
||||||
|
1:1: Unexpected file extension `css`, expected `markdown`, `md`, `mdown`, …
|
||||||
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
@ -38,64 +38,83 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Mercury.
|
||||||
*
|
*
|
||||||
* [example]: http://example.com "Example Domain"
|
* [venus]: http://example.com
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* [mercury]: http://example.com/mercury/
|
||||||
*
|
* [venus]: http://example.com/venus/
|
||||||
* [example]: http://example.com "Example Domain"
|
|
||||||
*
|
|
||||||
* Another paragraph.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
|
||||||
*
|
|
||||||
* 3:1-3:47: Move definitions to the end of the file (after `5:19`)
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-html-comments.md"}
|
* {"name": "ok-html-comments.md"}
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Mercury.
|
||||||
*
|
*
|
||||||
* [example-1]: http://example.com/one/
|
* [venus]: http://example.com/venus/
|
||||||
*
|
*
|
||||||
* <!-- Comments are fine between and after definitions. -->
|
* <!-- HTML comments in markdown are ignored. -->
|
||||||
*
|
*
|
||||||
* [example-2]: http://example.com/two/
|
* [earth]: http://example.com/earth/
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-mdx-comments.mdx", "mdx": true}
|
* {"name": "ok-mdx-comments.mdx", "mdx": true}
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Mercury.
|
||||||
*
|
*
|
||||||
* [example-1]: http://example.com/one/
|
* [venus]: http://example.com/venus/
|
||||||
*
|
*
|
||||||
* {/* Comments are fine in MDX. *␀/}
|
* {/* Comments in expressions in MDX are ignored. *␀/}
|
||||||
*
|
*
|
||||||
* [example-2]: http://example.com/two/
|
* [earth]: http://example.com/earth/
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* Mercury.
|
||||||
|
*
|
||||||
|
* [venus]: https://example.com/venus/
|
||||||
|
*
|
||||||
|
* Earth.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 3:1-3:36: Unexpected definition before last content, expected definitions after line `5`
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "input", "name": "gfm.md"}
|
||||||
|
*
|
||||||
|
* Mercury.
|
||||||
|
*
|
||||||
|
* [^venus]:
|
||||||
|
* **Venus** is the second planet from
|
||||||
|
* the Sun.
|
||||||
|
*
|
||||||
|
* Earth.
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "output", "name": "gfm.md"}
|
||||||
|
*
|
||||||
|
* 3:1-5:13: Unexpected footnote definition before last content, expected definitions after line `7`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Definition} Definition
|
* @typedef {import('mdast').Nodes} Nodes
|
||||||
* @typedef {import('mdast').FootnoteDefinition} FootnoteDefinition
|
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*
|
|
||||||
* @typedef {import('unist').Point} Point
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference types="mdast-util-mdx" />
|
/// <reference types="mdast-util-mdx" />
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd, pointStart} from 'unist-util-position'
|
||||||
import {stringifyPosition} from 'unist-util-stringify-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {visit} from 'unist-util-visit'
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const remarkLintFinalDefinition = lintRule(
|
const remarkLintFinalDefinition = lintRule(
|
||||||
{
|
{
|
||||||
@ -109,14 +128,14 @@ const remarkLintFinalDefinition = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
/** @type {Array<Definition | FootnoteDefinition>} */
|
/** @type {Array<Array<Nodes>>} */
|
||||||
const definitions = []
|
const definitionStacks = []
|
||||||
/** @type {Point | undefined} */
|
/** @type {Array<Nodes> | undefined} */
|
||||||
let last
|
let contentAncestors
|
||||||
|
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
if (node.type === 'definition' || node.type === 'footnoteDefinition') {
|
if (node.type === 'definition' || node.type === 'footnoteDefinition') {
|
||||||
definitions.push(node)
|
definitionStacks.push([...parents, node])
|
||||||
} else if (
|
} else if (
|
||||||
node.type === 'root' ||
|
node.type === 'root' ||
|
||||||
// Ignore HTML comments.
|
// Ignore HTML comments.
|
||||||
@ -128,24 +147,42 @@ const remarkLintFinalDefinition = lintRule(
|
|||||||
) {
|
) {
|
||||||
// Empty.
|
// Empty.
|
||||||
} else {
|
} else {
|
||||||
const place = pointEnd(node)
|
contentAncestors = [...parents, node]
|
||||||
|
|
||||||
if (place) {
|
|
||||||
last = place
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const node of definitions) {
|
const content = contentAncestors ? contentAncestors.at(-1) : undefined
|
||||||
const point = pointStart(node)
|
const contentEnd = pointEnd(content)
|
||||||
|
|
||||||
if (point && last && point.line < last.line) {
|
if (contentEnd) {
|
||||||
file.message(
|
assert(content) // Always defined.
|
||||||
'Move definitions to the end of the file (after `' +
|
assert(contentAncestors) // Always defined.
|
||||||
stringifyPosition(last) +
|
|
||||||
'`)',
|
for (const definitionAncestors of definitionStacks) {
|
||||||
node
|
const definition = definitionAncestors.at(-1)
|
||||||
)
|
assert(definition) // Always defined.
|
||||||
|
|
||||||
|
const definitionStart = pointStart(definition)
|
||||||
|
|
||||||
|
if (definitionStart && definitionStart.line < contentEnd.line) {
|
||||||
|
file.message(
|
||||||
|
'Unexpected ' +
|
||||||
|
(definition.type === 'footnoteDefinition' ? 'footnote ' : '') +
|
||||||
|
'definition before last content, expected definitions after line `' +
|
||||||
|
contentEnd.line +
|
||||||
|
'`',
|
||||||
|
{
|
||||||
|
ancestors: definitionAncestors,
|
||||||
|
cause: new VFileMessage('Last content defined here', {
|
||||||
|
ancestors: contentAncestors,
|
||||||
|
place: content.position,
|
||||||
|
ruleId: 'final-definition',
|
||||||
|
source: 'remark-lint'
|
||||||
|
}),
|
||||||
|
place: definition.position
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,12 +33,12 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"@types/unist": "^3.0.0",
|
"devlop": "^1.0.0",
|
||||||
"mdast-util-mdx": "^3.0.0",
|
"mdast-util-mdx": "^3.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-stringify-position": "^4.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -147,45 +147,40 @@ If you prefer that, turn on this rule.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
[example]: http://example.com "Example Domain"
|
[venus]: http://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `ok.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Paragraph.
|
[mercury]: http://example.com/mercury/
|
||||||
|
[venus]: http://example.com/venus/
|
||||||
[example]: http://example.com "Example Domain"
|
|
||||||
|
|
||||||
Another paragraph.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
No messages.
|
||||||
3:1-3:47: Move definitions to the end of the file (after `5:19`)
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `ok-html-comments.md`
|
##### `ok-html-comments.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
[example-1]: http://example.com/one/
|
[venus]: http://example.com/venus/
|
||||||
|
|
||||||
<!-- Comments are fine between and after definitions. -->
|
<!-- HTML comments in markdown are ignored. -->
|
||||||
|
|
||||||
[example-2]: http://example.com/two/
|
[earth]: http://example.com/earth/
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -200,19 +195,60 @@ No messages.
|
|||||||
> MDX ([`remark-mdx`][github-remark-mdx]).
|
> MDX ([`remark-mdx`][github-remark-mdx]).
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
[example-1]: http://example.com/one/
|
[venus]: http://example.com/venus/
|
||||||
|
|
||||||
{/* Comments are fine in MDX. */}
|
{/* Comments in expressions in MDX are ignored. */}
|
||||||
|
|
||||||
[example-2]: http://example.com/two/
|
[earth]: http://example.com/earth/
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
|
##### `not-ok.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Mercury.
|
||||||
|
|
||||||
|
[venus]: https://example.com/venus/
|
||||||
|
|
||||||
|
Earth.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
3:1-3:36: Unexpected definition before last content, expected definitions after line `5`
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `gfm.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Mercury.
|
||||||
|
|
||||||
|
[^venus]:
|
||||||
|
**Venus** is the second planet from
|
||||||
|
the Sun.
|
||||||
|
|
||||||
|
Earth.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
3:1-5:13: Unexpected footnote definition before last content, expected definitions after line `7`
|
||||||
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Projects maintained by the unified collective are compatible with maintained
|
Projects maintained by the unified collective are compatible with maintained
|
||||||
@ -282,6 +318,8 @@ abide by its terms.
|
|||||||
|
|
||||||
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
||||||
|
|
||||||
|
[github-remark-gfm]: https://github.com/remarkjs/remark-gfm
|
||||||
|
|
||||||
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
||||||
|
|
||||||
[github-remark-mdx]: https://mdxjs.com/packages/remark-mdx/
|
[github-remark-mdx]: https://mdxjs.com/packages/remark-mdx/
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
* ###### In
|
* ###### In
|
||||||
*
|
*
|
||||||
* ```markdown
|
* ```markdown
|
||||||
* Alpha␊
|
* Mercury␊
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ###### Out
|
* ###### Out
|
||||||
@ -57,13 +57,13 @@
|
|||||||
* ###### In
|
* ###### In
|
||||||
*
|
*
|
||||||
* ```markdown
|
* ```markdown
|
||||||
* Bravo␀
|
* Mercury␀
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ###### Out
|
* ###### Out
|
||||||
*
|
*
|
||||||
* ```text
|
* ```text
|
||||||
* 1:6: Missing newline character at end of file
|
* 1:8: Unexpected missing final newline character, expected line feed (`\n`) at end of file
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @module final-newline
|
* @module final-newline
|
||||||
@ -76,6 +76,7 @@
|
|||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {location} from 'vfile-location'
|
import {location} from 'vfile-location'
|
||||||
|
|
||||||
@ -95,8 +96,17 @@ const remarkLintFinalNewline = lintRule(
|
|||||||
const end = location(file).toPoint(value.length)
|
const end = location(file).toPoint(value.length)
|
||||||
const last = value.length - 1
|
const last = value.length - 1
|
||||||
|
|
||||||
if (end && last > -1 && value.charAt(last) !== '\n') {
|
assert(end) // Always defined.
|
||||||
file.message('Missing newline character at end of file', end)
|
|
||||||
|
if (
|
||||||
|
// Empty is fine.
|
||||||
|
last !== -1 &&
|
||||||
|
value.charAt(last) !== '\n'
|
||||||
|
) {
|
||||||
|
file.message(
|
||||||
|
'Unexpected missing final newline character, expected line feed (`\\n`) at end of file',
|
||||||
|
end
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"vfile-location": "^5.0.0"
|
"vfile-location": "^5.0.0"
|
||||||
},
|
},
|
||||||
|
@ -150,7 +150,7 @@ always adds final line endings.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Alpha␊
|
Mercury␊
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -162,13 +162,13 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Bravo␀
|
Mercury␀
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:6: Missing newline character at end of file
|
1:8: Unexpected missing final newline character, expected line feed (`\n`) at end of file
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -25,16 +25,6 @@
|
|||||||
*
|
*
|
||||||
* Transform ([`Transformer` from `unified`][github-unified-transformer]).
|
* Transform ([`Transformer` from `unified`][github-unified-transformer]).
|
||||||
*
|
*
|
||||||
* ### `Depth`
|
|
||||||
*
|
|
||||||
* Depth (TypeScript type).
|
|
||||||
*
|
|
||||||
* ###### Type
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* type Depth = 1 | 2 | 3 | 4 | 5 | 6
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* ### `Options`
|
* ### `Options`
|
||||||
*
|
*
|
||||||
* Configuration (TypeScript type).
|
* Configuration (TypeScript type).
|
||||||
@ -42,7 +32,7 @@
|
|||||||
* ###### Type
|
* ###### Type
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* type Options = Depth
|
* type Options = 1 | 2 | 3 | 4 | 5 | 6
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ## Recommendation
|
* ## Recommendation
|
||||||
@ -55,7 +45,6 @@
|
|||||||
* in which case a value of `2` can be defined here or the rule can be turned
|
* in which case a value of `2` can be defined here or the rule can be turned
|
||||||
* off.
|
* off.
|
||||||
*
|
*
|
||||||
* [api-depth]: #depth
|
|
||||||
* [api-options]: #options
|
* [api-options]: #options
|
||||||
* [api-remark-lint-first-heading-level]: #unifieduseremarklintfirstheadinglevel-options
|
* [api-remark-lint-first-heading-level]: #unifieduseremarklintfirstheadinglevel-options
|
||||||
* [github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
* [github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
||||||
@ -64,93 +53,55 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* # The default is to expect a level one heading
|
* # Mercury
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"name": "ok-delay.md"}
|
||||||
|
*
|
||||||
|
* Mercury.
|
||||||
|
*
|
||||||
|
* # Venus
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* ## Mercury
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 1:1-1:11: Unexpected first heading rank `2`, expected rank `1`
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": 2, "name": "ok.md"}
|
||||||
|
*
|
||||||
|
* ## Mercury
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-html.md"}
|
* {"name": "ok-html.md"}
|
||||||
*
|
*
|
||||||
* <h1>An HTML heading is also seen by this rule.</h1>
|
* <div>Mercury.</div>
|
||||||
|
*
|
||||||
|
* <h1>Venus</h1>
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-delayed.md"}
|
* {"mdx": true, "name": "ok-mdx.mdx"}
|
||||||
*
|
*
|
||||||
* You can use markdown content before the heading.
|
* <div>Mercury.</div>
|
||||||
*
|
*
|
||||||
* <div>Or non-heading HTML</div>
|
* <h1>Venus</h1>
|
||||||
*
|
|
||||||
* <h1>So the first heading, be it HTML or markdown, is checked</h1>
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"config": "🌍", "label": "output", "name": "not-ok-options.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* ## Bravo
|
* 1:1: Unexpected value `🌍` for `options`, expected `1`, `2`, `3`, `4`, `5`, or `6`
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
|
||||||
*
|
|
||||||
* 1:1-1:9: First heading level should be `1`
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok-html.md", "label": "input"}
|
|
||||||
*
|
|
||||||
* <h2>Charlie</h2>
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok-html.md", "label": "output"}
|
|
||||||
*
|
|
||||||
* 1:1-1:17: First heading level should be `1`
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": 2}
|
|
||||||
*
|
|
||||||
* ## Delta
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "ok-html.md", "config": 2}
|
|
||||||
*
|
|
||||||
* <h2>Echo</h2>
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok.md", "config": 2, "label": "input"}
|
|
||||||
*
|
|
||||||
* # Foxtrot
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok.md", "config": 2, "label": "output"}
|
|
||||||
*
|
|
||||||
* 1:1-1:10: First heading level should be `2`
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok-html.md", "config": 2, "label": "input"}
|
|
||||||
*
|
|
||||||
* <h1>Golf</h1>
|
|
||||||
*
|
|
||||||
* Paragraph.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok-html.md", "config": 2, "label": "output"}
|
|
||||||
*
|
|
||||||
* 1:1-1:14: First heading level should be `2`
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"mdx": true, "name": "ok.mdx"}
|
|
||||||
*
|
|
||||||
* In MDX, <b>JSX</b> is supported.
|
|
||||||
*
|
|
||||||
* <h1>First heading</h1>
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -159,18 +110,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Heading['depth']} Depth
|
* @typedef {1 | 2 | 3 | 4 | 5 | 6} Options
|
||||||
* Styles.
|
|
||||||
*
|
|
||||||
* @typedef {Depth} Options
|
|
||||||
* Configuration.
|
* Configuration.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference types="mdast-util-mdx" />
|
/// <reference types="mdast-util-mdx" />
|
||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {position} from 'unist-util-position'
|
import {EXIT, visitParents} from 'unist-util-visit-parents'
|
||||||
import {EXIT, visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const htmlRe = /<h([1-6])/
|
const htmlRe = /<h([1-6])/
|
||||||
const jsxNameRe = /^h([1-6])$/
|
const jsxNameRe = /^h([1-6])$/
|
||||||
@ -189,31 +136,60 @@ const remarkLintFirstHeadingLevel = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const option = options || 1
|
/** @type {Heading['depth']} */
|
||||||
|
let expected
|
||||||
|
|
||||||
visit(tree, function (node) {
|
if (options === null || options === undefined) {
|
||||||
/** @type {Depth | undefined} */
|
expected = 1
|
||||||
let rank
|
} else if (
|
||||||
|
options === 1 ||
|
||||||
|
options === 2 ||
|
||||||
|
options === 3 ||
|
||||||
|
options === 4 ||
|
||||||
|
options === 5 ||
|
||||||
|
options === 6
|
||||||
|
) {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' +
|
||||||
|
options +
|
||||||
|
'` for `options`, expected `1`, `2`, `3`, `4`, `5`, or `6`'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
visitParents(tree, function (node, parents) {
|
||||||
|
/** @type {Heading['depth'] | undefined} */
|
||||||
|
let actual
|
||||||
|
|
||||||
if (node.type === 'heading') {
|
if (node.type === 'heading') {
|
||||||
rank = node.depth
|
actual = node.depth
|
||||||
} else if (node.type === 'html') {
|
} else if (node.type === 'html') {
|
||||||
const results = node.value.match(htmlRe)
|
const results = node.value.match(htmlRe)
|
||||||
rank = results ? /** @type {Depth} */ (Number(results[1])) : undefined
|
actual = results
|
||||||
|
? /** @type {Heading['depth']} */ (Number(results[1]))
|
||||||
|
: undefined
|
||||||
} else if (
|
} else if (
|
||||||
(node.type === 'mdxJsxFlowElement' ||
|
(node.type === 'mdxJsxFlowElement' ||
|
||||||
node.type === 'mdxJsxTextElement') &&
|
node.type === 'mdxJsxTextElement') &&
|
||||||
node.name
|
node.name
|
||||||
) {
|
) {
|
||||||
const results = node.name.match(jsxNameRe)
|
const results = node.name.match(jsxNameRe)
|
||||||
rank = results ? /** @type {Depth} */ (Number(results[1])) : undefined
|
actual = results
|
||||||
|
? /** @type {Heading['depth']} */ (Number(results[1]))
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rank) {
|
if (actual && node.position) {
|
||||||
const place = position(node)
|
if (node.position && actual !== expected) {
|
||||||
|
file.message(
|
||||||
if (place && rank !== option) {
|
'Unexpected first heading rank `' +
|
||||||
file.message('First heading level should be `' + option + '`', place)
|
actual +
|
||||||
|
'`, expected rank `' +
|
||||||
|
expected +
|
||||||
|
'`',
|
||||||
|
{ancestors: [...parents, node], place: node.position}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXIT
|
return EXIT
|
||||||
|
@ -36,8 +36,7 @@
|
|||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"mdast-util-mdx": "^3.0.0",
|
"mdast-util-mdx": "^3.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
* [Use](#use)
|
* [Use](#use)
|
||||||
* [API](#api)
|
* [API](#api)
|
||||||
* [`unified().use(remarkLintFirstHeadingLevel[, options])`](#unifieduseremarklintfirstheadinglevel-options)
|
* [`unified().use(remarkLintFirstHeadingLevel[, options])`](#unifieduseremarklintfirstheadinglevel-options)
|
||||||
* [`Depth`](#depth)
|
|
||||||
* [`Options`](#options)
|
* [`Options`](#options)
|
||||||
* [Recommendation](#recommendation)
|
* [Recommendation](#recommendation)
|
||||||
* [Examples](#examples)
|
* [Examples](#examples)
|
||||||
@ -115,8 +114,7 @@ On the CLI in a config file (here a `package.json`):
|
|||||||
## API
|
## API
|
||||||
|
|
||||||
This package exports no identifiers.
|
This package exports no identifiers.
|
||||||
It exports the [TypeScript][typescript] types
|
It exports the [TypeScript][typescript] type
|
||||||
[`Depth`][api-depth] and
|
|
||||||
[`Options`][api-options].
|
[`Options`][api-options].
|
||||||
The default export is
|
The default export is
|
||||||
[`remarkLintFirstHeadingLevel`][api-remark-lint-first-heading-level].
|
[`remarkLintFirstHeadingLevel`][api-remark-lint-first-heading-level].
|
||||||
@ -134,16 +132,6 @@ Warn when the first heading has an unexpected rank.
|
|||||||
|
|
||||||
Transform ([`Transformer` from `unified`][github-unified-transformer]).
|
Transform ([`Transformer` from `unified`][github-unified-transformer]).
|
||||||
|
|
||||||
### `Depth`
|
|
||||||
|
|
||||||
Depth (TypeScript type).
|
|
||||||
|
|
||||||
###### Type
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type Depth = 1 | 2 | 3 | 4 | 5 | 6
|
|
||||||
```
|
|
||||||
|
|
||||||
### `Options`
|
### `Options`
|
||||||
|
|
||||||
Configuration (TypeScript type).
|
Configuration (TypeScript type).
|
||||||
@ -151,7 +139,7 @@ Configuration (TypeScript type).
|
|||||||
###### Type
|
###### Type
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
type Options = Depth
|
type Options = 1 | 2 | 3 | 4 | 5 | 6
|
||||||
```
|
```
|
||||||
|
|
||||||
## Recommendation
|
## Recommendation
|
||||||
@ -171,35 +159,21 @@ off.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# The default is to expect a level one heading
|
# Mercury
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok-html.md`
|
##### `ok-delay.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
<h1>An HTML heading is also seen by this rule.</h1>
|
Mercury.
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
# Venus
|
||||||
|
|
||||||
No messages.
|
|
||||||
|
|
||||||
##### `ok-delayed.md`
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
You can use markdown content before the heading.
|
|
||||||
|
|
||||||
<div>Or non-heading HTML</div>
|
|
||||||
|
|
||||||
<h1>So the first heading, be it HTML or markdown, is checked</h1>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -211,31 +185,15 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## Bravo
|
## Mercury
|
||||||
|
|
||||||
Paragraph.
|
Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:9: First heading level should be `1`
|
1:1-1:11: Unexpected first heading rank `2`, expected rank `1`
|
||||||
```
|
|
||||||
|
|
||||||
##### `not-ok-html.md`
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
<h2>Charlie</h2>
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:1-1:17: First heading level should be `1`
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok.md`
|
||||||
@ -245,9 +203,9 @@ When configured with `2`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## Delta
|
## Mercury
|
||||||
|
|
||||||
Paragraph.
|
Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -256,57 +214,19 @@ No messages.
|
|||||||
|
|
||||||
##### `ok-html.md`
|
##### `ok-html.md`
|
||||||
|
|
||||||
When configured with `2`.
|
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
<h2>Echo</h2>
|
<div>Mercury.</div>
|
||||||
|
|
||||||
Paragraph.
|
<h1>Venus</h1>
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `ok-mdx.mdx`
|
||||||
|
|
||||||
When configured with `2`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Foxtrot
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:1-1:10: First heading level should be `2`
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `not-ok-html.md`
|
|
||||||
|
|
||||||
When configured with `2`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
<h1>Golf</h1>
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:1-1:14: First heading level should be `2`
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `ok.mdx`
|
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
@ -314,15 +234,25 @@ Paragraph.
|
|||||||
> MDX ([`remark-mdx`][github-remark-mdx]).
|
> MDX ([`remark-mdx`][github-remark-mdx]).
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
In MDX, <b>JSX</b> is supported.
|
<div>Mercury.</div>
|
||||||
|
|
||||||
<h1>First heading</h1>
|
<h1>Venus</h1>
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
|
##### `not-ok-options.md`
|
||||||
|
|
||||||
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options`, expected `1`, `2`, `3`, `4`, `5`, or `6`
|
||||||
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Projects maintained by the unified collective are compatible with maintained
|
Projects maintained by the unified collective are compatible with maintained
|
||||||
@ -348,8 +278,6 @@ abide by its terms.
|
|||||||
|
|
||||||
[MIT][file-license] © [Titus Wormer][author]
|
[MIT][file-license] © [Titus Wormer][author]
|
||||||
|
|
||||||
[api-depth]: #depth
|
|
||||||
|
|
||||||
[api-options]: #options
|
[api-options]: #options
|
||||||
|
|
||||||
[api-remark-lint-first-heading-level]: #unifieduseremarklintfirstheadinglevel-options
|
[api-remark-lint-first-heading-level]: #unifieduseremarklintfirstheadinglevel-options
|
||||||
|
@ -41,19 +41,29 @@
|
|||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* Lorem ipsum␠␠
|
* **Mercury** is the first planet from the Sun␠␠
|
||||||
* dolor sit amet
|
* and the smallest in the Solar System.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* Lorem ipsum␠␠␠
|
* **Mercury** is the first planet from the Sun␠␠␠
|
||||||
* dolor sit amet.
|
* and the smallest in the Solar System.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 1:45-2:1: Unexpected `3` spaces for hard break, expected `2` spaces
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"gfm": true, "label": "input", "name": "containers.md"}
|
||||||
*
|
*
|
||||||
* 1:12-2:1: Use two spaces for hard line breaks
|
* [^mercury]:
|
||||||
|
* > * > * **Mercury** is the first planet from the Sun␠␠␠
|
||||||
|
* > > and the smallest in the Solar System.
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "output", "name": "containers.md"}
|
||||||
|
*
|
||||||
|
* 2:57-3:1: Unexpected `3` spaces for hard break, expected `2` spaces
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,8 +89,8 @@ const remarkLintHardBreakSpaces = lintRule(
|
|||||||
const value = String(file)
|
const value = String(file)
|
||||||
|
|
||||||
visit(tree, 'break', function (node) {
|
visit(tree, 'break', function (node) {
|
||||||
const start = pointStart(node)
|
|
||||||
const end = pointEnd(node)
|
const end = pointEnd(node)
|
||||||
|
const start = pointStart(node)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
end &&
|
end &&
|
||||||
@ -88,13 +98,18 @@ const remarkLintHardBreakSpaces = lintRule(
|
|||||||
typeof end.offset === 'number' &&
|
typeof end.offset === 'number' &&
|
||||||
typeof start.offset === 'number'
|
typeof start.offset === 'number'
|
||||||
) {
|
) {
|
||||||
const slice = value
|
const slice = value.slice(start.offset, end.offset)
|
||||||
.slice(start.offset, end.offset)
|
|
||||||
.split('\n', 1)[0]
|
|
||||||
.replace(/\r$/, '')
|
|
||||||
|
|
||||||
if (slice.length > 2) {
|
let actual = 0
|
||||||
file.message('Use two spaces for hard line breaks', node)
|
while (slice.charCodeAt(actual) === 32) actual++
|
||||||
|
|
||||||
|
if (actual > 2) {
|
||||||
|
file.message(
|
||||||
|
'Unexpected `' +
|
||||||
|
actual +
|
||||||
|
'` spaces for hard break, expected `2` spaces',
|
||||||
|
node
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -48,7 +48,8 @@
|
|||||||
"xo": {
|
"xo": {
|
||||||
"prettier": true,
|
"prettier": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"capitalized-comments": "off"
|
"capitalized-comments": "off",
|
||||||
|
"unicorn/prefer-code-point": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,8 +148,8 @@ Due to this, it’s recommended to turn this rule on.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Lorem ipsum␠␠
|
**Mercury** is the first planet from the Sun␠␠
|
||||||
dolor sit amet
|
and the smallest in the Solar System.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -161,14 +161,33 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Lorem ipsum␠␠␠
|
**Mercury** is the first planet from the Sun␠␠␠
|
||||||
dolor sit amet.
|
and the smallest in the Solar System.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:12-2:1: Use two spaces for hard line breaks
|
1:45-2:1: Unexpected `3` spaces for hard break, expected `2` spaces
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `containers.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[^mercury]:
|
||||||
|
> * > * **Mercury** is the first planet from the Sun␠␠␠
|
||||||
|
> > and the smallest in the Solar System.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
2:57-3:1: Unexpected `3` spaces for hard break, expected `2` spaces
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
@ -240,6 +259,8 @@ abide by its terms.
|
|||||||
|
|
||||||
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
||||||
|
|
||||||
|
[github-remark-gfm]: https://github.com/remarkjs/remark-gfm
|
||||||
|
|
||||||
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
||||||
|
|
||||||
[github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
[github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
||||||
|
@ -44,50 +44,88 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* # Alpha
|
* # Mercury
|
||||||
*
|
*
|
||||||
* ## Bravo
|
* ## Nomenclature
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"name": "also-ok.md"}
|
||||||
*
|
*
|
||||||
* # Charlie
|
* #### Impact basins and craters
|
||||||
*
|
*
|
||||||
* ### Delta
|
* #### Plains
|
||||||
|
*
|
||||||
|
* #### Compressional features
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 3:1-3:10: Heading levels should increment by one level at a time
|
* # Mercury
|
||||||
|
*
|
||||||
|
* ### Internal structure
|
||||||
|
*
|
||||||
|
* ### Surface geology
|
||||||
|
*
|
||||||
|
* ## Observation history
|
||||||
|
*
|
||||||
|
* #### Mariner 10
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "html.md"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* In markdown, <b>HTML</b> is supported.
|
* 3:1-3:23: Unexpected heading rank `3`, exected rank `2`
|
||||||
*
|
* 5:1-5:20: Unexpected heading rank `3`, exected rank `2`
|
||||||
* <h1>First heading</h1>
|
* 9:1-9:16: Unexpected heading rank `4`, exected rank `3`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.mdx", "mdx": true}
|
* {"label": "input", "name": "html.md"}
|
||||||
*
|
*
|
||||||
* In MDX, <b>JSX</b> is supported.
|
* # Mercury
|
||||||
*
|
*
|
||||||
* <h1>First heading</h1>
|
* <b>Mercury</b> is the first planet from the Sun and the smallest
|
||||||
|
* in the Solar System.
|
||||||
|
*
|
||||||
|
* <h3>Internal structure</h3>
|
||||||
|
*
|
||||||
|
* <h2>Orbit, rotation, and longitude</h2>
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "html.md"}
|
||||||
|
*
|
||||||
|
* 6:1-6:28: Unexpected heading rank `3`, exected rank `2`
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"mdx": true, "name": "mdx.mdx"}
|
||||||
|
*
|
||||||
|
* # Mercury
|
||||||
|
*
|
||||||
|
* <b>Mercury</b> is the first planet from the Sun and the smallest
|
||||||
|
* in the Solar System.
|
||||||
|
*
|
||||||
|
* <h3>Internal structure</h3>
|
||||||
|
*
|
||||||
|
* <h2>Orbit, rotation, and longitude</h2>
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "mdx": true, "name": "mdx.mdx"}
|
||||||
|
*
|
||||||
|
* 6:1-6:28: Unexpected heading rank `3`, exected rank `2`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Heading} Heading
|
* @typedef {import('mdast').Heading} Heading
|
||||||
|
* @typedef {import('mdast').Nodes} Nodes
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference types="mdast-util-mdx" />
|
/// <reference types="mdast-util-mdx" />
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {position} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {visit} from 'unist-util-visit'
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const htmlRe = /<h([1-6])/
|
const htmlRe = /<h([1-6])/
|
||||||
const jsxNameRe = /^h([1-6])$/
|
const jsxNameRe = /^h([1-6])$/
|
||||||
@ -104,47 +142,89 @@ const remarkLintHeadingIncrement = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
/** @type {Heading['depth'] | undefined} */
|
/** @type {Array<Array<Nodes> | undefined>} */
|
||||||
let previous
|
const stack = []
|
||||||
|
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
const place = position(node)
|
const rank = inferRank(node)
|
||||||
|
|
||||||
if (place) {
|
if (rank) {
|
||||||
/** @type {Heading['depth'] | undefined} */
|
let index = rank
|
||||||
let rank
|
/** @type {Array<Nodes> | undefined} */
|
||||||
|
let closestAncestors
|
||||||
|
|
||||||
if (node.type === 'heading') {
|
while (index--) {
|
||||||
rank = node.depth
|
if (stack[index]) {
|
||||||
} else if (node.type === 'html') {
|
closestAncestors = stack[index]
|
||||||
const results = node.value.match(htmlRe)
|
break
|
||||||
rank = results
|
}
|
||||||
? /** @type {Heading['depth']} */ (Number(results[1]))
|
|
||||||
: undefined
|
|
||||||
} else if (
|
|
||||||
(node.type === 'mdxJsxFlowElement' ||
|
|
||||||
node.type === 'mdxJsxTextElement') &&
|
|
||||||
node.name
|
|
||||||
) {
|
|
||||||
const results = node.name.match(jsxNameRe)
|
|
||||||
rank = results
|
|
||||||
? /** @type {Heading['depth']} */ (Number(results[1]))
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rank) {
|
if (closestAncestors) {
|
||||||
if (previous && rank > previous + 1) {
|
const parent = closestAncestors.at(-1)
|
||||||
|
assert(parent) // Always defined.
|
||||||
|
const parentRank = inferRank(parent)
|
||||||
|
assert(parentRank) // Always defined.
|
||||||
|
|
||||||
|
if (node.position && rank > parentRank + 1) {
|
||||||
file.message(
|
file.message(
|
||||||
'Heading levels should increment by one level at a time',
|
'Unexpected heading rank `' +
|
||||||
place
|
rank +
|
||||||
|
'`, exected rank `' +
|
||||||
|
(parentRank + 1) +
|
||||||
|
'`',
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
cause: new VFileMessage('Parent heading defined here', {
|
||||||
|
ancestors: closestAncestors,
|
||||||
|
place: parent.position,
|
||||||
|
source: 'remark-lint',
|
||||||
|
ruleId: 'heading-increment'
|
||||||
|
}),
|
||||||
|
place: node.position
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
previous = rank
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stack[rank] = [...parents, node]
|
||||||
|
// Drop things after it.
|
||||||
|
stack.length = rank + 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export default remarkLintHeadingIncrement
|
export default remarkLintHeadingIncrement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get rank of a node.
|
||||||
|
*
|
||||||
|
* @param {Nodes} node
|
||||||
|
* Node.
|
||||||
|
* @returns {Heading['depth'] | undefined}
|
||||||
|
* Rank, if heading.
|
||||||
|
*/
|
||||||
|
function inferRank(node) {
|
||||||
|
/** @type {Heading['depth'] | undefined} */
|
||||||
|
let rank
|
||||||
|
|
||||||
|
if (node.type === 'heading') {
|
||||||
|
rank = node.depth
|
||||||
|
} else if (node.type === 'html') {
|
||||||
|
const results = node.value.match(htmlRe)
|
||||||
|
rank = results
|
||||||
|
? /** @type {Heading['depth']} */ (Number(results[1]))
|
||||||
|
: undefined
|
||||||
|
} else if (
|
||||||
|
(node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') &&
|
||||||
|
node.name
|
||||||
|
) {
|
||||||
|
const results = node.name.match(jsxNameRe)
|
||||||
|
rank = results
|
||||||
|
? /** @type {Heading['depth']} */ (Number(results[1]))
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return rank
|
||||||
|
}
|
||||||
|
@ -33,10 +33,11 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
"mdast-util-mdx": "^3.0.0",
|
"mdast-util-mdx": "^3.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -153,9 +153,25 @@ it’s recommended that this rule is turned on.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Alpha
|
# Mercury
|
||||||
|
|
||||||
## Bravo
|
## Nomenclature
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `also-ok.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
#### Impact basins and craters
|
||||||
|
|
||||||
|
#### Plains
|
||||||
|
|
||||||
|
#### Compressional features
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -167,15 +183,23 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Charlie
|
# Mercury
|
||||||
|
|
||||||
### Delta
|
### Internal structure
|
||||||
|
|
||||||
|
### Surface geology
|
||||||
|
|
||||||
|
## Observation history
|
||||||
|
|
||||||
|
#### Mariner 10
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
3:1-3:10: Heading levels should increment by one level at a time
|
3:1-3:23: Unexpected heading rank `3`, exected rank `2`
|
||||||
|
5:1-5:20: Unexpected heading rank `3`, exected rank `2`
|
||||||
|
9:1-9:16: Unexpected heading rank `4`, exected rank `3`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `html.md`
|
##### `html.md`
|
||||||
@ -183,16 +207,23 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
In markdown, <b>HTML</b> is supported.
|
# Mercury
|
||||||
|
|
||||||
<h1>First heading</h1>
|
<b>Mercury</b> is the first planet from the Sun and the smallest
|
||||||
|
in the Solar System.
|
||||||
|
|
||||||
|
<h3>Internal structure</h3>
|
||||||
|
|
||||||
|
<h2>Orbit, rotation, and longitude</h2>
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
```text
|
||||||
|
6:1-6:28: Unexpected heading rank `3`, exected rank `2`
|
||||||
|
```
|
||||||
|
|
||||||
##### `ok.mdx`
|
##### `mdx.mdx`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
@ -200,14 +231,21 @@ No messages.
|
|||||||
> MDX ([`remark-mdx`][github-remark-mdx]).
|
> MDX ([`remark-mdx`][github-remark-mdx]).
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
In MDX, <b>JSX</b> is supported.
|
# Mercury
|
||||||
|
|
||||||
<h1>First heading</h1>
|
<b>Mercury</b> is the first planet from the Sun and the smallest
|
||||||
|
in the Solar System.
|
||||||
|
|
||||||
|
<h3>Internal structure</h3>
|
||||||
|
|
||||||
|
<h2>Orbit, rotation, and longitude</h2>
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
```text
|
||||||
|
6:1-6:28: Unexpected heading rank `3`, exected rank `2`
|
||||||
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
@ -81,55 +81,55 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": "atx"}
|
|
||||||
*
|
|
||||||
* # Alpha
|
|
||||||
*
|
|
||||||
* ## Bravo
|
|
||||||
*
|
|
||||||
* ### Charlie
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "atx-closed"}
|
* {"config": "atx", "name": "ok.md"}
|
||||||
*
|
*
|
||||||
* # Delta ##
|
* # Mercury
|
||||||
*
|
*
|
||||||
* ## Echo ##
|
* ## Venus
|
||||||
*
|
*
|
||||||
* ### Foxtrot ###
|
* ### Earth
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "setext"}
|
* {"config": "atx-closed", "name": "ok.md"}
|
||||||
*
|
*
|
||||||
* Golf
|
* # Mercury ##
|
||||||
* ====
|
|
||||||
*
|
*
|
||||||
* Hotel
|
* ## Venus ##
|
||||||
* -----
|
|
||||||
*
|
*
|
||||||
* ### India
|
* ### Earth ###
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"config": "setext", "name": "ok.md"}
|
||||||
*
|
*
|
||||||
* Juliett
|
* Mercury
|
||||||
* =======
|
* =======
|
||||||
*
|
*
|
||||||
* ## Kilo
|
* Venus
|
||||||
|
* -----
|
||||||
*
|
*
|
||||||
* ### Lima ###
|
* ### Earth
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 4:1-4:8: Headings should use setext
|
* Mercury
|
||||||
* 6:1-6:13: Headings should use setext
|
* =======
|
||||||
|
*
|
||||||
|
* ## Venus
|
||||||
|
*
|
||||||
|
* ### Earth ###
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 4:1-4:9: Unexpected ATX heading, expected setext
|
||||||
|
* 6:1-6:14: Unexpected ATX (closed) heading, expected setext
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "💩", "label": "output", "positionless": true}
|
* {"config": "🌍", "label": "output", "name": "not-ok.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect heading style type `💩`: use either `'consistent'`, `'atx'`, `'atx-closed'`, or `'setext'`
|
* 1:1: Unexpected value `🌍` for `options`, expected `'atx'`, `'atx-closed'`, `'setext'`, or `'consistent'`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,7 +147,8 @@
|
|||||||
import {headingStyle} from 'mdast-util-heading-style'
|
import {headingStyle} from 'mdast-util-heading-style'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {position} from 'unist-util-position'
|
import {position} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const remarkLintHeadingStyle = lintRule(
|
const remarkLintHeadingStyle = lintRule(
|
||||||
{
|
{
|
||||||
@ -163,30 +164,55 @@ const remarkLintHeadingStyle = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
let option = options || 'consistent'
|
/** @type {VFileMessage | undefined} */
|
||||||
|
let cause
|
||||||
|
/** @type {Style | undefined} */
|
||||||
|
let expected
|
||||||
|
|
||||||
if (
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
option !== 'atx' &&
|
// Empty.
|
||||||
option !== 'atx-closed' &&
|
} else if (
|
||||||
option !== 'consistent' &&
|
options === 'atx' ||
|
||||||
option !== 'setext'
|
options === 'atx-closed' ||
|
||||||
|
options === 'setext'
|
||||||
) {
|
) {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
file.fail(
|
file.fail(
|
||||||
'Incorrect heading style type `' +
|
'Unexpected value `' +
|
||||||
option +
|
options +
|
||||||
"`: use either `'consistent'`, `'atx'`, `'atx-closed'`, or `'setext'`"
|
"` for `options`, expected `'atx'`, `'atx-closed'`, `'setext'`, or `'consistent'`"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(tree, 'heading', function (node) {
|
visitParents(tree, 'heading', function (node, parents) {
|
||||||
const place = position(node)
|
const place = position(node)
|
||||||
|
const actual = headingStyle(node, expected)
|
||||||
|
|
||||||
if (place) {
|
if (actual) {
|
||||||
if (option === 'consistent') {
|
if (expected) {
|
||||||
/* c8 ignore next -- funky nodes perhaps cannot be detected. */
|
if (place && actual !== expected) {
|
||||||
option = headingStyle(node) || 'consistent'
|
file.message(
|
||||||
} else if (headingStyle(node, option) !== option) {
|
'Unexpected ' +
|
||||||
file.message('Headings should use ' + option, place)
|
displayStyle(actual) +
|
||||||
|
' heading, expected ' +
|
||||||
|
displayStyle(expected),
|
||||||
|
{ancestors: [...parents, node], cause, place}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expected = actual
|
||||||
|
cause = new VFileMessage(
|
||||||
|
'Heading style ' +
|
||||||
|
displayStyle(expected) +
|
||||||
|
" first defined for `'consistent'` here",
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
|
place,
|
||||||
|
ruleId: 'heading-style',
|
||||||
|
source: 'remark-lint'
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -194,3 +220,17 @@ const remarkLintHeadingStyle = lintRule(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export default remarkLintHeadingStyle
|
export default remarkLintHeadingStyle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Style} style
|
||||||
|
* Style.
|
||||||
|
* @returns {string}
|
||||||
|
* Display.
|
||||||
|
*/
|
||||||
|
function displayStyle(style) {
|
||||||
|
return style === 'atx'
|
||||||
|
? 'ATX'
|
||||||
|
: style === 'atx-closed'
|
||||||
|
? 'ATX (closed)'
|
||||||
|
: 'setext'
|
||||||
|
}
|
||||||
|
@ -37,7 +37,8 @@
|
|||||||
"mdast-util-heading-style": "^3.0.0",
|
"mdast-util-heading-style": "^3.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -195,11 +195,11 @@ When configured with `'atx'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Alpha
|
# Mercury
|
||||||
|
|
||||||
## Bravo
|
## Venus
|
||||||
|
|
||||||
### Charlie
|
### Earth
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -213,11 +213,11 @@ When configured with `'atx-closed'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Delta ##
|
# Mercury ##
|
||||||
|
|
||||||
## Echo ##
|
## Venus ##
|
||||||
|
|
||||||
### Foxtrot ###
|
### Earth ###
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -231,13 +231,13 @@ When configured with `'setext'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Golf
|
Mercury
|
||||||
====
|
=======
|
||||||
|
|
||||||
Hotel
|
Venus
|
||||||
-----
|
-----
|
||||||
|
|
||||||
### India
|
### Earth
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -249,29 +249,29 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Juliett
|
Mercury
|
||||||
=======
|
=======
|
||||||
|
|
||||||
## Kilo
|
## Venus
|
||||||
|
|
||||||
### Lima ###
|
### Earth ###
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
4:1-4:8: Headings should use setext
|
4:1-4:9: Unexpected ATX heading, expected setext
|
||||||
6:1-6:13: Headings should use setext
|
6:1-6:14: Unexpected ATX (closed) heading, expected setext
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
When configured with `'💩'`.
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect heading style type `💩`: use either `'consistent'`, `'atx'`, `'atx-closed'`, or `'setext'`
|
1:1: Unexpected value `🌍` for `options`, expected `'atx'`, `'atx-closed'`, `'setext'`, or `'consistent'`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
*
|
*
|
||||||
* ## Fix
|
* ## Fix
|
||||||
*
|
*
|
||||||
* [`remark-stringify`][github-remark-stringify] always uses Unix linebreaks.
|
* [`remark-stringify`][github-remark-stringify] always uses Unix line endings.
|
||||||
*
|
*
|
||||||
* [api-options]: #options
|
* [api-options]: #options
|
||||||
* [api-remark-lint-linebreak-style]: #unifieduseremarklintlinebreakstyle-options
|
* [api-remark-lint-linebreak-style]: #unifieduseremarklintlinebreakstyle-options
|
||||||
@ -67,35 +67,56 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2017 Titus Wormer
|
* @copyright 2017 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-consistent-as-windows.md"}
|
* {"name": "ok-consistent-as-windows.md"}
|
||||||
*
|
*
|
||||||
* Alpha␍␊Bravo␍␊
|
* Mercury␍␊and␍␊Venus.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-consistent-as-unix.md"}
|
* {"name": "ok-consistent-as-unix.md"}
|
||||||
*
|
*
|
||||||
* Alpha␊Bravo␊
|
* Mercury␊and␊Venus.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-unix.md", "label": "input", "config": "unix", "positionless": true}
|
* {"config": "unix", "label": "input", "name": "not-ok-unix.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* Alpha␍␊
|
* Mercury.␍␊
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-unix.md", "label": "output", "config": "unix"}
|
* {"config": "unix", "label": "output", "name": "not-ok-unix.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:7: Expected linebreaks to be unix (`\n`), not windows (`\r\n`)
|
* 1:10: Unexpected windows (`\r\n`) line ending, expected unix (`\n`) line endings
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-windows.md", "label": "input", "config": "windows", "positionless": true}
|
* {"config": "windows", "label": "input", "name": "not-ok-windows.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* Alpha␊
|
* Mercury.␊
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-windows.md", "label": "output", "config": "windows"}
|
* {"config": "windows", "label": "output", "name": "not-ok-windows.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:6: Expected linebreaks to be windows (`\r\n`), not unix (`\n`)
|
* 1:9: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "🌍", "label": "output", "name": "not-ok-options.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:1: Unexpected value `🌍` for `options`, expected `'unix'`, `'windows'`, or `'consistent'`
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "windows", "label": "input", "name": "many.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* Mercury.␊Venus.␊Earth.␊Mars.␊Jupiter.␊Saturn.␊Uranus.␊Neptune.␊
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "windows", "label": "output", "name": "many.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:9: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
* 2:7: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
* 3:7: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
* 4:6: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
* 5:9: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
* 6:8: Unexpected large number of incorrect line endings, stopping
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,10 +131,12 @@
|
|||||||
* Styles.
|
* Styles.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {location} from 'vfile-location'
|
import {location} from 'vfile-location'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const escaped = {unix: '\\n', windows: '\\r\\n'}
|
const max = 5
|
||||||
|
|
||||||
const remarkLintLinebreakStyle = lintRule(
|
const remarkLintLinebreakStyle = lintRule(
|
||||||
{
|
{
|
||||||
@ -129,28 +152,60 @@ const remarkLintLinebreakStyle = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (_, file, options) {
|
function (_, file, options) {
|
||||||
let option = options || 'consistent'
|
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
const toPoint = location(value).toPoint
|
const toPoint = location(value).toPoint
|
||||||
let index = value.indexOf('\n')
|
let index = value.indexOf('\n')
|
||||||
|
/** @type {VFileMessage | undefined} */
|
||||||
|
let cause
|
||||||
|
/** @type {Style | undefined} */
|
||||||
|
let expected
|
||||||
|
|
||||||
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
|
// Empty.
|
||||||
|
} else if (options === 'unix' || options === 'windows') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' +
|
||||||
|
options +
|
||||||
|
"` for `options`, expected `'unix'`, `'windows'`, or `'consistent'`"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let messages = 0
|
||||||
|
|
||||||
while (index !== -1) {
|
while (index !== -1) {
|
||||||
const type = value.charAt(index - 1) === '\r' ? 'windows' : 'unix'
|
const actual = value.charAt(index - 1) === '\r' ? 'windows' : 'unix'
|
||||||
|
const place = toPoint(index)
|
||||||
|
assert(place) // Always defined.
|
||||||
|
|
||||||
if (option === 'consistent') {
|
if (expected) {
|
||||||
option = type
|
if (expected !== actual) {
|
||||||
} else if (option !== type) {
|
if (messages === max) {
|
||||||
file.message(
|
file.info(
|
||||||
'Expected linebreaks to be ' +
|
'Unexpected large number of incorrect line endings, stopping',
|
||||||
option +
|
{place}
|
||||||
' (`' +
|
)
|
||||||
escaped[option] +
|
return
|
||||||
'`), not ' +
|
}
|
||||||
type +
|
|
||||||
' (`' +
|
file.message(
|
||||||
escaped[type] +
|
'Unexpected ' +
|
||||||
'`)',
|
displayStyle(actual) +
|
||||||
toPoint(index)
|
' line ending, expected ' +
|
||||||
|
displayStyle(expected) +
|
||||||
|
' line endings',
|
||||||
|
{cause, place}
|
||||||
|
)
|
||||||
|
messages++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expected = actual
|
||||||
|
cause = new VFileMessage(
|
||||||
|
'Line ending style ' +
|
||||||
|
displayStyle(expected) +
|
||||||
|
" first defined for `'consistent'` here",
|
||||||
|
{place, ruleId: 'linebreak-style', source: 'remark-lint'}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,3 +215,13 @@ const remarkLintLinebreakStyle = lintRule(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export default remarkLintLinebreakStyle
|
export default remarkLintLinebreakStyle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Style} style
|
||||||
|
* Style.
|
||||||
|
* @returns {string}
|
||||||
|
* Display.
|
||||||
|
*/
|
||||||
|
function displayStyle(style) {
|
||||||
|
return style === 'unix' ? 'unix (`\\n`)' : 'windows (`\\r\\n`)'
|
||||||
|
}
|
||||||
|
@ -38,8 +38,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"vfile-location": "^5.0.0"
|
"vfile-location": "^5.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -165,7 +165,7 @@ used.
|
|||||||
|
|
||||||
## Fix
|
## Fix
|
||||||
|
|
||||||
[`remark-stringify`][github-remark-stringify] always uses Unix linebreaks.
|
[`remark-stringify`][github-remark-stringify] always uses Unix line endings.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ used.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Alpha␍␊Bravo␍␊
|
Mercury␍␊and␍␊Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -186,7 +186,7 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Alpha␊Bravo␊
|
Mercury␊and␊Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -200,13 +200,13 @@ When configured with `'unix'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Alpha␍␊
|
Mercury.␍␊
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:7: Expected linebreaks to be unix (`\n`), not windows (`\r\n`)
|
1:10: Unexpected windows (`\r\n`) line ending, expected unix (`\n`) line endings
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok-windows.md`
|
##### `not-ok-windows.md`
|
||||||
@ -216,13 +216,44 @@ When configured with `'windows'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Alpha␊
|
Mercury.␊
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:6: Expected linebreaks to be windows (`\r\n`), not unix (`\n`)
|
1:9: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok-options.md`
|
||||||
|
|
||||||
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options`, expected `'unix'`, `'windows'`, or `'consistent'`
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `many.md`
|
||||||
|
|
||||||
|
When configured with `'windows'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Mercury.␊Venus.␊Earth.␊Mars.␊Jupiter.␊Saturn.␊Uranus.␊Neptune.␊
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:9: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
2:7: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
3:7: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
4:6: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
5:9: Unexpected unix (`\n`) line ending, expected windows (`\r\n`) line endings
|
||||||
|
6:8: Unexpected large number of incorrect line endings, stopping
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
*
|
*
|
||||||
* ## What is this?
|
* ## What is this?
|
||||||
*
|
*
|
||||||
* This package checks the style of link title markers.
|
* This package checks the style of link (*and* image and definition) title
|
||||||
|
* markers.
|
||||||
*
|
*
|
||||||
* ## When should I use this?
|
* ## When should I use this?
|
||||||
*
|
*
|
||||||
@ -59,7 +60,7 @@
|
|||||||
*
|
*
|
||||||
* ## Fix
|
* ## Fix
|
||||||
*
|
*
|
||||||
* [`remark-stringify`][github-remark-stringify] formats titles with double
|
* [`remark-stringify`][github-remark-stringify] formats titles with double
|
||||||
* quotes by default.
|
* quotes by default.
|
||||||
* Pass `quote: "'"` to use single quotes.
|
* Pass `quote: "'"` to use single quotes.
|
||||||
* There is no option to use parens.
|
* There is no option to use parens.
|
||||||
@ -74,82 +75,90 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "config": "\""}
|
|
||||||
*
|
|
||||||
* [Example](http://example.com#without-title)
|
|
||||||
* [Example](http://example.com "Example Domain")
|
|
||||||
* ![Example](http://example.com "Example Domain")
|
|
||||||
*
|
|
||||||
* [Example]: http://example.com "Example Domain"
|
|
||||||
*
|
|
||||||
* You can use parens in URLs if they’re not a title (see GH-166):
|
|
||||||
*
|
|
||||||
* [Example](#Heading-(optional))
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input", "config": "\""}
|
* {"name": "ok-consistent.md"}
|
||||||
*
|
*
|
||||||
* [Example]: http://example.com 'Example Domain'
|
* [Mercury](http://example.com/mercury/),
|
||||||
|
* [Venus](http://example.com/venus/ "Go to Venus"), and
|
||||||
|
* ![Earth](http://example.com/earth/ "Go to Earth").
|
||||||
|
*
|
||||||
|
* [Mars]: http://example.com/mars/ "Go to Mars"
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output", "config": "\""}
|
* {"label": "input", "name": "not-ok-consistent.md"}
|
||||||
*
|
*
|
||||||
* 1:31-1:47: Titles should use `"` as a quote
|
* [Mercury](http://example.com/mercury/ "Go to Mercury") and
|
||||||
|
* ![Venus](http://example.com/venus/ 'Go to Venus').
|
||||||
|
*
|
||||||
|
* [Earth]: http://example.com/earth/ (Go to Earth)
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-consistent.md"}
|
||||||
|
*
|
||||||
|
* 2:1-2:50: Unexpected title markers `'`, expected `"`
|
||||||
|
* 4:1-4:49: Unexpected title markers `'('` and `')'`, expected `"`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "'"}
|
* {"config": "\"", "name": "ok-double.md"}
|
||||||
*
|
*
|
||||||
* [Example](http://example.com#without-title)
|
* [Mercury](http://example.com/mercury/ "Go to Mercury").
|
||||||
* [Example](http://example.com 'Example Domain')
|
|
||||||
* ![Example](http://example.com 'Example Domain')
|
|
||||||
*
|
|
||||||
* [Example]: http://example.com 'Example Domain'
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input", "config": "'"}
|
* {"config": "\"", "label": "input", "name": "not-ok-double.md"}
|
||||||
*
|
*
|
||||||
* [Example]: http://example.com "Example Domain"
|
* [Mercury](http://example.com/mercury/ 'Go to Mercury').
|
||||||
|
* @example
|
||||||
|
* {"config": "\"", "label": "output", "name": "not-ok-double.md"}
|
||||||
|
*
|
||||||
|
* 1:1-1:55: Unexpected title markers `'`, expected `"`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output", "config": "'"}
|
* {"config": "'", "name": "ok-single.md"}
|
||||||
*
|
*
|
||||||
* 1:31-1:47: Titles should use `'` as a quote
|
* [Mercury](http://example.com/mercury/ 'Go to Mercury').
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "()"}
|
* {"config": "'", "label": "input", "name": "not-ok-single.md"}
|
||||||
*
|
*
|
||||||
* [Example](http://example.com#without-title)
|
* [Mercury](http://example.com/mercury/ "Go to Mercury").
|
||||||
* [Example](http://example.com (Example Domain))
|
* @example
|
||||||
* ![Example](http://example.com (Example Domain))
|
* {"config": "'", "label": "output", "name": "not-ok-single.md"}
|
||||||
*
|
*
|
||||||
* [Example]: http://example.com (Example Domain)
|
* 1:1-1:55: Unexpected title markers `"`, expected `'`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input", "config": "()"}
|
* {"config": "()", "name": "ok-paren.md"}
|
||||||
*
|
*
|
||||||
* [Example](http://example.com 'Example Domain')
|
* [Mercury](http://example.com/mercury/ (Go to Mercury)).
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output", "config": "()"}
|
* {"config": "()", "label": "input", "name": "not-ok-paren.md"}
|
||||||
*
|
*
|
||||||
* 1:30-1:46: Titles should use `()` as a quote
|
* [Mercury](http://example.com/mercury/ "Go to Mercury").
|
||||||
|
* @example
|
||||||
|
* {"config": "()", "label": "output", "name": "not-ok-paren.md"}
|
||||||
|
*
|
||||||
|
* 1:1-1:55: Unexpected title markers `"`, expected `'('` and `')'`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"config": "🌍", "label": "output", "name": "not-ok.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* [Example](http://example.com "Example Domain")
|
* 1:1: Unexpected value `🌍` for `options`, expected `'"'`, `"'"`, `'()'`, or `'consistent'`
|
||||||
* [Example](http://example.com 'Example Domain')
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"config": "\"", "name": "ok-parens-in-url.md"}
|
||||||
*
|
*
|
||||||
* 2:30-2:46: Titles should use `"` as a quote
|
* Parens in URLs work correctly:
|
||||||
|
*
|
||||||
|
* [Mercury](http://example.com/(mercury) "Go to Mercury") and
|
||||||
|
* [Venus](http://example.com/(venus)).
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "💩", "label": "output", "positionless": true}
|
* {"config": "\"", "name": "ok-whitespace.md"}
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect link title style marker `💩`: use either `'consistent'`, `'"'`, `'\''`, or `'()'`
|
* Trailing whitespace works correctly:
|
||||||
|
*
|
||||||
|
* [Mercury](http://example.com/mercury/␠"Go to Mercury"␠).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,15 +174,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {location} from 'vfile-location'
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const markers = {
|
|
||||||
'"': '"',
|
|
||||||
"'": "'",
|
|
||||||
')': '('
|
|
||||||
}
|
|
||||||
|
|
||||||
const remarkLintLinkTitleStyle = lintRule(
|
const remarkLintLinkTitleStyle = lintRule(
|
||||||
{
|
{
|
||||||
@ -190,78 +193,88 @@ const remarkLintLinkTitleStyle = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
const loc = location(file)
|
/** @type {Style | undefined} */
|
||||||
const option = options || 'consistent'
|
let expected
|
||||||
// @ts-expect-error: allow `(` too, even though untyped.
|
/** @type {VFileMessage | undefined} */
|
||||||
let look = option === '()' || option === '(' ? ')' : option
|
let cause
|
||||||
|
|
||||||
if (look !== 'consistent' && !Object.hasOwn(markers, look)) {
|
if (options === null || options === undefined || options === 'consistent') {
|
||||||
|
// Empty.
|
||||||
|
/* c8 ignore next 3 */
|
||||||
|
// @ts-expect-error: to do: remove.
|
||||||
|
} else if (options === '(') {
|
||||||
|
expected = '()'
|
||||||
|
} else if (options === '"' || options === "'" || options === '()') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
file.fail(
|
file.fail(
|
||||||
'Incorrect link title style marker `' +
|
'Unexpected value `' +
|
||||||
look +
|
options +
|
||||||
"`: use either `'consistent'`, `'\"'`, `'\\''`, or `'()'`"
|
"` for `options`, expected `'\"'`, `\"'\"`, `'()'`, or `'consistent'`"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
if (
|
if (
|
||||||
node.type === 'definition' ||
|
node.type === 'definition' ||
|
||||||
node.type === 'image' ||
|
node.type === 'image' ||
|
||||||
node.type === 'link'
|
node.type === 'link'
|
||||||
) {
|
) {
|
||||||
const tail =
|
// Exit w/o title.
|
||||||
'children' in node
|
if (!node.title) return
|
||||||
? node.children[node.children.length - 1]
|
|
||||||
: undefined
|
|
||||||
const begin = tail ? pointEnd(tail) : pointStart(node)
|
|
||||||
const end = pointEnd(node)
|
const end = pointEnd(node)
|
||||||
|
let endIndex = end ? end.offset : undefined
|
||||||
|
|
||||||
if (
|
// Exit w/o position.
|
||||||
!begin ||
|
if (!endIndex) return
|
||||||
!end ||
|
|
||||||
typeof begin.offset !== 'number' ||
|
// `)`
|
||||||
typeof end.offset !== 'number'
|
if (node.type !== 'definition') endIndex--
|
||||||
) {
|
|
||||||
return
|
// Whitespace.
|
||||||
|
let before = value.charCodeAt(endIndex - 1)
|
||||||
|
while (before === 9 || before === 32) {
|
||||||
|
endIndex--
|
||||||
|
before = value.charCodeAt(endIndex - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let last = end.offset - 1
|
/** @type {Style | undefined} */
|
||||||
|
const actual =
|
||||||
|
before === 34 /* `"` */
|
||||||
|
? '"'
|
||||||
|
: before === 39 /* `'` */
|
||||||
|
? "'"
|
||||||
|
: before === 41 /* `)` */
|
||||||
|
? '()'
|
||||||
|
: /* c8 ignore next -- we should find a correct marker. */
|
||||||
|
undefined
|
||||||
|
|
||||||
if (node.type !== 'definition') {
|
/* c8 ignore next -- we should find a correct marker. */
|
||||||
last--
|
if (!actual) return
|
||||||
}
|
|
||||||
|
|
||||||
const final = /** @type {keyof markers} */ (value.charAt(last))
|
if (expected) {
|
||||||
|
if (actual !== expected) {
|
||||||
// Exit if the final marker is not a known marker.
|
file.message(
|
||||||
if (!(final in markers)) {
|
'Unexpected title markers ' +
|
||||||
return
|
displayStyle(actual) +
|
||||||
}
|
', expected ' +
|
||||||
|
displayStyle(expected),
|
||||||
const initial = markers[final]
|
{ancestors: [...parents, node], cause, place: node.position}
|
||||||
|
)
|
||||||
// Find the starting delimiter
|
}
|
||||||
const first = value.lastIndexOf(initial, last - 1)
|
} else {
|
||||||
|
expected = actual
|
||||||
// Exit if there’s no starting delimiter, the starting delimiter is before
|
cause = new VFileMessage(
|
||||||
// the start of the node, or if it’s not preceded by whitespace.
|
'Title marker style ' +
|
||||||
if (first <= begin.offset || !/\s/.test(value.charAt(first - 1))) {
|
displayStyle(expected) +
|
||||||
return
|
" first defined for `'consistent'` here",
|
||||||
}
|
{
|
||||||
|
ancestors: [...parents, node],
|
||||||
if (look === 'consistent') {
|
place: node.position,
|
||||||
look = final
|
ruleId: 'link-title-style',
|
||||||
} else if (look !== final) {
|
source: 'remark-lint'
|
||||||
const start = loc.toPoint(first)
|
}
|
||||||
const end = loc.toPoint(last + 1)
|
|
||||||
/* c8 ignore next -- we get here if we have offsets. */
|
|
||||||
const place = start && end ? {start, end} : undefined
|
|
||||||
|
|
||||||
file.message(
|
|
||||||
'Titles should use `' +
|
|
||||||
(look === ')' ? '()' : look) +
|
|
||||||
'` as a quote',
|
|
||||||
place
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,3 +283,13 @@ const remarkLintLinkTitleStyle = lintRule(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export default remarkLintLinkTitleStyle
|
export default remarkLintLinkTitleStyle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Style} style
|
||||||
|
* Style.
|
||||||
|
* @returns {string}
|
||||||
|
* Display.
|
||||||
|
*/
|
||||||
|
function displayStyle(style) {
|
||||||
|
return style === '"' ? '`"`' : style === "'" ? "`'`" : "`'('` and `')'`"
|
||||||
|
}
|
||||||
|
@ -36,8 +36,8 @@
|
|||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"vfile-location": "^5.0.0"
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
@ -50,7 +50,9 @@
|
|||||||
"prettier": true,
|
"prettier": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"capitalized-comments": "off",
|
"capitalized-comments": "off",
|
||||||
"unicorn/prefer-at": "off"
|
"unicorn/prefer-at": "off",
|
||||||
|
"unicorn/prefer-code-point": "off",
|
||||||
|
"unicorn/prefer-switch": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,8 @@
|
|||||||
|
|
||||||
## What is this?
|
## What is this?
|
||||||
|
|
||||||
This package checks the style of link title markers.
|
This package checks the style of link (*and* image and definition) title
|
||||||
|
markers.
|
||||||
|
|
||||||
## When should I use this?
|
## When should I use this?
|
||||||
|
|
||||||
@ -174,143 +175,179 @@ markdown, so it’s recommended to configure this rule with `'"'`.
|
|||||||
|
|
||||||
## Fix
|
## Fix
|
||||||
|
|
||||||
[`remark-stringify`][github-remark-stringify] formats titles with double
|
[`remark-stringify`][github-remark-stringify] formats titles with double
|
||||||
quotes by default.
|
quotes by default.
|
||||||
Pass `quote: "'"` to use single quotes.
|
Pass `quote: "'"` to use single quotes.
|
||||||
There is no option to use parens.
|
There is no option to use parens.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-consistent.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[Mercury](http://example.com/mercury/),
|
||||||
|
[Venus](http://example.com/venus/ "Go to Venus"), and
|
||||||
|
![Earth](http://example.com/earth/ "Go to Earth").
|
||||||
|
|
||||||
|
[Mars]: http://example.com/mars/ "Go to Mars"
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `not-ok-consistent.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[Mercury](http://example.com/mercury/ "Go to Mercury") and
|
||||||
|
![Venus](http://example.com/venus/ 'Go to Venus').
|
||||||
|
|
||||||
|
[Earth]: http://example.com/earth/ (Go to Earth)
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
2:1-2:50: Unexpected title markers `'`, expected `"`
|
||||||
|
4:1-4:49: Unexpected title markers `'('` and `')'`, expected `"`
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `ok-double.md`
|
||||||
|
|
||||||
When configured with `'"'`.
|
When configured with `'"'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example](http://example.com#without-title)
|
[Mercury](http://example.com/mercury/ "Go to Mercury").
|
||||||
[Example](http://example.com "Example Domain")
|
|
||||||
![Example](http://example.com "Example Domain")
|
|
||||||
|
|
||||||
[Example]: http://example.com "Example Domain"
|
|
||||||
|
|
||||||
You can use parens in URLs if they’re not a title (see GH-166):
|
|
||||||
|
|
||||||
[Example](#Heading-(optional))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-double.md`
|
||||||
|
|
||||||
When configured with `'"'`.
|
When configured with `'"'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example]: http://example.com 'Example Domain'
|
[Mercury](http://example.com/mercury/ 'Go to Mercury').
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:31-1:47: Titles should use `"` as a quote
|
1:1-1:55: Unexpected title markers `'`, expected `"`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-single.md`
|
||||||
|
|
||||||
When configured with `"'"`.
|
When configured with `"'"`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example](http://example.com#without-title)
|
[Mercury](http://example.com/mercury/ 'Go to Mercury').
|
||||||
[Example](http://example.com 'Example Domain')
|
|
||||||
![Example](http://example.com 'Example Domain')
|
|
||||||
|
|
||||||
[Example]: http://example.com 'Example Domain'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-single.md`
|
||||||
|
|
||||||
When configured with `"'"`.
|
When configured with `"'"`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example]: http://example.com "Example Domain"
|
[Mercury](http://example.com/mercury/ "Go to Mercury").
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:31-1:47: Titles should use `'` as a quote
|
1:1-1:55: Unexpected title markers `"`, expected `'`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.md`
|
##### `ok-paren.md`
|
||||||
|
|
||||||
When configured with `'()'`.
|
When configured with `'()'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example](http://example.com#without-title)
|
[Mercury](http://example.com/mercury/ (Go to Mercury)).
|
||||||
[Example](http://example.com (Example Domain))
|
|
||||||
![Example](http://example.com (Example Domain))
|
|
||||||
|
|
||||||
[Example]: http://example.com (Example Domain)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok-paren.md`
|
||||||
|
|
||||||
When configured with `'()'`.
|
When configured with `'()'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example](http://example.com 'Example Domain')
|
[Mercury](http://example.com/mercury/ "Go to Mercury").
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:30-1:46: Titles should use `()` as a quote
|
1:1-1:55: Unexpected title markers `"`, expected `'('` and `')'`
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options`, expected `'"'`, `"'"`, `'()'`, or `'consistent'`
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `ok-parens-in-url.md`
|
||||||
|
|
||||||
|
When configured with `'"'`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[Example](http://example.com "Example Domain")
|
Parens in URLs work correctly:
|
||||||
[Example](http://example.com 'Example Domain')
|
|
||||||
|
[Mercury](http://example.com/(mercury) "Go to Mercury") and
|
||||||
|
[Venus](http://example.com/(venus)).
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
No messages.
|
||||||
2:30-2:46: Titles should use `"` as a quote
|
|
||||||
|
##### `ok-whitespace.md`
|
||||||
|
|
||||||
|
When configured with `'"'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Trailing whitespace works correctly:
|
||||||
|
|
||||||
|
[Mercury](http://example.com/mercury/␠"Go to Mercury"␠).
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
|
||||||
|
|
||||||
When configured with `'💩'`.
|
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
No messages.
|
||||||
1:1: Incorrect link title style marker `💩`: use either `'consistent'`, `'"'`, `'\''`, or `'()'`
|
|
||||||
```
|
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@
|
|||||||
* While it is possible to use an indent to align ordered lists on their marker:
|
* While it is possible to use an indent to align ordered lists on their marker:
|
||||||
*
|
*
|
||||||
* ```markdown
|
* ```markdown
|
||||||
* 1. One
|
* 1. Mercury
|
||||||
* 10. Ten
|
* 10. Venus
|
||||||
* 100. Hundred
|
* 100. Earth
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* …such a style is uncommon and hard to maintain as adding a 10th item
|
* …such a style is uncommon and hard to maintain as adding a 10th item
|
||||||
@ -56,34 +56,33 @@
|
|||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Mercury.
|
||||||
*
|
*
|
||||||
* * List item
|
* * Venus.
|
||||||
* * List item
|
* * Earth.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* Mercury.
|
||||||
*
|
*
|
||||||
* ␠* List item
|
* ␠* Venus.
|
||||||
* ␠* List item
|
* ␠* Earth.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 3:2: Incorrect indentation before bullet: remove 1 space
|
* 3:2: Unexpected `1` space before list item, expected `0` spaces, remove them
|
||||||
* 4:2: Incorrect indentation before bullet: remove 1 space
|
* 4:2: Unexpected `1` space before list item, expected `0` spaces, remove them
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import plural from 'pluralize'
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const remarkLintListItemBulletIndent = lintRule(
|
const remarkLintListItemBulletIndent = lintRule(
|
||||||
{
|
{
|
||||||
@ -97,34 +96,37 @@ const remarkLintListItemBulletIndent = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
visit(tree, 'list', function (list, _, grandparent) {
|
const treeStart = pointStart(tree)
|
||||||
let index = -1
|
|
||||||
const pointStartGrandparent = pointStart(grandparent)
|
|
||||||
|
|
||||||
while (++index < list.children.length) {
|
// Unknown containers are not supported.
|
||||||
const item = list.children[index]
|
if (!tree || tree.type !== 'root' || !treeStart) return
|
||||||
const itemStart = pointStart(item)
|
|
||||||
|
|
||||||
if (
|
for (const child of tree.children) {
|
||||||
grandparent &&
|
if (child.type !== 'list') continue
|
||||||
pointStartGrandparent &&
|
|
||||||
itemStart &&
|
|
||||||
grandparent.type === 'root'
|
|
||||||
) {
|
|
||||||
const indent = itemStart.column - pointStartGrandparent.column
|
|
||||||
|
|
||||||
if (indent) {
|
const list = child
|
||||||
file.message(
|
|
||||||
'Incorrect indentation before bullet: remove ' +
|
for (const item of list.children) {
|
||||||
indent +
|
const place = pointStart(item)
|
||||||
' ' +
|
|
||||||
plural('space', indent),
|
/* c8 ignore next 2 -- doesn’t happen in tests as the whole tree is
|
||||||
itemStart
|
* generated. */
|
||||||
)
|
if (!place) continue
|
||||||
}
|
|
||||||
|
const actual = place.column - treeStart.column
|
||||||
|
|
||||||
|
if (actual) {
|
||||||
|
file.message(
|
||||||
|
'Unexpected `' +
|
||||||
|
actual +
|
||||||
|
'` ' +
|
||||||
|
pluralize('space', actual) +
|
||||||
|
' before list item, expected `0` spaces, remove them',
|
||||||
|
{ancestors: [tree, list, item], place}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,8 +35,7 @@
|
|||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -140,9 +140,9 @@ There is no specific handling of indented list items in markdown.
|
|||||||
While it is possible to use an indent to align ordered lists on their marker:
|
While it is possible to use an indent to align ordered lists on their marker:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
1. One
|
1. Mercury
|
||||||
10. Ten
|
10. Venus
|
||||||
100. Hundred
|
100. Earth
|
||||||
```
|
```
|
||||||
|
|
||||||
…such a style is uncommon and hard to maintain as adding a 10th item
|
…such a style is uncommon and hard to maintain as adding a 10th item
|
||||||
@ -162,10 +162,10 @@ indent.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
* List item
|
* Venus.
|
||||||
* List item
|
* Earth.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -177,17 +177,17 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Paragraph.
|
Mercury.
|
||||||
|
|
||||||
␠* List item
|
␠* Venus.
|
||||||
␠* List item
|
␠* Earth.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
3:2: Incorrect indentation before bullet: remove 1 space
|
3:2: Unexpected `1` space before list item, expected `0` spaces, remove them
|
||||||
4:2: Incorrect indentation before bullet: remove 1 space
|
4:2: Unexpected `1` space before list item, expected `0` spaces, remove them
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
* ## What is this?
|
* ## What is this?
|
||||||
*
|
*
|
||||||
* This package checks the indent of list item content.
|
* This package checks the indent of list item content.
|
||||||
|
* It checks the first thing in a list item and makes sure that all other
|
||||||
|
* children have the same indent.
|
||||||
*
|
*
|
||||||
* ## When should I use this?
|
* ## When should I use this?
|
||||||
*
|
*
|
||||||
@ -42,32 +44,76 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "gfm": true}
|
|
||||||
*
|
|
||||||
* 1.␠[x] Alpha
|
|
||||||
* ␠␠␠1. Bravo
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input", "gfm": true}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* 1.␠[x] Charlie
|
* 1.␠Mercury.
|
||||||
* ␠␠␠␠1. Delta
|
* ␠␠␠***
|
||||||
|
* ␠␠␠* Venus.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output", "gfm": true}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 2:5: Don’t use mixed indentation for children, remove 1 space
|
* 1.␠Mercury.
|
||||||
|
* ␠␠␠␠␠***
|
||||||
|
* ␠␠␠␠* Venus.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 2:6: Unexpected unaligned list item child, expected to align with first child, remove `2` spaces
|
||||||
|
* 3:5: Unexpected unaligned list item child, expected to align with first child, remove `1` space
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"name": "ok-more.md"}
|
||||||
|
*
|
||||||
|
* *␠␠␠Mercury.
|
||||||
|
* ␠␠␠␠***
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "not-ok-more.md"}
|
||||||
|
*
|
||||||
|
* *␠␠␠Mercury.
|
||||||
|
* ␠␠␠␠␠␠***
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-more.md"}
|
||||||
|
*
|
||||||
|
* 2:7: Unexpected unaligned list item child, expected to align with first child, remove `2` spaces
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "gfm": true, "name": "gfm-nok.md"}
|
||||||
|
*
|
||||||
|
* 1.␠[x] Mercury
|
||||||
|
* ␠␠␠␠␠***
|
||||||
|
* ␠␠␠␠* Venus
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "gfm": true, "name": "gfm-nok.md"}
|
||||||
|
*
|
||||||
|
* 2:6: Unexpected unaligned list item child, expected to align with first child, remove `2` spaces
|
||||||
|
* 3:5: Unexpected unaligned list item child, expected to align with first child, remove `1` space
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "initial-blank.md"}
|
||||||
|
*
|
||||||
|
* *
|
||||||
|
* ␠␠␠␠␠asd
|
||||||
|
*
|
||||||
|
* ␠␠***
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "initial-blank.md"}
|
||||||
|
*
|
||||||
|
* 4:3: Unexpected unaligned list item child, expected to align with first child, add `3` spaces
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import plural from 'pluralize'
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
const remarkLintListItemContentIndent = lintRule(
|
const remarkLintListItemContentIndent = lintRule(
|
||||||
{
|
{
|
||||||
@ -82,57 +128,67 @@ const remarkLintListItemContentIndent = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
|
/** @type {VFileMessage | undefined} */
|
||||||
|
let cause
|
||||||
|
|
||||||
visit(tree, 'listItem', function (node) {
|
visitParents(tree, 'listItem', function (node, parents) {
|
||||||
let index = -1
|
let index = -1
|
||||||
/** @type {number | undefined} */
|
/** @type {number | undefined} */
|
||||||
let style
|
let expected
|
||||||
|
|
||||||
while (++index < node.children.length) {
|
while (++index < node.children.length) {
|
||||||
const item = node.children[index]
|
const child = node.children[index]
|
||||||
const begin = pointStart(item)
|
const childStart = pointStart(child)
|
||||||
|
|
||||||
if (!begin || typeof begin.offset !== 'number') {
|
if (!childStart || typeof childStart.offset !== 'number') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let column = begin.column
|
let actual = childStart.column
|
||||||
|
|
||||||
// Get indentation for the first child.
|
// Get indentation for the first child.
|
||||||
// Only the first item can have a checkbox, so here we remove that from
|
// Only the first item can have a checkbox,
|
||||||
// the column.
|
// when it’s a paragraph,
|
||||||
if (index === 0) {
|
// so here we remove that from the column.
|
||||||
// If there’s a checkbox before the content, look backwards to find
|
if (index === 0 && typeof node.checked === 'boolean') {
|
||||||
// the start of that checkbox.
|
let beforeIndex = childStart.offset - 1
|
||||||
if (typeof node.checked === 'boolean') {
|
|
||||||
let char = begin.offset - 1
|
|
||||||
|
|
||||||
while (char > 0 && value.charAt(char) !== '[') {
|
while (
|
||||||
char--
|
beforeIndex > 0 &&
|
||||||
}
|
value.charCodeAt(beforeIndex) !== 91 /* `[` */
|
||||||
|
) {
|
||||||
column -= begin.offset - char
|
beforeIndex--
|
||||||
}
|
}
|
||||||
|
|
||||||
style = column
|
actual -= childStart.offset - beforeIndex
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn for violating children.
|
if (expected) {
|
||||||
if (style && column !== style) {
|
// Warn for violating children.
|
||||||
const diff = style - column
|
if (actual !== expected) {
|
||||||
const abs = Math.abs(diff)
|
const difference = expected - actual
|
||||||
|
const differenceAbsolute = Math.abs(difference)
|
||||||
|
|
||||||
file.message(
|
file.message(
|
||||||
'Don’t use mixed indentation for children, ' +
|
'Unexpected unaligned list item child, expected to align with first child, ' +
|
||||||
/* c8 ignore next -- hard to test, I couldn’t find it at least. */
|
(difference > 0 ? 'add' : 'remove') +
|
||||||
(diff > 0 ? 'add' : 'remove') +
|
' `' +
|
||||||
' ' +
|
differenceAbsolute +
|
||||||
abs +
|
'` ' +
|
||||||
' ' +
|
pluralize('space', differenceAbsolute),
|
||||||
plural('space', abs),
|
{ancestors: [...parents, node, child], cause, place: childStart}
|
||||||
{line: begin.line, column}
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expected = actual
|
||||||
|
cause = new VFileMessage(
|
||||||
|
'Alignment of first child first defined here',
|
||||||
|
{
|
||||||
|
ancestors: [...parents, node, child],
|
||||||
|
place: childStart,
|
||||||
|
ruleId: 'list-item-content-indent',
|
||||||
|
source: 'remark-lint'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,8 @@
|
|||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
@ -49,7 +50,8 @@
|
|||||||
"xo": {
|
"xo": {
|
||||||
"prettier": true,
|
"prettier": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"capitalized-comments": "off"
|
"capitalized-comments": "off",
|
||||||
|
"unicorn/prefer-code-point": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ consistent.
|
|||||||
## What is this?
|
## What is this?
|
||||||
|
|
||||||
This package checks the indent of list item content.
|
This package checks the indent of list item content.
|
||||||
|
It checks the first thing in a list item and makes sure that all other
|
||||||
|
children have the same indent.
|
||||||
|
|
||||||
## When should I use this?
|
## When should I use this?
|
||||||
|
|
||||||
@ -151,12 +153,10 @@ Further children should align with it.
|
|||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
> 👉 **Note**: this example uses
|
|
||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
1.␠[x] Alpha
|
1.␠Mercury.
|
||||||
␠␠␠1. Bravo
|
␠␠␠***
|
||||||
|
␠␠␠* Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -167,18 +167,82 @@ No messages.
|
|||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
> 👉 **Note**: this example uses
|
|
||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
1.␠[x] Charlie
|
1.␠Mercury.
|
||||||
␠␠␠␠1. Delta
|
␠␠␠␠␠***
|
||||||
|
␠␠␠␠* Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:5: Don’t use mixed indentation for children, remove 1 space
|
2:6: Unexpected unaligned list item child, expected to align with first child, remove `2` spaces
|
||||||
|
3:5: Unexpected unaligned list item child, expected to align with first child, remove `1` space
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `ok-more.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠␠␠Mercury.
|
||||||
|
␠␠␠␠***
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `not-ok-more.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠␠␠Mercury.
|
||||||
|
␠␠␠␠␠␠***
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
2:7: Unexpected unaligned list item child, expected to align with first child, remove `2` spaces
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `gfm-nok.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
1.␠[x] Mercury
|
||||||
|
␠␠␠␠␠***
|
||||||
|
␠␠␠␠* Venus
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
2:6: Unexpected unaligned list item child, expected to align with first child, remove `2` spaces
|
||||||
|
3:5: Unexpected unaligned list item child, expected to align with first child, remove `1` space
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `initial-blank.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*
|
||||||
|
␠␠␠␠␠asd
|
||||||
|
|
||||||
|
␠␠***
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
4:3: Unexpected unaligned list item child, expected to align with first child, add `3` spaces
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -92,114 +92,204 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* *␠List
|
* *␠Mercury.
|
||||||
* ␠␠item.
|
* *␠Venus.
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* 111.␠Earth
|
||||||
|
* ␠␠␠␠␠and Mars.
|
||||||
*
|
*
|
||||||
* 11.␠List
|
* *␠**Jupiter**.
|
||||||
* ␠␠␠␠item.
|
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* ␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
* ␠␠System.
|
||||||
*
|
*
|
||||||
* *␠List
|
* *␠Saturn.
|
||||||
* ␠␠item.
|
|
||||||
*
|
*
|
||||||
* *␠List
|
* ␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
* ␠␠item.
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "mixed"}
|
* {"config": "mixed", "name": "ok.md"}
|
||||||
*
|
*
|
||||||
* *␠List item.
|
* *␠Mercury.
|
||||||
|
* *␠Venus.
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* 111.␠Earth
|
||||||
|
* ␠␠␠␠␠and Mars.
|
||||||
*
|
*
|
||||||
* 11.␠List item
|
* *␠␠␠**Jupiter**.
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* ␠␠␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
* ␠␠␠␠System.
|
||||||
*
|
*
|
||||||
* *␠␠␠List
|
* *␠␠␠Saturn.
|
||||||
* ␠␠␠␠item.
|
|
||||||
*
|
*
|
||||||
* *␠␠␠List
|
* ␠␠␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
* ␠␠␠␠item.
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": "one"}
|
* {"config": "mixed", "label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* *␠List item.
|
* *␠␠␠Mercury.
|
||||||
|
* *␠␠␠Venus.
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* 111.␠␠␠␠Earth
|
||||||
|
* ␠␠␠␠␠␠␠␠and Mars.
|
||||||
*
|
*
|
||||||
* 11.␠List item
|
* *␠**Jupiter**.
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* ␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
* ␠␠System.
|
||||||
*
|
*
|
||||||
* *␠List
|
* *␠Saturn.
|
||||||
* ␠␠item.
|
|
||||||
*
|
*
|
||||||
* *␠List
|
* ␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
* ␠␠item.
|
* @example
|
||||||
|
* {"config": "mixed", "label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 1:5: Unexpected `3` spaces between list item marker and content in tight list, expected `1` space, remove `2` spaces
|
||||||
|
* 2:5: Unexpected `3` spaces between list item marker and content in tight list, expected `1` space, remove `2` spaces
|
||||||
|
* 4:9: Unexpected `4` spaces between list item marker and content in tight list, expected `1` space, remove `3` spaces
|
||||||
|
* 7:3: Unexpected `1` space between list item marker and content in loose list, expected `3` spaces, add `2` spaces
|
||||||
|
* 12:3: Unexpected `1` space between list item marker and content in loose list, expected `3` spaces, add `2` spaces
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "one", "name": "ok.md"}
|
||||||
|
*
|
||||||
|
* *␠Mercury.
|
||||||
|
* *␠Venus.
|
||||||
|
*
|
||||||
|
* 111.␠Earth
|
||||||
|
* ␠␠␠␠␠and Mars.
|
||||||
|
*
|
||||||
|
* *␠**Jupiter**.
|
||||||
|
*
|
||||||
|
* ␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
* ␠␠System.
|
||||||
|
*
|
||||||
|
* *␠Saturn.
|
||||||
|
*
|
||||||
|
* ␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "one", "label": "input", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* *␠␠␠Mercury.
|
||||||
|
* *␠␠␠Venus.
|
||||||
|
*
|
||||||
|
* 111.␠␠␠␠Earth
|
||||||
|
* ␠␠␠␠␠␠␠␠and Mars.
|
||||||
|
*
|
||||||
|
* *␠␠␠**Jupiter**.
|
||||||
|
*
|
||||||
|
* ␠␠␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
* ␠␠␠␠System.
|
||||||
|
*
|
||||||
|
* *␠␠␠Saturn.
|
||||||
|
*
|
||||||
|
* ␠␠␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
|
* @example
|
||||||
|
* {"config": "one", "label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 1:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
|
* 2:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
|
* 4:9: Unexpected `4` spaces between list item marker and content, expected `1` space, remove `3` spaces
|
||||||
|
* 7:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
|
* 12:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": "tab", "name": "ok.md"}
|
* {"config": "tab", "name": "ok.md"}
|
||||||
*
|
*
|
||||||
* *␠␠␠List
|
* *␠␠␠Mercury.
|
||||||
* ␠␠␠␠item.
|
* *␠␠␠Venus.
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* 111.␠␠␠␠Earth
|
||||||
|
* ␠␠␠␠␠␠␠␠and Mars.
|
||||||
*
|
*
|
||||||
* 11.␠List
|
* *␠␠␠**Jupiter**.
|
||||||
* ␠␠␠␠item.
|
|
||||||
*
|
*
|
||||||
* Paragraph.
|
* ␠␠␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
* ␠␠␠␠System.
|
||||||
*
|
*
|
||||||
* *␠␠␠List
|
* *␠␠␠Saturn.
|
||||||
* ␠␠␠␠item.
|
|
||||||
*
|
*
|
||||||
* *␠␠␠List
|
* ␠␠␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
* ␠␠␠␠item.
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "one", "label": "input"}
|
* {"config": "tab", "label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* *␠␠␠List
|
* *␠Mercury.
|
||||||
* ␠␠␠␠item.
|
* *␠Venus.
|
||||||
|
*
|
||||||
|
* 111.␠Earth
|
||||||
|
* ␠␠␠␠␠and Mars.
|
||||||
|
*
|
||||||
|
* *␠**Jupiter**.
|
||||||
|
*
|
||||||
|
* ␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
* ␠␠System.
|
||||||
|
*
|
||||||
|
* *␠Saturn.
|
||||||
|
*
|
||||||
|
* ␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
|
* @example
|
||||||
|
* {"config": "tab", "label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 1:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
|
* 2:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
|
* 4:6: Unexpected `1` space between list item marker and content, expected `4` spaces, add `3` spaces
|
||||||
|
* 7:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
|
* 12:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "one", "label": "output"}
|
* {"config": "🌍", "label": "output", "name": "not-ok.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:5: Incorrect list-item indent: remove 2 spaces
|
* 1:1: Unexpected value `🌍` for `options`, expected `'mixed'`, `'one'`, or `'tab'`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "tab", "label": "input"}
|
* {"config": "mixed", "gfm": true, "label": "input", "name": "gfm.md"}
|
||||||
*
|
*
|
||||||
* *␠List
|
* *␠[x] Mercury.
|
||||||
* ␠␠item.
|
*
|
||||||
|
* 1.␠␠[ ] Venus.
|
||||||
|
*
|
||||||
|
* 2.␠␠[ ] Earth.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "tab", "label": "output"}
|
* {"config": "one", "gfm": true, "name": "gfm.md"}
|
||||||
*
|
*
|
||||||
* 1:3: Incorrect list-item indent: add 2 spaces
|
* *␠[x] Mercury.
|
||||||
|
*
|
||||||
|
* 1.␠[ ] Venus.
|
||||||
|
*
|
||||||
|
* 2.␠[ ] Earth.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "mixed", "label": "input"}
|
* {"config": "tab", "gfm": true, "name": "gfm.md"}
|
||||||
*
|
*
|
||||||
* *␠␠␠List item.
|
* *␠␠␠[x] Mercury.
|
||||||
|
*
|
||||||
|
* 1.␠␠[ ] Venus.
|
||||||
|
*
|
||||||
|
* 2.␠␠[ ] Earth.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": "mixed", "label": "output"}
|
* {"config": "mixed", "name": "loose-tight.md"}
|
||||||
*
|
*
|
||||||
* 1:5: Incorrect list-item indent: remove 2 spaces
|
* Loose lists have blank lines between items:
|
||||||
*
|
*
|
||||||
* @example
|
* *␠␠␠Mercury.
|
||||||
* {"name": "not-ok.md", "config": "💩", "label": "output", "positionless": true}
|
|
||||||
*
|
*
|
||||||
* 1:1: Incorrect list-item indent style `💩`: use either `'mixed'`, `'one'`, or `'tab'`
|
* *␠␠␠Venus.
|
||||||
|
*
|
||||||
|
* …or between children of items:
|
||||||
|
*
|
||||||
|
* 1.␠␠Earth.
|
||||||
|
*
|
||||||
|
* ␠␠␠␠Earth is the third planet from the Sun and the only astronomical
|
||||||
|
* ␠␠␠␠object known to harbor life.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -211,10 +301,10 @@
|
|||||||
* Configuration.
|
* Configuration.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import plural from 'pluralize'
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart} from 'unist-util-position'
|
import {pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
|
||||||
const remarkLintListItemIndent = lintRule(
|
const remarkLintListItemIndent = lintRule(
|
||||||
{
|
{
|
||||||
@ -231,70 +321,106 @@ const remarkLintListItemIndent = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
const option = options || 'one'
|
/** @type {Options} */
|
||||||
|
let expected
|
||||||
|
|
||||||
/* c8 ignore next 13 -- previous names. */
|
if (options === null || options === undefined) {
|
||||||
// @ts-expect-error: old name.
|
expected = 'one'
|
||||||
if (option === 'space') {
|
/* c8 ignore next 10 -- previous names. */
|
||||||
|
// @ts-expect-error: old name.
|
||||||
|
} else if (options === 'space') {
|
||||||
file.fail(
|
file.fail(
|
||||||
'Incorrect list-item indent style `' + option + "`: use `'one'` instead"
|
'Unexpected value `' + options + "` for `options`, expected `'one'`"
|
||||||
|
)
|
||||||
|
// @ts-expect-error: old name.
|
||||||
|
} else if (options === 'tab-size') {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' + options + "` for `options`, expected `'tab'`"
|
||||||
|
)
|
||||||
|
} else if (options === 'mixed' || options === 'one' || options === 'tab') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' +
|
||||||
|
options +
|
||||||
|
"` for `options`, expected `'mixed'`, `'one'`, or `'tab'`"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error: old name.
|
visitParents(tree, 'list', function (list, parents) {
|
||||||
if (option === 'tab-size') {
|
let loose = list.spread
|
||||||
file.fail(
|
|
||||||
'Incorrect list-item indent style `' + option + "`: use `'tab'` instead"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option !== 'mixed' && option !== 'one' && option !== 'tab') {
|
if (!loose) {
|
||||||
file.fail(
|
for (const item of list.children) {
|
||||||
'Incorrect list-item indent style `' +
|
if (item.spread) {
|
||||||
option +
|
loose = true
|
||||||
"`: use either `'mixed'`, `'one'`, or `'tab'`"
|
break
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
visit(tree, 'list', function (node) {
|
for (const item of list.children) {
|
||||||
const spread = node.spread
|
|
||||||
let index = -1
|
|
||||||
|
|
||||||
while (++index < node.children.length) {
|
|
||||||
const item = node.children[index]
|
|
||||||
const head = item.children[0]
|
const head = item.children[0]
|
||||||
const start = pointStart(item)
|
const itemStart = pointStart(item)
|
||||||
const final = pointStart(head)
|
const headStart = pointStart(head)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
start &&
|
itemStart &&
|
||||||
final &&
|
headStart &&
|
||||||
typeof start.offset === 'number' &&
|
typeof itemStart.offset === 'number' &&
|
||||||
typeof final.offset === 'number'
|
typeof headStart.offset === 'number'
|
||||||
) {
|
) {
|
||||||
const marker = value
|
let slice = value.slice(itemStart.offset, headStart.offset)
|
||||||
.slice(start.offset, final.offset)
|
|
||||||
.replace(/\[[x ]?]\s*$/i, '')
|
|
||||||
|
|
||||||
const bulletSize = marker.replace(/\s+$/, '').length
|
// GFM tasklist.
|
||||||
|
const checkboxIndex = slice.indexOf('[')
|
||||||
|
if (checkboxIndex !== -1) slice = slice.slice(0, checkboxIndex)
|
||||||
|
|
||||||
const style =
|
const actualIndent = slice.length
|
||||||
option === 'tab' || (option === 'mixed' && spread)
|
|
||||||
? Math.ceil(bulletSize / 4) * 4
|
|
||||||
: bulletSize + 1
|
|
||||||
|
|
||||||
if (marker.length !== style) {
|
// To do: actual hard tabs?
|
||||||
const diff = style - marker.length
|
// Remove whitespace.
|
||||||
const abs = Math.abs(diff)
|
let end = actualIndent
|
||||||
|
let previous = slice.charCodeAt(end - 1)
|
||||||
|
|
||||||
|
while (previous === 9 || previous === 32) {
|
||||||
|
end--
|
||||||
|
previous = slice.charCodeAt(end - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let expectedIndent = end + 1 // One space needed after marker.
|
||||||
|
|
||||||
|
if (expected === 'tab' || (expected === 'mixed' && loose)) {
|
||||||
|
expectedIndent = Math.ceil(expectedIndent / 4) * 4
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedSpaces = expectedIndent - end
|
||||||
|
const actualSpaces = actualIndent - end
|
||||||
|
|
||||||
|
if (actualSpaces !== expectedSpaces) {
|
||||||
|
const difference = expectedSpaces - actualSpaces
|
||||||
|
const differenceAbsolute = Math.abs(difference)
|
||||||
|
|
||||||
file.message(
|
file.message(
|
||||||
'Incorrect list-item indent: ' +
|
'Unexpected `' +
|
||||||
(diff > 0 ? 'add' : 'remove') +
|
actualSpaces +
|
||||||
' ' +
|
'` ' +
|
||||||
abs +
|
pluralize('space', actualSpaces) +
|
||||||
' ' +
|
' between list item marker and content' +
|
||||||
plural('space', abs),
|
(expected === 'mixed'
|
||||||
final
|
? ' in ' + (loose ? 'loose' : 'tight') + ' list'
|
||||||
|
: '') +
|
||||||
|
', expected `' +
|
||||||
|
expectedSpaces +
|
||||||
|
'` ' +
|
||||||
|
pluralize('space', expectedSpaces) +
|
||||||
|
', ' +
|
||||||
|
(difference > 0 ? 'add' : 'remove') +
|
||||||
|
' `' +
|
||||||
|
differenceAbsolute +
|
||||||
|
'` ' +
|
||||||
|
pluralize('space', differenceAbsolute),
|
||||||
|
{ancestors: [...parents, list, item], place: headStart}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
@ -48,7 +48,9 @@
|
|||||||
"xo": {
|
"xo": {
|
||||||
"prettier": true,
|
"prettier": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"capitalized-comments": "off"
|
"capitalized-comments": "off",
|
||||||
|
"unicorn/prefer-code-point": "off",
|
||||||
|
"unicorn/prefer-switch": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,21 +203,20 @@ by default.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
*␠List
|
*␠Mercury.
|
||||||
␠␠item.
|
*␠Venus.
|
||||||
|
|
||||||
Paragraph.
|
111.␠Earth
|
||||||
|
␠␠␠␠␠and Mars.
|
||||||
|
|
||||||
11.␠List
|
*␠**Jupiter**.
|
||||||
␠␠␠␠item.
|
|
||||||
|
|
||||||
Paragraph.
|
␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
␠␠System.
|
||||||
|
|
||||||
*␠List
|
*␠Saturn.
|
||||||
␠␠item.
|
|
||||||
|
|
||||||
*␠List
|
␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
␠␠item.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -231,113 +230,26 @@ When configured with `'mixed'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
*␠List item.
|
*␠Mercury.
|
||||||
|
*␠Venus.
|
||||||
|
|
||||||
Paragraph.
|
111.␠Earth
|
||||||
|
␠␠␠␠␠and Mars.
|
||||||
|
|
||||||
11.␠List item
|
*␠␠␠**Jupiter**.
|
||||||
|
|
||||||
Paragraph.
|
␠␠␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
␠␠␠␠System.
|
||||||
|
|
||||||
*␠␠␠List
|
*␠␠␠Saturn.
|
||||||
␠␠␠␠item.
|
|
||||||
|
|
||||||
*␠␠␠List
|
␠␠␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
␠␠␠␠item.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `ok.md`
|
|
||||||
|
|
||||||
When configured with `'one'`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
*␠List item.
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
|
|
||||||
11.␠List item
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
|
|
||||||
*␠List
|
|
||||||
␠␠item.
|
|
||||||
|
|
||||||
*␠List
|
|
||||||
␠␠item.
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
No messages.
|
|
||||||
|
|
||||||
##### `ok.md`
|
|
||||||
|
|
||||||
When configured with `'tab'`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
*␠␠␠List
|
|
||||||
␠␠␠␠item.
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
|
|
||||||
11.␠List
|
|
||||||
␠␠␠␠item.
|
|
||||||
|
|
||||||
Paragraph.
|
|
||||||
|
|
||||||
*␠␠␠List
|
|
||||||
␠␠␠␠item.
|
|
||||||
|
|
||||||
*␠␠␠List
|
|
||||||
␠␠␠␠item.
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
No messages.
|
|
||||||
|
|
||||||
##### `not-ok.md`
|
|
||||||
|
|
||||||
When configured with `'one'`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
*␠␠␠List
|
|
||||||
␠␠␠␠item.
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:5: Incorrect list-item indent: remove 2 spaces
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `not-ok.md`
|
|
||||||
|
|
||||||
When configured with `'tab'`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
*␠List
|
|
||||||
␠␠item.
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:3: Incorrect list-item indent: add 2 spaces
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
When configured with `'mixed'`.
|
When configured with `'mixed'`.
|
||||||
@ -345,25 +257,250 @@ When configured with `'mixed'`.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
*␠␠␠List item.
|
*␠␠␠Mercury.
|
||||||
|
*␠␠␠Venus.
|
||||||
|
|
||||||
|
111.␠␠␠␠Earth
|
||||||
|
␠␠␠␠␠␠␠␠and Mars.
|
||||||
|
|
||||||
|
*␠**Jupiter**.
|
||||||
|
|
||||||
|
␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
␠␠System.
|
||||||
|
|
||||||
|
*␠Saturn.
|
||||||
|
|
||||||
|
␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:5: Incorrect list-item indent: remove 2 spaces
|
1:5: Unexpected `3` spaces between list item marker and content in tight list, expected `1` space, remove `2` spaces
|
||||||
|
2:5: Unexpected `3` spaces between list item marker and content in tight list, expected `1` space, remove `2` spaces
|
||||||
|
4:9: Unexpected `4` spaces between list item marker and content in tight list, expected `1` space, remove `3` spaces
|
||||||
|
7:3: Unexpected `1` space between list item marker and content in loose list, expected `3` spaces, add `2` spaces
|
||||||
|
12:3: Unexpected `1` space between list item marker and content in loose list, expected `3` spaces, add `2` spaces
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `ok.md`
|
||||||
|
|
||||||
|
When configured with `'one'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠Mercury.
|
||||||
|
*␠Venus.
|
||||||
|
|
||||||
|
111.␠Earth
|
||||||
|
␠␠␠␠␠and Mars.
|
||||||
|
|
||||||
|
*␠**Jupiter**.
|
||||||
|
|
||||||
|
␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
␠␠System.
|
||||||
|
|
||||||
|
*␠Saturn.
|
||||||
|
|
||||||
|
␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `not-ok.md`
|
||||||
|
|
||||||
|
When configured with `'one'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠␠␠Mercury.
|
||||||
|
*␠␠␠Venus.
|
||||||
|
|
||||||
|
111.␠␠␠␠Earth
|
||||||
|
␠␠␠␠␠␠␠␠and Mars.
|
||||||
|
|
||||||
|
*␠␠␠**Jupiter**.
|
||||||
|
|
||||||
|
␠␠␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
␠␠␠␠System.
|
||||||
|
|
||||||
|
*␠␠␠Saturn.
|
||||||
|
|
||||||
|
␠␠␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
|
2:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
|
4:9: Unexpected `4` spaces between list item marker and content, expected `1` space, remove `3` spaces
|
||||||
|
7:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
|
12:5: Unexpected `3` spaces between list item marker and content, expected `1` space, remove `2` spaces
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `ok.md`
|
||||||
|
|
||||||
|
When configured with `'tab'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠␠␠Mercury.
|
||||||
|
*␠␠␠Venus.
|
||||||
|
|
||||||
|
111.␠␠␠␠Earth
|
||||||
|
␠␠␠␠␠␠␠␠and Mars.
|
||||||
|
|
||||||
|
*␠␠␠**Jupiter**.
|
||||||
|
|
||||||
|
␠␠␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
␠␠␠␠System.
|
||||||
|
|
||||||
|
*␠␠␠Saturn.
|
||||||
|
|
||||||
|
␠␠␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `not-ok.md`
|
||||||
|
|
||||||
|
When configured with `'tab'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠Mercury.
|
||||||
|
*␠Venus.
|
||||||
|
|
||||||
|
111.␠Earth
|
||||||
|
␠␠␠␠␠and Mars.
|
||||||
|
|
||||||
|
*␠**Jupiter**.
|
||||||
|
|
||||||
|
␠␠Jupiter is the fifth planet from the Sun and the largest in the Solar
|
||||||
|
␠␠System.
|
||||||
|
|
||||||
|
*␠Saturn.
|
||||||
|
|
||||||
|
␠␠Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after Jupiter.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
|
2:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
|
4:6: Unexpected `1` space between list item marker and content, expected `4` spaces, add `3` spaces
|
||||||
|
7:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
|
12:3: Unexpected `1` space between list item marker and content, expected `3` spaces, add `2` spaces
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
When configured with `'💩'`.
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Incorrect list-item indent style `💩`: use either `'mixed'`, `'one'`, or `'tab'`
|
1:1: Unexpected value `🌍` for `options`, expected `'mixed'`, `'one'`, or `'tab'`
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### `gfm.md`
|
||||||
|
|
||||||
|
When configured with `'mixed'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠[x] Mercury.
|
||||||
|
|
||||||
|
1.␠␠[ ] Venus.
|
||||||
|
|
||||||
|
2.␠␠[ ] Earth.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `gfm.md`
|
||||||
|
|
||||||
|
When configured with `'one'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠[x] Mercury.
|
||||||
|
|
||||||
|
1.␠[ ] Venus.
|
||||||
|
|
||||||
|
2.␠[ ] Earth.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `gfm.md`
|
||||||
|
|
||||||
|
When configured with `'tab'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
*␠␠␠[x] Mercury.
|
||||||
|
|
||||||
|
1.␠␠[ ] Venus.
|
||||||
|
|
||||||
|
2.␠␠[ ] Earth.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `loose-tight.md`
|
||||||
|
|
||||||
|
When configured with `'mixed'`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Loose lists have blank lines between items:
|
||||||
|
|
||||||
|
*␠␠␠Mercury.
|
||||||
|
|
||||||
|
*␠␠␠Venus.
|
||||||
|
|
||||||
|
…or between children of items:
|
||||||
|
|
||||||
|
1.␠␠Earth.
|
||||||
|
|
||||||
|
␠␠␠␠Earth is the third planet from the Sun and the only astronomical
|
||||||
|
␠␠␠␠object known to harbor life.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Projects maintained by the unified collective are compatible with maintained
|
Projects maintained by the unified collective are compatible with maintained
|
||||||
@ -435,6 +572,8 @@ abide by its terms.
|
|||||||
|
|
||||||
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
||||||
|
|
||||||
|
[github-remark-gfm]: https://github.com/remarkjs/remark-gfm
|
||||||
|
|
||||||
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
||||||
|
|
||||||
[github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
|
[github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
|
||||||
|
@ -65,97 +65,77 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* A tight list:
|
* * Mercury.
|
||||||
|
* * Venus.
|
||||||
*
|
*
|
||||||
* - item 1
|
* + Mercury and
|
||||||
* - item 2
|
* Venus.
|
||||||
* - item 3
|
|
||||||
*
|
*
|
||||||
* A loose list:
|
* + Earth.
|
||||||
*
|
|
||||||
* - Wrapped
|
|
||||||
* item
|
|
||||||
*
|
|
||||||
* - item 2
|
|
||||||
*
|
|
||||||
* - item 3
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"config": {"checkBlanks": true}, "name": "ok-check-blanks.md"}
|
||||||
*
|
*
|
||||||
* A tight list:
|
* * Mercury.
|
||||||
|
* * Venus.
|
||||||
*
|
*
|
||||||
* - Wrapped
|
* + Mercury
|
||||||
* item
|
|
||||||
* - item 2
|
|
||||||
* - item 3
|
|
||||||
*
|
*
|
||||||
* A loose list:
|
* Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
* System.
|
||||||
*
|
*
|
||||||
* - item 1
|
* + Earth.
|
||||||
*
|
|
||||||
* - item 2
|
|
||||||
*
|
|
||||||
* - item 3
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 4:9-5:1: Missing new line after list item
|
* * Mercury.
|
||||||
* 5:11-6:1: Missing new line after list item
|
*
|
||||||
* 10:11-12:1: Extraneous new line after list item
|
* * Venus.
|
||||||
* 12:11-14:1: Extraneous new line after list item
|
*
|
||||||
|
* + Mercury and
|
||||||
|
* Venus.
|
||||||
|
* + Earth.
|
||||||
|
*
|
||||||
|
* * Mercury.
|
||||||
|
*
|
||||||
|
* Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
* System.
|
||||||
|
* * Earth.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 1:11-3:1: Unexpected `1` blank line between list items, expected `0` blank lines, remove `1` blank line
|
||||||
|
* 6:11-7:1: Unexpected `0` blank lines between list items, expected `1` blank line, add `1` blank line
|
||||||
|
* 12:12-13:1: Unexpected `0` blank lines between list items, expected `1` blank line, add `1` blank line
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md", "config": {"checkBlanks": true}}
|
* {"config": {"checkBlanks": true}, "label": "input", "name": "not-ok-blank.md"}
|
||||||
*
|
*
|
||||||
* A tight list:
|
* * Mercury.
|
||||||
*
|
*
|
||||||
* - item 1
|
* * Venus.
|
||||||
* - item 1.A
|
|
||||||
* - item 2
|
|
||||||
* > Block quote
|
|
||||||
*
|
*
|
||||||
* A loose list:
|
* + Mercury and
|
||||||
|
* Venus.
|
||||||
*
|
*
|
||||||
* - item 1
|
* + Earth.
|
||||||
*
|
*
|
||||||
* - item 1.A
|
* * Mercury.
|
||||||
*
|
|
||||||
* - item 2
|
|
||||||
*
|
|
||||||
* > Block quote
|
|
||||||
*
|
*
|
||||||
|
* Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
* System.
|
||||||
|
* * Earth.
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": {"checkBlanks": true}, "label": "input"}
|
* {"config": {"checkBlanks": true}, "label": "output", "name": "not-ok-blank.md"}
|
||||||
*
|
*
|
||||||
* A tight list:
|
* 1:11-3:1: Unexpected `1` blank line between list items, expected `0` blank lines, remove `1` blank line
|
||||||
*
|
* 6:11-8:1: Unexpected `1` blank line between list items, expected `0` blank lines, remove `1` blank line
|
||||||
* - item 1
|
* 13:12-14:1: Unexpected `0` blank lines between list items, expected `1` blank line, add `1` blank line
|
||||||
*
|
|
||||||
* - item 1.A
|
|
||||||
* - item 2
|
|
||||||
*
|
|
||||||
* > Block quote
|
|
||||||
* - item 3
|
|
||||||
*
|
|
||||||
* A loose list:
|
|
||||||
*
|
|
||||||
* - item 1
|
|
||||||
* - item 1.A
|
|
||||||
*
|
|
||||||
* - item 2
|
|
||||||
* > Block quote
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "not-ok.md", "config": {"checkBlanks": true}, "label": "output"}
|
|
||||||
*
|
|
||||||
* 5:15-6:1: Missing new line after list item
|
|
||||||
* 8:18-9:1: Missing new line after list item
|
|
||||||
* 14:15-16:1: Extraneous new line after list item
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,9 +151,11 @@
|
|||||||
* preference (default: `false`).
|
* preference (default: `false`).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd, pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
import {VFileMessage} from 'vfile-message'
|
||||||
|
|
||||||
/** @type {Readonly<Options>} */
|
/** @type {Readonly<Options>} */
|
||||||
const emptyOptions = {}
|
const emptyOptions = {}
|
||||||
@ -193,82 +175,83 @@ const remarkLintListItemSpacing = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const settings = options || emptyOptions
|
const settings = options || emptyOptions
|
||||||
|
// To do: change options. Maybe to `Style = 'markdown' | 'markdown-style-guide'`?
|
||||||
const checkBlanks = settings.checkBlanks || false
|
const checkBlanks = settings.checkBlanks || false
|
||||||
const infer = checkBlanks ? blanksBetween : multiline
|
|
||||||
|
|
||||||
visit(tree, 'list', function (node) {
|
visitParents(tree, 'list', function (list, parents) {
|
||||||
let index = -1
|
/** @type {VFileMessage | undefined} */
|
||||||
let anySpaced = false
|
let spacedCause
|
||||||
|
|
||||||
while (++index < node.children.length) {
|
for (const item of list.children) {
|
||||||
const spaced = infer(node.children[index])
|
/** @type {boolean | null | undefined} */
|
||||||
|
let spaced = false
|
||||||
|
|
||||||
|
if (checkBlanks) {
|
||||||
|
spaced = item.spread
|
||||||
|
} else {
|
||||||
|
const tail = item.children.at(-1)
|
||||||
|
const end = pointEnd(tail)
|
||||||
|
const start = pointStart(item)
|
||||||
|
spaced = end && start && end.line - start.line > 0
|
||||||
|
}
|
||||||
|
|
||||||
if (spaced) {
|
if (spaced) {
|
||||||
anySpaced = true
|
spacedCause = new VFileMessage(
|
||||||
|
'Spaced list item first defined here',
|
||||||
|
{
|
||||||
|
ancestors: [...parents, list, item],
|
||||||
|
place: item.position,
|
||||||
|
ruleId: 'list-item-spacing',
|
||||||
|
source: 'remark-lint'
|
||||||
|
}
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
index = 0 // Skip first.
|
const expected = spacedCause ? 1 : 0
|
||||||
|
/** @type {ListItem | undefined} */
|
||||||
|
let previous
|
||||||
|
|
||||||
while (++index < node.children.length) {
|
for (const item of list.children) {
|
||||||
const previous = node.children[index - 1]
|
|
||||||
const current = node.children[index]
|
|
||||||
const previousEnd = pointEnd(previous)
|
const previousEnd = pointEnd(previous)
|
||||||
const start = pointStart(current)
|
const itemStart = pointStart(item)
|
||||||
|
|
||||||
if (previousEnd && start) {
|
if (previousEnd && itemStart) {
|
||||||
const spaced = start.line - previousEnd.line > 1
|
const actual = itemStart.line - previousEnd.line - 1
|
||||||
|
|
||||||
|
if (actual !== expected) {
|
||||||
|
const difference = expected - actual
|
||||||
|
const differenceAbsolute = Math.abs(difference)
|
||||||
|
|
||||||
if (spaced !== anySpaced) {
|
|
||||||
file.message(
|
file.message(
|
||||||
anySpaced
|
'Unexpected `' +
|
||||||
? 'Missing new line after list item'
|
actual +
|
||||||
: 'Extraneous new line after list item',
|
'` blank ' +
|
||||||
{start: previousEnd, end: start}
|
pluralize('line', actual) +
|
||||||
|
' between list items, expected `' +
|
||||||
|
expected +
|
||||||
|
'` blank ' +
|
||||||
|
pluralize('line', expected) +
|
||||||
|
', ' +
|
||||||
|
(difference > 0 ? 'add' : 'remove') +
|
||||||
|
' `' +
|
||||||
|
differenceAbsolute +
|
||||||
|
'` blank ' +
|
||||||
|
pluralize('line', differenceAbsolute),
|
||||||
|
{
|
||||||
|
ancestors: [...parents, list, item],
|
||||||
|
cause: spacedCause,
|
||||||
|
place: {start: previousEnd, end: itemStart}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previous = item
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export default remarkLintListItemSpacing
|
export default remarkLintListItemSpacing
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ListItem} node
|
|
||||||
* Item.
|
|
||||||
* @returns {boolean}
|
|
||||||
* Whether there is a blank line between one of the children.
|
|
||||||
*/
|
|
||||||
function blanksBetween(node) {
|
|
||||||
let index = 0 // Skip first.
|
|
||||||
|
|
||||||
while (++index < node.children.length) {
|
|
||||||
const previousEnd = pointEnd(node.children[index - 1])
|
|
||||||
const start = pointStart(node.children[index])
|
|
||||||
|
|
||||||
// Note: all children in `listItem`s are flow.
|
|
||||||
if (start && previousEnd && start.line - previousEnd.line > 1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ListItem} node
|
|
||||||
* Item.
|
|
||||||
* @returns {boolean}
|
|
||||||
* Whether `node` spans multiple lines.
|
|
||||||
*/
|
|
||||||
function multiline(node) {
|
|
||||||
const head = node.children[0]
|
|
||||||
const tail = node.children[node.children.length - 1]
|
|
||||||
const end = pointEnd(tail)
|
|
||||||
const start = pointStart(head)
|
|
||||||
|
|
||||||
return Boolean(end && start && end.line - start.line > 0)
|
|
||||||
}
|
|
||||||
|
@ -34,9 +34,11 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
|
"vfile-message": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -174,20 +174,35 @@ all items must be loose.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
A tight list:
|
* Mercury.
|
||||||
|
* Venus.
|
||||||
|
|
||||||
- item 1
|
+ Mercury and
|
||||||
- item 2
|
Venus.
|
||||||
- item 3
|
|
||||||
|
|
||||||
A loose list:
|
+ Earth.
|
||||||
|
```
|
||||||
|
|
||||||
- Wrapped
|
###### Out
|
||||||
item
|
|
||||||
|
|
||||||
- item 2
|
No messages.
|
||||||
|
|
||||||
- item 3
|
##### `ok-check-blanks.md`
|
||||||
|
|
||||||
|
When configured with `{ checkBlanks: true }`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
* Mercury.
|
||||||
|
* Venus.
|
||||||
|
|
||||||
|
+ Mercury
|
||||||
|
|
||||||
|
Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
System.
|
||||||
|
|
||||||
|
+ Earth.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -199,92 +214,58 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
A tight list:
|
* Mercury.
|
||||||
|
|
||||||
- Wrapped
|
* Venus.
|
||||||
item
|
|
||||||
- item 2
|
|
||||||
- item 3
|
|
||||||
|
|
||||||
A loose list:
|
+ Mercury and
|
||||||
|
Venus.
|
||||||
|
+ Earth.
|
||||||
|
|
||||||
- item 1
|
* Mercury.
|
||||||
|
|
||||||
- item 2
|
Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
System.
|
||||||
- item 3
|
* Earth.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
4:9-5:1: Missing new line after list item
|
1:11-3:1: Unexpected `1` blank line between list items, expected `0` blank lines, remove `1` blank line
|
||||||
5:11-6:1: Missing new line after list item
|
6:11-7:1: Unexpected `0` blank lines between list items, expected `1` blank line, add `1` blank line
|
||||||
10:11-12:1: Extraneous new line after list item
|
12:12-13:1: Unexpected `0` blank lines between list items, expected `1` blank line, add `1` blank line
|
||||||
12:11-14:1: Extraneous new line after list item
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.md`
|
##### `not-ok-blank.md`
|
||||||
|
|
||||||
When configured with `{ checkBlanks: true }`.
|
When configured with `{ checkBlanks: true }`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
A tight list:
|
* Mercury.
|
||||||
|
|
||||||
- item 1
|
* Venus.
|
||||||
- item 1.A
|
|
||||||
- item 2
|
|
||||||
> Block quote
|
|
||||||
|
|
||||||
A loose list:
|
+ Mercury and
|
||||||
|
Venus.
|
||||||
|
|
||||||
- item 1
|
+ Earth.
|
||||||
|
|
||||||
- item 1.A
|
* Mercury.
|
||||||
|
|
||||||
- item 2
|
Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
System.
|
||||||
> Block quote
|
* Earth.
|
||||||
```
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
No messages.
|
|
||||||
|
|
||||||
##### `not-ok.md`
|
|
||||||
|
|
||||||
When configured with `{ checkBlanks: true }`.
|
|
||||||
|
|
||||||
###### In
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
A tight list:
|
|
||||||
|
|
||||||
- item 1
|
|
||||||
|
|
||||||
- item 1.A
|
|
||||||
- item 2
|
|
||||||
|
|
||||||
> Block quote
|
|
||||||
- item 3
|
|
||||||
|
|
||||||
A loose list:
|
|
||||||
|
|
||||||
- item 1
|
|
||||||
- item 1.A
|
|
||||||
|
|
||||||
- item 2
|
|
||||||
> Block quote
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5:15-6:1: Missing new line after list item
|
1:11-3:1: Unexpected `1` blank line between list items, expected `0` blank lines, remove `1` blank line
|
||||||
8:18-9:1: Missing new line after list item
|
6:11-8:1: Unexpected `1` blank line between list items, expected `0` blank lines, remove `1` blank line
|
||||||
14:15-16:1: Extraneous new line after list item
|
13:12-14:1: Unexpected `0` blank lines between list items, expected `1` blank line, add `1` blank line
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -43,28 +43,31 @@
|
|||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* # Alpha bravo charlie delta echo foxtrot golf hotel
|
* # Mercury is the first planet from the Sun
|
||||||
*
|
|
||||||
* # ![Alpha bravo charlie delta echo foxtrot golf hotel](http://example.com/nato.png)
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": 40, "label": "input"}
|
* {"config": 30, "label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* # Alpha bravo charlie delta echo foxtrot golf hotel
|
* # Mercury is the first planet from the Sun
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": 40, "label": "output"}
|
* {"config": 30, "label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:52: Use headings shorter than `40`
|
* 1:1-1:43: Unexpected `40` characters in heading, expected at most `30` characters
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"config": 30, "label": "input", "mdx": true, "name": "ok.mdx"}
|
* {"config": 30, "label": "input", "mdx": true, "name": "mdx.mdx"}
|
||||||
*
|
*
|
||||||
* <h1>In MDX, headings are checked too</h1>
|
* <h1>Mercury is the first planet from the Sun</h1>
|
||||||
* @example
|
* @example
|
||||||
* {"config": 30, "label": "output", "mdx": true, "name": "ok.mdx"}
|
* {"config": 30, "label": "output", "mdx": true, "name": "mdx.mdx"}
|
||||||
*
|
*
|
||||||
* 1:1-1:42: Use headings shorter than `30`
|
* 1:1-1:50: Unexpected `40` characters in heading, expected at most `30` characters
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "🌍", "label": "output", "name": "not-ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:1: Unexpected value `🌍` for `options`, expected `number`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,7 +79,7 @@
|
|||||||
import {toString} from 'mdast-util-to-string'
|
import {toString} from 'mdast-util-to-string'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {position} from 'unist-util-position'
|
import {position} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
|
|
||||||
const jsxNameRe = /^h([1-6])$/
|
const jsxNameRe = /^h([1-6])$/
|
||||||
|
|
||||||
@ -94,12 +97,22 @@ const remarkLintMaximumHeadingLength = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const option = options || 60
|
let expected = 60
|
||||||
|
|
||||||
|
if (options === null || options === undefined) {
|
||||||
|
// Empty.
|
||||||
|
} else if (typeof options === 'number') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' + options + '` for `options`, expected `number`'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Note: HTML headings cannot properly be checked,
|
// Note: HTML headings cannot properly be checked,
|
||||||
// because for markdown, blocks are one single raw string.
|
// because for markdown, blocks are one single raw string.
|
||||||
|
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
if (
|
if (
|
||||||
node.type === 'heading' ||
|
node.type === 'heading' ||
|
||||||
((node.type === 'mdxJsxFlowElement' ||
|
((node.type === 'mdxJsxFlowElement' ||
|
||||||
@ -108,10 +121,17 @@ const remarkLintMaximumHeadingLength = lintRule(
|
|||||||
jsxNameRe.test(node.name))
|
jsxNameRe.test(node.name))
|
||||||
) {
|
) {
|
||||||
const place = position(node)
|
const place = position(node)
|
||||||
const codePoints = Array.from(toString(node, {includeHtml: false}))
|
const actual = Array.from(toString(node, {includeHtml: false})).length
|
||||||
|
|
||||||
if (place && codePoints.length > option) {
|
if (place && actual > expected) {
|
||||||
file.message('Use headings shorter than `' + option + '`', place)
|
file.message(
|
||||||
|
'Unexpected `' +
|
||||||
|
actual +
|
||||||
|
'` characters in heading, expected at most `' +
|
||||||
|
expected +
|
||||||
|
'` characters',
|
||||||
|
{ancestors: [...parents, node], place}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -149,9 +149,7 @@ every heading out loud to navigate within a page).
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Alpha bravo charlie delta echo foxtrot golf hotel
|
# Mercury is the first planet from the Sun
|
||||||
|
|
||||||
# ![Alpha bravo charlie delta echo foxtrot golf hotel](http://example.com/nato.png)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -160,21 +158,21 @@ No messages.
|
|||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
When configured with `40`.
|
When configured with `30`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Alpha bravo charlie delta echo foxtrot golf hotel
|
# Mercury is the first planet from the Sun
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:52: Use headings shorter than `40`
|
1:1-1:43: Unexpected `40` characters in heading, expected at most `30` characters
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `ok.mdx`
|
##### `mdx.mdx`
|
||||||
|
|
||||||
When configured with `30`.
|
When configured with `30`.
|
||||||
|
|
||||||
@ -184,13 +182,23 @@ When configured with `30`.
|
|||||||
> MDX ([`remark-mdx`][github-remark-mdx]).
|
> MDX ([`remark-mdx`][github-remark-mdx]).
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
<h1>In MDX, headings are checked too</h1>
|
<h1>Mercury is the first planet from the Sun</h1>
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:42: Use headings shorter than `30`
|
1:1-1:50: Unexpected `40` characters in heading, expected at most `30` characters
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok.md`
|
||||||
|
|
||||||
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options`, expected `number`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -43,80 +43,112 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @example
|
|
||||||
* {"name": "ok.md", "positionless": true, "gfm": true}
|
|
||||||
*
|
|
||||||
* This line is simply not toooooooooooooooooooooooooooooooooooooooooooo
|
|
||||||
* long.
|
|
||||||
*
|
|
||||||
* This is also fine: <http://this-long-url-with-a-long-domain.co.uk/a-long-path?query=variables>
|
|
||||||
*
|
|
||||||
* <http://this-link-is-fine.com>
|
|
||||||
*
|
|
||||||
* `alphaBravoCharlieDeltaEchoFoxtrotGolfHotelIndiaJuliettKiloLimaMikeNovemberOscarPapaQuebec.romeo()`
|
|
||||||
*
|
|
||||||
* [foo](http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables)
|
|
||||||
*
|
|
||||||
* <http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables>
|
|
||||||
*
|
|
||||||
* ![foo](http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables)
|
|
||||||
*
|
|
||||||
* | An | exception | is | line | length | in | long | tables | because | those | can’t | just |
|
|
||||||
* | -- | --------- | -- | ---- | ------ | -- | ---- | ------ | ------- | ----- | ----- | ---- |
|
|
||||||
* | be | helped | | | | | | | | | | . |
|
|
||||||
*
|
|
||||||
* <a><b><i><p><q><s><u>alpha bravo charlie delta echo foxtrot golf</u></s></q></p></i></b></a>
|
|
||||||
*
|
|
||||||
* The following is also fine (note the `.`), because there is no whitespace.
|
|
||||||
*
|
|
||||||
* <http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables>.
|
|
||||||
*
|
|
||||||
* In addition, definitions are also fine:
|
|
||||||
*
|
|
||||||
* [foo]: <http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables>
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": 80, "label": "input", "positionless": true}
|
* {"name": "ok.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* This line is simply not tooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
* Mercury mercury mercury mercury mercury mercury mercury mercury mercury mercury
|
||||||
* long.
|
* mercury.
|
||||||
*
|
*
|
||||||
* Just like thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis one.
|
* Mercury mercury mercury mercury mercury mercury mercury mercury mercury `mercury()`.
|
||||||
*
|
*
|
||||||
* And this one is also very wrong: because the link starts aaaaaaafter the column: <http://line.com>
|
* Mercury mercury mercury mercury mercury mercury mercury mercury mercury <http://localhost>.
|
||||||
*
|
*
|
||||||
* <http://this-long-url-with-a-long-domain-is-not-ok.co.uk/a-long-path?query=variables> and such.
|
* Mercury mercury mercury mercury mercury mercury mercury mercury mercury [mercury](http://localhost).
|
||||||
*
|
*
|
||||||
* And this one is also very wrong: because the code starts aaaaaaafter the column: `alpha.bravo()`
|
* Mercury mercury mercury mercury mercury mercury mercury mercury mercury ![mercury](http://localhost).
|
||||||
*
|
*
|
||||||
* `alphaBravoCharlieDeltaEchoFoxtrotGolfHotelIndiaJuliettKiloLimaMikeNovemberOscar.papa()` and such.
|
* <div>Mercury mercury mercury mercury mercury mercury mercury mercury mercury</div>
|
||||||
|
*
|
||||||
|
* [foo]: http://localhost/mercury/mercury/mercury/mercury/mercury/mercury/mercury/mercury
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "config": 80, "label": "output", "positionless": true}
|
* {"config": 20, "label": "input", "name": "not-ok.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 4:86: Line must be at most 80 characters
|
* Mercury mercury mercury
|
||||||
* 6:99: Line must be at most 80 characters
|
* mercury.
|
||||||
* 8:96: Line must be at most 80 characters
|
*
|
||||||
* 10:97: Line must be at most 80 characters
|
* Mercury mercury mercury `mercury()`.
|
||||||
* 12:99: Line must be at most 80 characters
|
*
|
||||||
|
* Mercury mercury mercury <http://localhost>.
|
||||||
|
*
|
||||||
|
* Mercury mercury mercury [m](example.com).
|
||||||
|
*
|
||||||
|
* Mercury mercury mercury ![m](example.com).
|
||||||
|
*
|
||||||
|
* `mercury()` mercury mercury mercury.
|
||||||
|
*
|
||||||
|
* <http://localhost> mercury.
|
||||||
|
*
|
||||||
|
* [m](example.com) mercury.
|
||||||
|
*
|
||||||
|
* ![m](example.com) mercury.
|
||||||
|
*
|
||||||
|
* Mercury mercury ![m](example.com) mercury.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-mixed-line-endings.md", "config": 10, "positionless": true}
|
* {"config": 20, "label": "output", "name": "not-ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:24: Unexpected `23` character line, expected at most `20` characters, remove `3` characters
|
||||||
|
* 4:37: Unexpected `36` character line, expected at most `20` characters, remove `16` characters
|
||||||
|
* 6:44: Unexpected `43` character line, expected at most `20` characters, remove `23` characters
|
||||||
|
* 8:42: Unexpected `41` character line, expected at most `20` characters, remove `21` characters
|
||||||
|
* 10:43: Unexpected `42` character line, expected at most `20` characters, remove `22` characters
|
||||||
|
* 12:37: Unexpected `36` character line, expected at most `20` characters, remove `16` characters
|
||||||
|
* 14:28: Unexpected `27` character line, expected at most `20` characters, remove `7` characters
|
||||||
|
* 16:26: Unexpected `25` character line, expected at most `20` characters, remove `5` characters
|
||||||
|
* 18:27: Unexpected `26` character line, expected at most `20` characters, remove `6` characters
|
||||||
|
* 20:43: Unexpected `42` character line, expected at most `20` characters, remove `22` characters
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": 20, "frontmatter": true, "name": "ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* ---
|
||||||
|
* description: Mercury mercury mercury mercury.
|
||||||
|
* ---
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": 20, "gfm": true, "name": "ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* | Mercury | Mercury | Mercury |
|
||||||
|
* | ------- | ------- | ------- |
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": 20, "math": true, "name": "ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* $$
|
||||||
|
* L = \frac{1}{2} \rho v^2 S C_L
|
||||||
|
* $$
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": 20, "mdx": true, "name": "ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* export const description = 'Mercury mercury mercury mercury.'
|
||||||
|
*
|
||||||
|
* {description}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": 10, "name": "ok-mixed-line-endings.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 0123456789␍␊0123456789␊01234␍␊01234␊
|
* 0123456789␍␊0123456789␊01234␍␊01234␊
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-mixed-line-endings.md", "config": 10, "label": "input", "positionless": true}
|
* {"config": 10, "label": "input", "name": "not-ok-mixed-line-endings.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 012345678901␍␊012345678901␊01234567890␍␊01234567890␊
|
* 012345678901␍␊012345678901␊01234567890␍␊01234567890␊
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-mixed-line-endings.md", "config": 10, "label": "output", "positionless": true}
|
* {"config": 10, "label": "output", "name": "not-ok-mixed-line-endings.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:13: Line must be at most 10 characters
|
* 1:13: Unexpected `12` character line, expected at most `10` characters, remove `2` characters
|
||||||
* 2:13: Line must be at most 10 characters
|
* 2:13: Unexpected `12` character line, expected at most `10` characters, remove `2` characters
|
||||||
* 3:12: Line must be at most 10 characters
|
* 3:12: Unexpected `11` character line, expected at most `10` characters, remove `1` character
|
||||||
* 4:12: Line must be at most 10 characters
|
* 4:12: Unexpected `11` character line, expected at most `10` characters, remove `1` character
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"config": "🌍", "label": "output", "name": "not-ok.md", "positionless": true}
|
||||||
|
*
|
||||||
|
* 1:1: Unexpected value `🌍` for `options`, expected `number`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,9 +157,10 @@
|
|||||||
|
|
||||||
/// <reference types="mdast-util-mdx" />
|
/// <reference types="mdast-util-mdx" />
|
||||||
|
|
||||||
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd, pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {SKIP, visit} from 'unist-util-visit'
|
||||||
|
|
||||||
const remarkLintMaximumLineLength = lintRule(
|
const remarkLintMaximumLineLength = lintRule(
|
||||||
{
|
{
|
||||||
@ -145,16 +178,29 @@ const remarkLintMaximumLineLength = lintRule(
|
|||||||
function (tree, file, options) {
|
function (tree, file, options) {
|
||||||
const value = String(file)
|
const value = String(file)
|
||||||
const lines = value.split(/\r?\n/)
|
const lines = value.split(/\r?\n/)
|
||||||
const option = options || 80
|
let expected = 80
|
||||||
|
|
||||||
// Allow nodes that cannot be wrapped.
|
if (options === null || options === undefined) {
|
||||||
visit(tree, function (node) {
|
// Empty.
|
||||||
|
} else if (typeof options === 'number') {
|
||||||
|
expected = options
|
||||||
|
} else {
|
||||||
|
file.fail(
|
||||||
|
'Unexpected value `' + options + '` for `options`, expected `number`'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
|
visit(tree, function (node, index, parent) {
|
||||||
|
// Allow nodes that cannot be wrapped.
|
||||||
if (
|
if (
|
||||||
node.type === 'code' ||
|
node.type === 'code' ||
|
||||||
node.type === 'definition' ||
|
node.type === 'definition' ||
|
||||||
node.type === 'heading' ||
|
node.type === 'heading' ||
|
||||||
node.type === 'html' ||
|
node.type === 'html' ||
|
||||||
node.type === 'mdxJsxTextElement' ||
|
node.type === 'math' ||
|
||||||
|
node.type === 'mdxjsEsm' ||
|
||||||
|
node.type === 'mdxFlowExpression' ||
|
||||||
node.type === 'mdxTextExpression' ||
|
node.type === 'mdxTextExpression' ||
|
||||||
node.type === 'table' ||
|
node.type === 'table' ||
|
||||||
// @ts-expect-error: TOML from frontmatter.
|
// @ts-expect-error: TOML from frontmatter.
|
||||||
@ -165,44 +211,52 @@ const remarkLintMaximumLineLength = lintRule(
|
|||||||
const start = pointStart(node)
|
const start = pointStart(node)
|
||||||
|
|
||||||
if (end && start) {
|
if (end && start) {
|
||||||
allowList(start.line - 1, end.line)
|
let line = start.line - 1
|
||||||
|
while (line < end.line) {
|
||||||
|
lines[line++] = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return SKIP
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// Allow text spans to cross the border.
|
|
||||||
visit(tree, function (node, index, parent) {
|
|
||||||
const final = pointEnd(node)
|
|
||||||
const initial = pointStart(node)
|
|
||||||
|
|
||||||
|
// Allow text spans to cross the border.
|
||||||
if (
|
if (
|
||||||
(node.type === 'image' ||
|
node.type === 'image' ||
|
||||||
node.type === 'inlineCode' ||
|
node.type === 'inlineCode' ||
|
||||||
node.type === 'link') &&
|
node.type === 'link'
|
||||||
initial &&
|
|
||||||
final &&
|
|
||||||
parent &&
|
|
||||||
typeof index === 'number'
|
|
||||||
) {
|
) {
|
||||||
// Not allowing when starting after the border, or ending before it.
|
const end = pointEnd(node)
|
||||||
if (initial.column > option || final.column < option) {
|
const start = pointStart(node)
|
||||||
return
|
|
||||||
|
if (end && start && parent && typeof index === 'number') {
|
||||||
|
// Not allowing when starting after the border.
|
||||||
|
if (start.column > expected) return
|
||||||
|
|
||||||
|
// Not allowing when ending before it.
|
||||||
|
if (end.column < expected) return
|
||||||
|
|
||||||
|
const next = parent.children[index + 1]
|
||||||
|
const nextStart = pointStart(next)
|
||||||
|
|
||||||
|
// Not allowing when there’s a following child.
|
||||||
|
if (
|
||||||
|
next &&
|
||||||
|
nextStart &&
|
||||||
|
nextStart.line === start.line &&
|
||||||
|
// Either something with children:
|
||||||
|
(!('value' in next) ||
|
||||||
|
// Or with whitespace:
|
||||||
|
/[ \t]/.test(next.value))
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = start.line - 1
|
||||||
|
while (line < end.line) {
|
||||||
|
lines[line++] = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = parent.children[index + 1]
|
|
||||||
const nextStart = pointStart(next)
|
|
||||||
|
|
||||||
// Not allowing when there’s whitespace after the link.
|
|
||||||
if (
|
|
||||||
next &&
|
|
||||||
nextStart &&
|
|
||||||
nextStart.line === initial.line &&
|
|
||||||
(!('value' in next) || /^(.+?[ \t].+?)/.test(next.value))
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
allowList(initial.line - 1, final.line)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -210,29 +264,25 @@ const remarkLintMaximumLineLength = lintRule(
|
|||||||
let index = -1
|
let index = -1
|
||||||
|
|
||||||
while (++index < lines.length) {
|
while (++index < lines.length) {
|
||||||
const lineLength = lines[index].length
|
const actualBytes = lines[index].length
|
||||||
|
const actualCharacters = Array.from(lines[index]).length
|
||||||
|
const difference = actualCharacters - expected
|
||||||
|
|
||||||
if (lineLength > option) {
|
if (difference > 0) {
|
||||||
file.message('Line must be at most ' + option + ' characters', {
|
file.message(
|
||||||
line: index + 1,
|
'Unexpected `' +
|
||||||
column: lineLength + 1
|
actualCharacters +
|
||||||
})
|
'` character line, expected at most `' +
|
||||||
}
|
expected +
|
||||||
}
|
'` characters, remove `' +
|
||||||
|
difference +
|
||||||
/**
|
'` ' +
|
||||||
* Allowlist from `initial` to `final`, zero-based.
|
pluralize('character', difference),
|
||||||
*
|
{
|
||||||
* @param {number} initial
|
line: index + 1,
|
||||||
* Initial line.
|
column: actualBytes + 1
|
||||||
* @param {number} final
|
}
|
||||||
* Final line.
|
)
|
||||||
* @returns {undefined}
|
|
||||||
* Nothing.
|
|
||||||
*/
|
|
||||||
function allowList(initial, final) {
|
|
||||||
while (initial < final) {
|
|
||||||
lines[initial++] = ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
"mdast-util-mdx": "^3.0.0",
|
"mdast-util-mdx": "^3.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
|
@ -151,38 +151,21 @@ Whether to wrap prose or not is a stylistic choice.
|
|||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
> 👉 **Note**: this example uses
|
|
||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
This line is simply not toooooooooooooooooooooooooooooooooooooooooooo
|
Mercury mercury mercury mercury mercury mercury mercury mercury mercury mercury
|
||||||
long.
|
mercury.
|
||||||
|
|
||||||
This is also fine: <http://this-long-url-with-a-long-domain.co.uk/a-long-path?query=variables>
|
Mercury mercury mercury mercury mercury mercury mercury mercury mercury `mercury()`.
|
||||||
|
|
||||||
<http://this-link-is-fine.com>
|
Mercury mercury mercury mercury mercury mercury mercury mercury mercury <http://localhost>.
|
||||||
|
|
||||||
`alphaBravoCharlieDeltaEchoFoxtrotGolfHotelIndiaJuliettKiloLimaMikeNovemberOscarPapaQuebec.romeo()`
|
Mercury mercury mercury mercury mercury mercury mercury mercury mercury [mercury](http://localhost).
|
||||||
|
|
||||||
[foo](http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables)
|
Mercury mercury mercury mercury mercury mercury mercury mercury mercury ![mercury](http://localhost).
|
||||||
|
|
||||||
<http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables>
|
<div>Mercury mercury mercury mercury mercury mercury mercury mercury mercury</div>
|
||||||
|
|
||||||
![foo](http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables)
|
[foo]: http://localhost/mercury/mercury/mercury/mercury/mercury/mercury/mercury/mercury
|
||||||
|
|
||||||
| An | exception | is | line | length | in | long | tables | because | those | can’t | just |
|
|
||||||
| -- | --------- | -- | ---- | ------ | -- | ---- | ------ | ------- | ----- | ----- | ---- |
|
|
||||||
| be | helped | | | | | | | | | | . |
|
|
||||||
|
|
||||||
<a><b><i><p><q><s><u>alpha bravo charlie delta echo foxtrot golf</u></s></q></p></i></b></a>
|
|
||||||
|
|
||||||
The following is also fine (note the `.`), because there is no whitespace.
|
|
||||||
|
|
||||||
<http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables>.
|
|
||||||
|
|
||||||
In addition, definitions are also fine:
|
|
||||||
|
|
||||||
[foo]: <http://this-long-url-with-a-long-domain-is-ok.co.uk/a-long-path?query=variables>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -191,35 +174,123 @@ No messages.
|
|||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
When configured with `80`.
|
When configured with `20`.
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
This line is simply not tooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
Mercury mercury mercury
|
||||||
long.
|
mercury.
|
||||||
|
|
||||||
Just like thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis one.
|
Mercury mercury mercury `mercury()`.
|
||||||
|
|
||||||
And this one is also very wrong: because the link starts aaaaaaafter the column: <http://line.com>
|
Mercury mercury mercury <http://localhost>.
|
||||||
|
|
||||||
<http://this-long-url-with-a-long-domain-is-not-ok.co.uk/a-long-path?query=variables> and such.
|
Mercury mercury mercury [m](example.com).
|
||||||
|
|
||||||
And this one is also very wrong: because the code starts aaaaaaafter the column: `alpha.bravo()`
|
Mercury mercury mercury ![m](example.com).
|
||||||
|
|
||||||
`alphaBravoCharlieDeltaEchoFoxtrotGolfHotelIndiaJuliettKiloLimaMikeNovemberOscar.papa()` and such.
|
`mercury()` mercury mercury mercury.
|
||||||
|
|
||||||
|
<http://localhost> mercury.
|
||||||
|
|
||||||
|
[m](example.com) mercury.
|
||||||
|
|
||||||
|
![m](example.com) mercury.
|
||||||
|
|
||||||
|
Mercury mercury ![m](example.com) mercury.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
4:86: Line must be at most 80 characters
|
1:24: Unexpected `23` character line, expected at most `20` characters, remove `3` characters
|
||||||
6:99: Line must be at most 80 characters
|
4:37: Unexpected `36` character line, expected at most `20` characters, remove `16` characters
|
||||||
8:96: Line must be at most 80 characters
|
6:44: Unexpected `43` character line, expected at most `20` characters, remove `23` characters
|
||||||
10:97: Line must be at most 80 characters
|
8:42: Unexpected `41` character line, expected at most `20` characters, remove `21` characters
|
||||||
12:99: Line must be at most 80 characters
|
10:43: Unexpected `42` character line, expected at most `20` characters, remove `22` characters
|
||||||
|
12:37: Unexpected `36` character line, expected at most `20` characters, remove `16` characters
|
||||||
|
14:28: Unexpected `27` character line, expected at most `20` characters, remove `7` characters
|
||||||
|
16:26: Unexpected `25` character line, expected at most `20` characters, remove `5` characters
|
||||||
|
18:27: Unexpected `26` character line, expected at most `20` characters, remove `6` characters
|
||||||
|
20:43: Unexpected `42` character line, expected at most `20` characters, remove `22` characters
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### `ok.md`
|
||||||
|
|
||||||
|
When configured with `20`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> frontmatter ([`remark-frontmatter`][github-remark-frontmatter]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
description: Mercury mercury mercury mercury.
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `ok.md`
|
||||||
|
|
||||||
|
When configured with `20`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
| Mercury | Mercury | Mercury |
|
||||||
|
| ------- | ------- | ------- |
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `ok.md`
|
||||||
|
|
||||||
|
When configured with `20`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> math ([`remark-math`][github-remark-math]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
$$
|
||||||
|
L = \frac{1}{2} \rho v^2 S C_L
|
||||||
|
$$
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `ok.md`
|
||||||
|
|
||||||
|
When configured with `20`.
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> MDX ([`remark-mdx`][github-remark-mdx]).
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
export const description = 'Mercury mercury mercury mercury.'
|
||||||
|
|
||||||
|
{description}
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
##### `ok-mixed-line-endings.md`
|
##### `ok-mixed-line-endings.md`
|
||||||
|
|
||||||
When configured with `10`.
|
When configured with `10`.
|
||||||
@ -247,10 +318,20 @@ When configured with `10`.
|
|||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:13: Line must be at most 10 characters
|
1:13: Unexpected `12` character line, expected at most `10` characters, remove `2` characters
|
||||||
2:13: Line must be at most 10 characters
|
2:13: Unexpected `12` character line, expected at most `10` characters, remove `2` characters
|
||||||
3:12: Line must be at most 10 characters
|
3:12: Unexpected `11` character line, expected at most `10` characters, remove `1` character
|
||||||
4:12: Line must be at most 10 characters
|
4:12: Unexpected `11` character line, expected at most `10` characters, remove `1` character
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `not-ok.md`
|
||||||
|
|
||||||
|
When configured with `'🌍'`.
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:1: Unexpected value `🌍` for `options`, expected `number`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
@ -322,10 +403,16 @@ abide by its terms.
|
|||||||
|
|
||||||
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
||||||
|
|
||||||
|
[github-remark-frontmatter]: https://github.com/remarkjs/remark-frontmatter
|
||||||
|
|
||||||
[github-remark-gfm]: https://github.com/remarkjs/remark-gfm
|
[github-remark-gfm]: https://github.com/remarkjs/remark-gfm
|
||||||
|
|
||||||
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
||||||
|
|
||||||
|
[github-remark-math]: https://github.com/remarkjs/remark-math
|
||||||
|
|
||||||
|
[github-remark-mdx]: https://mdxjs.com/packages/remark-mdx/
|
||||||
|
|
||||||
[github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
[github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
||||||
|
|
||||||
[npm-install]: https://docs.npmjs.com/cli/install
|
[npm-install]: https://docs.npmjs.com/cli/install
|
||||||
|
@ -45,50 +45,82 @@
|
|||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* > Foo…
|
* > Mercury,
|
||||||
* > …bar…
|
* > Venus,
|
||||||
* > …baz.
|
* > and Earth.
|
||||||
|
*
|
||||||
|
* Mars.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok-tabs.md"}
|
* {"name": "ok-tabs.md"}
|
||||||
*
|
*
|
||||||
* >␉Foo…
|
* >␉Mercury,
|
||||||
* >␉…bar…
|
* >␉Venus,
|
||||||
* >␉…baz.
|
* >␉and Earth.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* > Foo…
|
* > Mercury,
|
||||||
* …bar…
|
* Venus,
|
||||||
* > …baz.
|
* > and Earth.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 2:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "input", "name": "not-ok-tabs.md"}
|
||||||
*
|
*
|
||||||
* 2:1: Missing marker in block quote
|
* >␉Mercury,
|
||||||
|
* ␉Venus,
|
||||||
|
* and Earth.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok-tabs.md"}
|
||||||
|
*
|
||||||
|
* 2:2: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
|
* 3:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-tabs.md", "label": "input"}
|
* {"label": "input", "name": "containers.md"}
|
||||||
*
|
*
|
||||||
* >␉Foo…
|
* * > Mercury and
|
||||||
* ␉…bar…
|
* Venus.
|
||||||
* …baz.
|
|
||||||
*
|
*
|
||||||
|
* > * Mercury and
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* * > * Mercury and
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* > * > Mercury and
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* ***
|
||||||
|
*
|
||||||
|
* > * > Mercury and
|
||||||
|
* > Venus.
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-tabs.md", "label": "output"}
|
* {"label": "output", "name": "containers.md"}
|
||||||
*
|
*
|
||||||
* 2:1: Missing marker in block quote
|
* 2:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
* 3:1: Missing marker in block quote
|
* 5:3: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
|
* 8:5: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
|
* 11:7: Unexpected `0` block quote markers before paragraph line, expected `2` markers, add `2` markers
|
||||||
|
* 16:7: Unexpected `1` block quote marker before paragraph line, expected `2` markers, add `1` marker
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/// <reference types="mdast-util-directive" />
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd, pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {SKIP, visitParents} from 'unist-util-visit-parents'
|
||||||
import {location} from 'vfile-location'
|
import {location} from 'vfile-location'
|
||||||
|
|
||||||
const remarkLintNoBlockquoteWithoutMarker = lintRule(
|
const remarkLintNoBlockquoteWithoutMarker = lintRule(
|
||||||
@ -106,35 +138,81 @@ const remarkLintNoBlockquoteWithoutMarker = lintRule(
|
|||||||
const value = String(file)
|
const value = String(file)
|
||||||
const loc = location(file)
|
const loc = location(file)
|
||||||
|
|
||||||
visit(tree, 'blockquote', function (node) {
|
// Only paragraphs can be lazy.
|
||||||
let index = -1
|
visitParents(tree, 'paragraph', function (node, parents) {
|
||||||
|
let expected = 0
|
||||||
|
|
||||||
while (++index < node.children.length) {
|
for (const parent of parents) {
|
||||||
const child = node.children[index]
|
if (parent.type === 'blockquote') {
|
||||||
const start = pointStart(child)
|
expected++
|
||||||
const end = pointEnd(child)
|
}
|
||||||
|
// All known parents that only use whitespace for indent.
|
||||||
|
else if (
|
||||||
|
parent.type === 'containerDirective' ||
|
||||||
|
parent.type === 'footnoteDefinition' ||
|
||||||
|
parent.type === 'list' ||
|
||||||
|
parent.type === 'listItem' ||
|
||||||
|
parent.type === 'root'
|
||||||
|
) {
|
||||||
|
// Empty.
|
||||||
|
/* c8 ignore next 3 -- exit on unknown nodes. */
|
||||||
|
} else {
|
||||||
|
return SKIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (child.type === 'paragraph' && start && end) {
|
if (!expected) return SKIP
|
||||||
const column = start.column
|
|
||||||
let line = start.line
|
|
||||||
|
|
||||||
// Skip past the first line.
|
const end = pointEnd(node)
|
||||||
while (++line <= end.line) {
|
const start = pointStart(node)
|
||||||
const offset = loc.toOffset({line, column})
|
|
||||||
|
|
||||||
if (
|
if (!end || !start) return SKIP
|
||||||
typeof offset !== 'number' ||
|
|
||||||
/>[\t ]+$/.test(value.slice(offset - 5, offset))
|
|
||||||
) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Roughly here.
|
let line = start.line
|
||||||
file.message('Missing marker in block quote', {
|
|
||||||
line,
|
while (++line <= end.line) {
|
||||||
column: column - 2
|
// Skip first line.
|
||||||
})
|
const lineStart = loc.toOffset({line, column: 1})
|
||||||
|
assert(lineStart !== undefined) // Always defined.
|
||||||
|
let actual = 0
|
||||||
|
let index = lineStart
|
||||||
|
|
||||||
|
while (index < value.length) {
|
||||||
|
const code = value.charCodeAt(index)
|
||||||
|
|
||||||
|
if (code === 9 || code === 32) {
|
||||||
|
// Fine.
|
||||||
|
} else if (code === 62 /* `>` */) {
|
||||||
|
actual++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
const point = loc.toPoint(index)
|
||||||
|
assert(point) // Always defined.
|
||||||
|
|
||||||
|
const difference = expected - actual
|
||||||
|
|
||||||
|
// Roughly here.
|
||||||
|
if (difference) {
|
||||||
|
file.message(
|
||||||
|
'Unexpected `' +
|
||||||
|
actual +
|
||||||
|
'` block quote ' +
|
||||||
|
pluralize('marker', actual) +
|
||||||
|
' before paragraph line, expected `' +
|
||||||
|
expected +
|
||||||
|
'` ' +
|
||||||
|
pluralize('marker', expected) +
|
||||||
|
', add `' +
|
||||||
|
difference +
|
||||||
|
'` ' +
|
||||||
|
pluralize('marker', difference),
|
||||||
|
{ancestors: [...parents, node], place: point}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -33,9 +33,12 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"mdast-util-directive": "^3.0.0",
|
||||||
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"vfile-location": "^5.0.0"
|
"vfile-location": "^5.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
@ -48,7 +51,8 @@
|
|||||||
"xo": {
|
"xo": {
|
||||||
"prettier": true,
|
"prettier": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"capitalized-comments": "off"
|
"capitalized-comments": "off",
|
||||||
|
"unicorn/prefer-code-point": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,9 +152,11 @@ in a block quote.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
> Foo…
|
> Mercury,
|
||||||
> …bar…
|
> Venus,
|
||||||
> …baz.
|
> and Earth.
|
||||||
|
|
||||||
|
Mars.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -166,9 +168,9 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
>␉Foo…
|
>␉Mercury,
|
||||||
>␉…bar…
|
>␉Venus,
|
||||||
>␉…baz.
|
>␉and Earth.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -180,15 +182,15 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
> Foo…
|
> Mercury,
|
||||||
…bar…
|
Venus,
|
||||||
> …baz.
|
> and Earth.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:1: Missing marker in block quote
|
2:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok-tabs.md`
|
##### `not-ok-tabs.md`
|
||||||
@ -196,16 +198,49 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
>␉Foo…
|
>␉Mercury,
|
||||||
␉…bar…
|
␉Venus,
|
||||||
…baz.
|
and Earth.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:1: Missing marker in block quote
|
2:2: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
3:1: Missing marker in block quote
|
3:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `containers.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
* > Mercury and
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
> * Mercury and
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
* > * Mercury and
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
> * > Mercury and
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
> * > Mercury and
|
||||||
|
> Venus.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
2:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
|
5:3: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
|
8:5: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker
|
||||||
|
11:7: Unexpected `0` block quote markers before paragraph line, expected `2` markers, add `2` markers
|
||||||
|
16:7: Unexpected `1` block quote marker before paragraph line, expected `2` markers, add `1` marker
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -41,37 +41,207 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* Foo…␊␊…Bar.
|
* # Planets
|
||||||
|
*
|
||||||
|
* Mercury.
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* # Planets
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Mercury.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
|
*
|
||||||
|
* 4:1: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
* 8:1: Unexpected `3` blank lines before node, expected up to `1` blank line, remove `2` blank lines
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "initial.md"}
|
||||||
|
*
|
||||||
|
* ␊Mercury.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "initial.md"}
|
||||||
|
*
|
||||||
|
* 2:1: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"name": "final-one.md"}
|
||||||
|
*
|
||||||
|
* Mercury.␊
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "final-more.md"}
|
||||||
|
*
|
||||||
|
* Mercury.␊␊
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "final-more.md"}
|
||||||
|
*
|
||||||
|
* 1:9: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "empty-document.md"}
|
* {"name": "empty-document.md"}
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "block-quote.md"}
|
||||||
*
|
*
|
||||||
* Foo…␊␊␊…Bar␊␊␊
|
* > Mercury.
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* >
|
||||||
|
* > Earth.
|
||||||
|
* >
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "block-quote.md"}
|
||||||
|
*
|
||||||
|
* 6:3: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
* 6:9: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"directive": true, "label": "input", "name": "directive.md"}
|
||||||
*
|
*
|
||||||
* 4:1: Remove 1 line before node
|
* :::mercury
|
||||||
* 4:5: Remove 2 lines after node
|
* Venus.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Earth.
|
||||||
|
* :::
|
||||||
|
* @example
|
||||||
|
* {"directive": true, "label": "output", "name": "directive.md"}
|
||||||
|
*
|
||||||
|
* 5:1: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "input", "name": "footnote.md"}
|
||||||
|
*
|
||||||
|
* [^x]:
|
||||||
|
* Mercury.
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* [^y]:
|
||||||
|
*
|
||||||
|
* Earth.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Mars.
|
||||||
|
* @example
|
||||||
|
* {"gfm": true, "label": "output", "name": "footnote.md"}
|
||||||
|
*
|
||||||
|
* 8:5: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
* 11:5: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "mdx": true, "name": "jsx.md"}
|
||||||
|
*
|
||||||
|
* <Mercury>
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Earth.
|
||||||
|
* </Mercury>
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "mdx": true, "name": "jsx.md"}
|
||||||
|
*
|
||||||
|
* 5:3: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "list.md"}
|
||||||
|
*
|
||||||
|
* * Mercury.
|
||||||
|
* * Venus.
|
||||||
|
*
|
||||||
|
* ***
|
||||||
|
*
|
||||||
|
* * Mercury.
|
||||||
|
*
|
||||||
|
* * Venus.
|
||||||
|
*
|
||||||
|
* ***
|
||||||
|
*
|
||||||
|
* * Mercury.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* * Venus.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "list.md"}
|
||||||
|
*
|
||||||
|
* 15:1: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "list-item.md"}
|
||||||
|
*
|
||||||
|
* * Mercury.
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* ***
|
||||||
|
*
|
||||||
|
* * Mercury.
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* ***
|
||||||
|
*
|
||||||
|
* * Mercury.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Venus.
|
||||||
|
*
|
||||||
|
* ***
|
||||||
|
*
|
||||||
|
* *
|
||||||
|
* Mercury.
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "list-item.md"}
|
||||||
|
*
|
||||||
|
* 15:3: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
* 20:3: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "deep-block-quote.md"}
|
||||||
|
*
|
||||||
|
* * > * > # Venus␊␊
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "deep-block-quote.md"}
|
||||||
|
*
|
||||||
|
* 1:16: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* {"label": "input", "name": "deep-list-item.md"}
|
||||||
|
*
|
||||||
|
* > * > * # Venus␊␊
|
||||||
|
* @example
|
||||||
|
* {"label": "output", "name": "deep-list-item.md"}
|
||||||
|
*
|
||||||
|
* 1:16: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @typedef {import('mdast').Nodes} Nodes
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
* @typedef {import('unist').Point} Point
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import plural from 'pluralize'
|
/// <reference types="mdast-util-directive" />
|
||||||
|
/// <reference types="mdast-util-mdx" />
|
||||||
|
|
||||||
|
import {phrasing} from 'mdast-util-phrasing'
|
||||||
|
import pluralize from 'pluralize'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointEnd, pointStart} from 'unist-util-position'
|
import {pointEnd, pointStart} from 'unist-util-position'
|
||||||
import {visit} from 'unist-util-visit'
|
import {SKIP, visitParents} from 'unist-util-visit-parents'
|
||||||
|
|
||||||
const unknownContainerSize = new Set(['mdxJsxFlowElement', 'mdxJsxTextElement'])
|
|
||||||
|
|
||||||
const remarkLintNoConsecutiveBlankLines = lintRule(
|
const remarkLintNoConsecutiveBlankLines = lintRule(
|
||||||
{
|
{
|
||||||
@ -85,79 +255,108 @@ const remarkLintNoConsecutiveBlankLines = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
if ('children' in node) {
|
const parent = parents.at(-1)
|
||||||
|
|
||||||
|
// Ignore phrasing nodes and non-parents.
|
||||||
|
if (!parent) return
|
||||||
|
if (phrasing(node)) return SKIP
|
||||||
|
|
||||||
|
const siblings = /** @type {Array<Nodes>} */ (parent.children)
|
||||||
|
const index = siblings.indexOf(node)
|
||||||
|
|
||||||
|
// Compare parent and first child.
|
||||||
|
if (
|
||||||
|
index === 0 &&
|
||||||
|
// Container directives and JSX have arbitrary opening length.
|
||||||
|
parent.type !== 'containerDirective' &&
|
||||||
|
parent.type !== 'mdxJsxFlowElement'
|
||||||
|
) {
|
||||||
|
const parentStart = pointStart(parent)
|
||||||
const start = pointStart(node)
|
const start = pointStart(node)
|
||||||
const head = node.children[0]
|
|
||||||
const headStart = pointStart(head)
|
|
||||||
|
|
||||||
if (head && headStart && start) {
|
if (parentStart && start) {
|
||||||
if (!unknownContainerSize.has(node.type)) {
|
// For footnote definitions, the first line with the label can
|
||||||
// Compare parent and first child.
|
// otherwise be empty.
|
||||||
compare(start, headStart, 0)
|
const difference =
|
||||||
}
|
start.line -
|
||||||
|
parentStart.line -
|
||||||
|
(parent.type === 'footnoteDefinition' ? 1 : 0)
|
||||||
|
|
||||||
// Compare between each child.
|
if (difference > 0) {
|
||||||
let index = -1
|
file.message(
|
||||||
|
'Unexpected `' +
|
||||||
while (++index < node.children.length) {
|
difference +
|
||||||
const previous = node.children[index - 1]
|
'` blank ' +
|
||||||
const child = node.children[index]
|
pluralize('line', difference) +
|
||||||
const previousEnd = pointEnd(previous)
|
' before node, expected `0` blank lines, remove `' +
|
||||||
const childStart = pointStart(child)
|
difference +
|
||||||
|
'` blank ' +
|
||||||
if (previous && previousEnd && childStart) {
|
pluralize('line', difference),
|
||||||
compare(previousEnd, childStart, 2)
|
{ancestors: [...parents, node], place: start}
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
const end = pointEnd(node)
|
|
||||||
const tail = node.children[node.children.length - 1]
|
|
||||||
const tailEnd = pointEnd(tail)
|
|
||||||
|
|
||||||
// Compare parent and last child.
|
|
||||||
if (
|
|
||||||
end &&
|
|
||||||
tailEnd &&
|
|
||||||
tail !== head &&
|
|
||||||
!unknownContainerSize.has(node.type)
|
|
||||||
) {
|
|
||||||
compare(end, tailEnd, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
const next = siblings[index + 1]
|
||||||
* Compare the difference between `start` and `end`, and warn when that
|
const end = pointEnd(node)
|
||||||
* difference exceeds `max`.
|
const nextStart = pointStart(next)
|
||||||
*
|
|
||||||
* @param {Point} start
|
|
||||||
* Start.
|
|
||||||
* @param {Point} end
|
|
||||||
* End.
|
|
||||||
* @param {0 | 1 | 2} max
|
|
||||||
* Max.
|
|
||||||
* @returns {undefined}
|
|
||||||
* Nothing.
|
|
||||||
*/
|
|
||||||
function compare(start, end, max) {
|
|
||||||
const diff = end.line - start.line
|
|
||||||
const lines = Math.abs(diff) - max
|
|
||||||
|
|
||||||
if (lines > 0) {
|
// Compare child and next sibling.
|
||||||
file.message(
|
if (end && nextStart) {
|
||||||
'Remove ' +
|
// `2` for line ending after node and optional line ending of blank
|
||||||
lines +
|
// line.
|
||||||
' ' +
|
const difference = nextStart.line - end.line - 2
|
||||||
plural('line', Math.abs(lines)) +
|
|
||||||
' ' +
|
if (difference > 0) {
|
||||||
(diff > 0 ? 'before' : 'after') +
|
const actual = difference + 1
|
||||||
' node',
|
|
||||||
end
|
file.message(
|
||||||
)
|
'Unexpected `' +
|
||||||
|
actual +
|
||||||
|
'` blank ' +
|
||||||
|
pluralize('line', actual) +
|
||||||
|
' before node, expected up to `1` blank line, remove `' +
|
||||||
|
difference +
|
||||||
|
'` blank ' +
|
||||||
|
pluralize('line', difference),
|
||||||
|
{ancestors: [...parents, next], place: nextStart}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const parentEnd = pointEnd(parent)
|
||||||
|
|
||||||
|
// Compare parent and last child.
|
||||||
|
if (
|
||||||
|
!next &&
|
||||||
|
parentEnd &&
|
||||||
|
end &&
|
||||||
|
// Container directives and JSX have arbitrary closing length.
|
||||||
|
parent.type !== 'containerDirective' &&
|
||||||
|
parent.type !== 'mdxJsxFlowElement'
|
||||||
|
) {
|
||||||
|
// Block quote can have extra blank lines in them if with `>`.
|
||||||
|
// Other containers cannot.
|
||||||
|
const difference =
|
||||||
|
parentEnd.line - end.line - (parent.type === 'blockquote' ? 0 : 1)
|
||||||
|
|
||||||
|
if (difference > 0) {
|
||||||
|
file.message(
|
||||||
|
'Unexpected `' +
|
||||||
|
difference +
|
||||||
|
'` blank ' +
|
||||||
|
pluralize('line', difference) +
|
||||||
|
' after node, expected `0` blank lines, remove `' +
|
||||||
|
difference +
|
||||||
|
'` blank ' +
|
||||||
|
pluralize('line', difference),
|
||||||
|
{ancestors: [...parents, node], place: end}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,11 +32,13 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"@types/unist": "^3.0.0",
|
"mdast-util-directive": "^3.0.0",
|
||||||
|
"mdast-util-mdx": "^3.0.0",
|
||||||
|
"mdast-util-phrasing": "^4.0.0",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-position": "^5.0.0",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
@ -49,7 +51,8 @@
|
|||||||
"prettier": true,
|
"prettier": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"capitalized-comments": "off",
|
"capitalized-comments": "off",
|
||||||
"unicorn/prefer-at": "off"
|
"unicorn/prefer-at": "off",
|
||||||
|
"unicorn/prefer-set-has": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,32 +150,266 @@ It has a `join` option to configure more complex cases.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Foo…␊␊…Bar.
|
# Planets
|
||||||
|
|
||||||
|
Mercury.
|
||||||
|
|
||||||
|
Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
No messages.
|
No messages.
|
||||||
|
|
||||||
##### `empty-document.md`
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
No messages.
|
|
||||||
|
|
||||||
##### `not-ok.md`
|
##### `not-ok.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Foo…␊␊␊…Bar␊␊␊
|
# Planets
|
||||||
|
|
||||||
|
|
||||||
|
Mercury.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Venus.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
4:1: Remove 1 line before node
|
4:1: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
4:5: Remove 2 lines after node
|
8:1: Unexpected `3` blank lines before node, expected up to `1` blank line, remove `2` blank lines
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `initial.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
␊Mercury.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
2:1: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `final-one.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Mercury.␊
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `final-more.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Mercury.␊␊
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:9: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `empty-document.md`
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
No messages.
|
||||||
|
|
||||||
|
##### `block-quote.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> Mercury.
|
||||||
|
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
>
|
||||||
|
> Earth.
|
||||||
|
>
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
6:3: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
6:9: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `directive.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> directives ([`remark-directive`][github-remark-directive]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
:::mercury
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
|
||||||
|
Earth.
|
||||||
|
:::
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
5:1: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `footnote.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[^x]:
|
||||||
|
Mercury.
|
||||||
|
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
[^y]:
|
||||||
|
|
||||||
|
Earth.
|
||||||
|
|
||||||
|
|
||||||
|
Mars.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
8:5: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
11:5: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `jsx.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
> 👉 **Note**: this example uses
|
||||||
|
> MDX ([`remark-mdx`][github-remark-mdx]).
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Mercury>
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
|
||||||
|
Earth.
|
||||||
|
</Mercury>
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
5:3: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `list.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
* Mercury.
|
||||||
|
* Venus.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
* Mercury.
|
||||||
|
|
||||||
|
* Venus.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
* Mercury.
|
||||||
|
|
||||||
|
|
||||||
|
* Venus.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
15:1: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `list-item.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
* Mercury.
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
* Mercury.
|
||||||
|
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
* Mercury.
|
||||||
|
|
||||||
|
|
||||||
|
Venus.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
*
|
||||||
|
Mercury.
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
15:3: Unexpected `2` blank lines before node, expected up to `1` blank line, remove `1` blank line
|
||||||
|
20:3: Unexpected `1` blank line before node, expected `0` blank lines, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `deep-block-quote.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
* > * > # Venus␊␊
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:16: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `deep-list-item.md`
|
||||||
|
|
||||||
|
###### In
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> * > * # Venus␊␊
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Out
|
||||||
|
|
||||||
|
```text
|
||||||
|
1:16: Unexpected `1` blank line after node, expected `0` blank lines, remove `1` blank line
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
@ -247,8 +481,14 @@ abide by its terms.
|
|||||||
|
|
||||||
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
||||||
|
|
||||||
|
[github-remark-directive]: https://github.com/remarkjs/remark-directive
|
||||||
|
|
||||||
|
[github-remark-gfm]: https://github.com/remarkjs/remark-gfm
|
||||||
|
|
||||||
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
[github-remark-lint]: https://github.com/remarkjs/remark-lint
|
||||||
|
|
||||||
|
[github-remark-mdx]: https://mdxjs.com/packages/remark-mdx/
|
||||||
|
|
||||||
[github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
|
[github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
|
||||||
|
|
||||||
[github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
[github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
|
||||||
|
@ -35,32 +35,33 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2020 Titus Wormer
|
* @copyright 2020 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* [alpha]: alpha.com
|
* [mercury]: https://example.com/mercury/
|
||||||
* [bravo]: bravo.com
|
* [venus]: https://example.com/venus/
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
|
||||||
* [alpha]: alpha.com
|
|
||||||
* [bravo]: alpha.com
|
|
||||||
*
|
*
|
||||||
|
* [mercury]: https://example.com/mercury/
|
||||||
|
* [venus]: https://example.com/mercury/
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 2:1-2:19: Do not use different definitions with the same URL (1:1)
|
* 2:1-2:38: Unexpected definition with an already defined URL (as `mercury`), expected unique URLs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @typedef {import('mdast').Nodes} Nodes
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart, position} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {stringifyPosition} from 'unist-util-stringify-position'
|
import {VFileMessage} from 'vfile-message'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const remarkLintNoDuplicateDefinedUrls = lintRule(
|
const remarkLintNoDuplicateDefinedUrls = lintRule(
|
||||||
{
|
{
|
||||||
@ -74,27 +75,39 @@ const remarkLintNoDuplicateDefinedUrls = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
/** @type {Map<string, string>} */
|
/** @type {Map<string, Array<Nodes>>} */
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
|
|
||||||
visit(tree, 'definition', function (node) {
|
visitParents(tree, 'definition', function (node, parents) {
|
||||||
const place = position(node)
|
const ancestors = [...parents, node]
|
||||||
const start = pointStart(node)
|
|
||||||
|
|
||||||
if (place && start && node.url) {
|
if (node.position && node.url) {
|
||||||
const url = String(node.url).toUpperCase()
|
const urlNormal = String(node.url).toUpperCase()
|
||||||
const duplicate = map.get(url)
|
const duplicateAncestors = map.get(urlNormal)
|
||||||
|
|
||||||
|
if (duplicateAncestors) {
|
||||||
|
const duplicate = duplicateAncestors.at(-1)
|
||||||
|
assert(duplicate) // Always defined.
|
||||||
|
assert(duplicate.type === 'definition') // Always tail.
|
||||||
|
|
||||||
if (duplicate) {
|
|
||||||
file.message(
|
file.message(
|
||||||
'Do not use different definitions with the same URL (' +
|
'Unexpected definition with an already defined URL (as `' +
|
||||||
duplicate +
|
duplicate.identifier +
|
||||||
')',
|
'`), expected unique URLs',
|
||||||
place
|
{
|
||||||
|
ancestors,
|
||||||
|
cause: new VFileMessage('URL already defined here', {
|
||||||
|
ancestors: duplicateAncestors,
|
||||||
|
place: duplicate.position,
|
||||||
|
source: 'remark-lint',
|
||||||
|
ruleId: 'no-duplicate-defined-urls'
|
||||||
|
}),
|
||||||
|
place: node.position
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
map.set(url, stringifyPosition(start))
|
map.set(urlNormal, ancestors)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -33,10 +33,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"unist-util-stringify-position": "^4.0.0",
|
"vfile-message": "4.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -140,8 +140,8 @@ identifiers.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[alpha]: alpha.com
|
[mercury]: https://example.com/mercury/
|
||||||
[bravo]: bravo.com
|
[venus]: https://example.com/venus/
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -153,14 +153,14 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[alpha]: alpha.com
|
[mercury]: https://example.com/mercury/
|
||||||
[bravo]: alpha.com
|
[venus]: https://example.com/mercury/
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:1-2:19: Do not use different definitions with the same URL (1:1)
|
2:1-2:38: Unexpected definition with an already defined URL (as `mercury`), expected unique URLs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -34,45 +34,50 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* [foo]: bar
|
* [mercury]: https://example.com/mercury/
|
||||||
* [baz]: qux
|
* [venus]: https://example.com/venus/
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
|
||||||
* [foo]: bar
|
|
||||||
* [foo]: qux
|
|
||||||
*
|
*
|
||||||
|
* [mercury]: https://example.com/mercury/
|
||||||
|
* [mercury]: https://example.com/venus/
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 2:1-2:11: Do not use definitions with the same identifier (1:1)
|
* 2:1-2:38: Unexpected definition with an already defined identifier (`mercury`), expected unique identifiers
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"gfm": true, "label": "input", "name": "gfm.md"}
|
* {"gfm": true, "label": "input", "name": "gfm.md"}
|
||||||
*
|
*
|
||||||
* GFM footnote definitions are checked too[^a].
|
* Mercury[^mercury].
|
||||||
*
|
*
|
||||||
* [^a]: alpha
|
* [^mercury]:
|
||||||
* [^a]: bravo
|
* Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
* System.
|
||||||
|
*
|
||||||
|
* [^mercury]:
|
||||||
|
* Venus is the second planet from the Sun.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"gfm": true, "label": "output", "name": "gfm.md"}
|
* {"gfm": true, "label": "output", "name": "gfm.md"}
|
||||||
*
|
*
|
||||||
* 4:1-4:12: Do not use footnote definitions with the same identifier (3:1)
|
* 7:1-7:12: Unexpected footnote definition with an already defined identifier (`mercury`), expected unique identifiers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @typedef {import('mdast').Nodes} Nodes
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart, position} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {stringifyPosition} from 'unist-util-stringify-position'
|
import {VFileMessage} from 'vfile-message'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
/** @type {ReadonlyArray<never>} */
|
/** @type {ReadonlyArray<never>} */
|
||||||
const empty = []
|
const empty = []
|
||||||
@ -89,14 +94,12 @@ const remarkLintNoDuplicateDefinitions = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
/** @type {Map<string, string>} */
|
/** @type {Map<string, Array<Nodes>>} */
|
||||||
const definitions = new Map()
|
const definitions = new Map()
|
||||||
/** @type {Map<string, string>} */
|
/** @type {Map<string, Array<Nodes>>} */
|
||||||
const footnoteDefinitions = new Map()
|
const footnoteDefinitions = new Map()
|
||||||
|
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
const place = position(node)
|
|
||||||
const start = pointStart(node)
|
|
||||||
const [map, identifier] =
|
const [map, identifier] =
|
||||||
node.type === 'definition'
|
node.type === 'definition'
|
||||||
? [definitions, node.identifier]
|
? [definitions, node.identifier]
|
||||||
@ -104,21 +107,34 @@ const remarkLintNoDuplicateDefinitions = lintRule(
|
|||||||
? [footnoteDefinitions, node.identifier]
|
? [footnoteDefinitions, node.identifier]
|
||||||
: empty
|
: empty
|
||||||
|
|
||||||
if (map && identifier && place && start) {
|
if (map && identifier && node.position) {
|
||||||
const duplicate = map.get(identifier)
|
const ancestors = [...parents, node]
|
||||||
|
const duplicateAncestors = map.get(identifier)
|
||||||
|
|
||||||
|
if (duplicateAncestors) {
|
||||||
|
const duplicate = duplicateAncestors.at(-1)
|
||||||
|
assert(duplicate) // Always defined.
|
||||||
|
|
||||||
if (duplicate) {
|
|
||||||
file.message(
|
file.message(
|
||||||
'Do not use' +
|
'Unexpected ' +
|
||||||
(node.type === 'footnoteDefinition' ? ' footnote' : '') +
|
(node.type === 'footnoteDefinition' ? 'footnote ' : '') +
|
||||||
' definitions with the same identifier (' +
|
'definition with an already defined identifier (`' +
|
||||||
duplicate +
|
identifier +
|
||||||
')',
|
'`), expected unique identifiers',
|
||||||
place
|
{
|
||||||
|
ancestors,
|
||||||
|
cause: new VFileMessage('Identifier already defined here', {
|
||||||
|
ancestors: duplicateAncestors,
|
||||||
|
place: duplicate.position,
|
||||||
|
source: 'remark-lint',
|
||||||
|
ruleId: 'no-duplicate-definitions'
|
||||||
|
}),
|
||||||
|
place: node.position
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
map.set(identifier, stringifyPosition(start))
|
map.set(identifier, ancestors)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"unist-util-stringify-position": "^4.0.0",
|
"vfile-message": "^4.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -143,8 +143,8 @@ It’s a mistake when the same identifier is defined multiple times.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[foo]: bar
|
[mercury]: https://example.com/mercury/
|
||||||
[baz]: qux
|
[venus]: https://example.com/venus/
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -156,14 +156,14 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[foo]: bar
|
[mercury]: https://example.com/mercury/
|
||||||
[foo]: qux
|
[mercury]: https://example.com/venus/
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2:1-2:11: Do not use definitions with the same identifier (1:1)
|
2:1-2:38: Unexpected definition with an already defined identifier (`mercury`), expected unique identifiers
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `gfm.md`
|
##### `gfm.md`
|
||||||
@ -174,16 +174,20 @@ No messages.
|
|||||||
> GFM ([`remark-gfm`][github-remark-gfm]).
|
> GFM ([`remark-gfm`][github-remark-gfm]).
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
GFM footnote definitions are checked too[^a].
|
Mercury[^mercury].
|
||||||
|
|
||||||
[^a]: alpha
|
[^mercury]:
|
||||||
[^a]: bravo
|
Mercury is the first planet from the Sun and the smallest in the Solar
|
||||||
|
System.
|
||||||
|
|
||||||
|
[^mercury]:
|
||||||
|
Venus is the second planet from the Sun.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
4:1-4:12: Do not use footnote definitions with the same identifier (3:1)
|
7:1-7:12: Unexpected footnote definition with an already defined identifier (`mercury`), expected unique identifiers
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -39,80 +39,83 @@
|
|||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* ## Alpha
|
* # Planets
|
||||||
*
|
*
|
||||||
* ### Bravo
|
* ## Venus
|
||||||
*
|
*
|
||||||
* ## Charlie
|
* ### Discovery
|
||||||
*
|
*
|
||||||
* ### Bravo
|
* ## Mars
|
||||||
*
|
*
|
||||||
* ### Delta
|
* ### Discovery
|
||||||
*
|
*
|
||||||
* #### Bravo
|
* ### Phobos
|
||||||
*
|
*
|
||||||
* #### Echo
|
* #### Discovery
|
||||||
*
|
|
||||||
* ##### Bravo
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* ## Foxtrot
|
* # Planets
|
||||||
*
|
*
|
||||||
* ### Golf
|
* ## Mars
|
||||||
*
|
*
|
||||||
* ### Golf
|
* ### Discovery
|
||||||
|
*
|
||||||
|
* ### Discovery
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 5:1-5:9: Do not use headings with similar content per section (3:1)
|
* 7:1-7:14: Unexpected heading with equivalent text in section, expected unique headings
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-tolerant-heading-increment.md", "label": "input"}
|
* {"label": "input", "name": "tolerant-heading-increment.md"}
|
||||||
*
|
*
|
||||||
* # Alpha
|
* # Planets
|
||||||
*
|
*
|
||||||
* #### Bravo
|
* #### Discovery
|
||||||
*
|
*
|
||||||
* ###### Charlie
|
* ###### Phobos
|
||||||
*
|
*
|
||||||
* #### Bravo
|
* #### Discovery
|
||||||
*
|
*
|
||||||
* ###### Delta
|
* ###### Deimos
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok-tolerant-heading-increment.md", "label": "output"}
|
* {"label": "output", "name": "tolerant-heading-increment.md"}
|
||||||
*
|
*
|
||||||
* 7:1-7:11: Do not use headings with similar content per section (3:1)
|
* 7:1-7:15: Unexpected heading with equivalent text in section, expected unique headings
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"label": "input", "mdx": true, "name": "mdx.mdx"}
|
* {"label": "input", "mdx": true, "name": "mdx.mdx"}
|
||||||
*
|
*
|
||||||
* MDX is supported <em>too</em>.
|
* MDX is supported <em>too</em>.
|
||||||
*
|
*
|
||||||
* <h2>Alpha</h2>
|
* <h1>Planets</h1>
|
||||||
* <h2>Alpha</h2>
|
* <h2>Mars</h2>
|
||||||
|
* <h3>Discovery</h3>
|
||||||
|
* <h3>Discovery</h3>
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"label": "output", "mdx": true, "name": "mdx.mdx"}
|
* {"label": "output", "mdx": true, "name": "mdx.mdx"}
|
||||||
*
|
*
|
||||||
* 4:1-4:15: Do not use headings with similar content per section (3:1)
|
* 6:1-6:19: Unexpected heading with equivalent text in section, expected unique headings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Heading} Heading
|
* @typedef {import('mdast').Heading} Heading
|
||||||
|
* @typedef {import('mdast').Nodes} Nodes
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference types="mdast-util-mdx" />
|
/// <reference types="mdast-util-mdx" />
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {toString} from 'mdast-util-to-string'
|
import {toString} from 'mdast-util-to-string'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart, position} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {stringifyPosition} from 'unist-util-stringify-position'
|
import {VFileMessage} from 'vfile-message'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const jsxNameRe = /^h([1-6])$/
|
const jsxNameRe = /^h([1-6])$/
|
||||||
|
|
||||||
@ -128,10 +131,10 @@ const remarkLintNoDuplicateHeadingsInSection = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
/** @type {Array<Map<string, string>>} */
|
/** @type {Array<Map<string, Array<Nodes>>>} */
|
||||||
const stack = []
|
const stack = []
|
||||||
|
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
/** @type {Heading['depth'] | undefined} */
|
/** @type {Heading['depth'] | undefined} */
|
||||||
let rank
|
let rank
|
||||||
|
|
||||||
@ -149,23 +152,32 @@ const remarkLintNoDuplicateHeadingsInSection = lintRule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rank) {
|
if (rank) {
|
||||||
|
const ancestors = [...parents, node]
|
||||||
const value = toString(node).toLowerCase()
|
const value = toString(node).toLowerCase()
|
||||||
const index = rank - 1
|
const index = rank - 1
|
||||||
const scope = stack[index] || (stack[index] = new Map())
|
const map = stack[index] || (stack[index] = new Map())
|
||||||
const duplicate = scope.get(value)
|
const duplicateAncestors = map.get(value)
|
||||||
const place = position(node)
|
|
||||||
const start = pointStart(node)
|
if (node.position && duplicateAncestors) {
|
||||||
|
const duplicate = duplicateAncestors.at(-1)
|
||||||
|
assert(duplicate) // Always defined.
|
||||||
|
|
||||||
if (place && duplicate) {
|
|
||||||
file.message(
|
file.message(
|
||||||
'Do not use headings with similar content per section (' +
|
'Unexpected heading with equivalent text in section, expected unique headings',
|
||||||
duplicate +
|
{
|
||||||
')',
|
ancestors,
|
||||||
place
|
cause: new VFileMessage('Equivalent heading text defined here', {
|
||||||
|
ancestors: duplicateAncestors,
|
||||||
|
place: duplicate.position,
|
||||||
|
source: 'remark-lint',
|
||||||
|
ruleId: 'no-duplicate-headings-in-section'
|
||||||
|
}),
|
||||||
|
place: node.position
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.set(value, stringifyPosition(start))
|
map.set(value, ancestors)
|
||||||
// Drop things after it.
|
// Drop things after it.
|
||||||
stack.length = rank
|
stack.length = rank
|
||||||
}
|
}
|
||||||
|
@ -33,12 +33,12 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
"mdast-util-mdx": "^3.0.0",
|
"mdast-util-mdx": "^3.0.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"unist-util-stringify-position": "^4.0.0",
|
"vfile-message": "^4.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -141,21 +141,19 @@ section.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## Alpha
|
# Planets
|
||||||
|
|
||||||
### Bravo
|
## Venus
|
||||||
|
|
||||||
## Charlie
|
### Discovery
|
||||||
|
|
||||||
### Bravo
|
## Mars
|
||||||
|
|
||||||
### Delta
|
### Discovery
|
||||||
|
|
||||||
#### Bravo
|
### Phobos
|
||||||
|
|
||||||
#### Echo
|
#### Discovery
|
||||||
|
|
||||||
##### Bravo
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -167,39 +165,41 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## Foxtrot
|
# Planets
|
||||||
|
|
||||||
### Golf
|
## Mars
|
||||||
|
|
||||||
### Golf
|
### Discovery
|
||||||
|
|
||||||
|
### Discovery
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
5:1-5:9: Do not use headings with similar content per section (3:1)
|
7:1-7:14: Unexpected heading with equivalent text in section, expected unique headings
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `not-ok-tolerant-heading-increment.md`
|
##### `tolerant-heading-increment.md`
|
||||||
|
|
||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Alpha
|
# Planets
|
||||||
|
|
||||||
#### Bravo
|
#### Discovery
|
||||||
|
|
||||||
###### Charlie
|
###### Phobos
|
||||||
|
|
||||||
#### Bravo
|
#### Discovery
|
||||||
|
|
||||||
###### Delta
|
###### Deimos
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
7:1-7:11: Do not use headings with similar content per section (3:1)
|
7:1-7:15: Unexpected heading with equivalent text in section, expected unique headings
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `mdx.mdx`
|
##### `mdx.mdx`
|
||||||
@ -212,14 +212,16 @@ No messages.
|
|||||||
```mdx
|
```mdx
|
||||||
MDX is supported <em>too</em>.
|
MDX is supported <em>too</em>.
|
||||||
|
|
||||||
<h2>Alpha</h2>
|
<h1>Planets</h1>
|
||||||
<h2>Alpha</h2>
|
<h2>Mars</h2>
|
||||||
|
<h3>Discovery</h3>
|
||||||
|
<h3>Discovery</h3>
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
4:1-4:15: Do not use headings with similar content per section (3:1)
|
6:1-6:19: Unexpected heading with equivalent text in section, expected unique headings
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -42,53 +42,51 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* # Foo
|
* # Mercury
|
||||||
*
|
*
|
||||||
* ## Bar
|
* ## Venus
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"label": "input", "name": "not-ok.md"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* # Foo
|
* # Mercury
|
||||||
*
|
*
|
||||||
* ## Foo
|
* ## Mercury
|
||||||
*
|
|
||||||
* ## [Foo](http://foo.com/bar)
|
|
||||||
*
|
*
|
||||||
|
* ## [Mercury](http://example.com/mercury/)
|
||||||
* @example
|
* @example
|
||||||
* {"label": "output", "name": "not-ok.md"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 3:1-3:7: Do not use headings with similar content (1:1)
|
* 3:1-3:11: Unexpected heading with equivalent text, expected unique headings
|
||||||
* 5:1-5:29: Do not use headings with similar content (3:1)
|
* 5:1-5:42: Unexpected heading with equivalent text, expected unique headings
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"label": "input", "mdx": true, "name": "mdx.mdx"}
|
* {"label": "input", "mdx": true, "name": "mdx.mdx"}
|
||||||
*
|
*
|
||||||
* MDX is supported too.
|
* <h1>Mercury</h1>
|
||||||
*
|
* <h2>Mercury</h2>
|
||||||
* <h1>Alpha</h1>
|
|
||||||
* <h2>Alpha</h2>
|
|
||||||
*
|
|
||||||
* @example
|
* @example
|
||||||
* {"label": "output", "mdx": true, "name": "mdx.mdx"}
|
* {"label": "output", "mdx": true, "name": "mdx.mdx"}
|
||||||
*
|
*
|
||||||
* 4:1-4:15: Do not use headings with similar content (3:1)
|
* 2:1-2:17: Unexpected heading with equivalent text, expected unique headings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @typedef {import('mdast').Nodes} Nodes
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference types="mdast-util-mdx" />
|
/// <reference types="mdast-util-mdx" />
|
||||||
|
|
||||||
|
import {ok as assert} from 'devlop'
|
||||||
import {toString} from 'mdast-util-to-string'
|
import {toString} from 'mdast-util-to-string'
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {pointStart, position} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {stringifyPosition} from 'unist-util-stringify-position'
|
import {VFileMessage} from 'vfile-message'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const jsxNameRe = /^h([1-6])$/
|
const jsxNameRe = /^h([1-6])$/
|
||||||
|
|
||||||
@ -104,10 +102,10 @@ const remarkLintNoDuplicateHeadings = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
/** @type {Map<string, string>} */
|
/** @type {Map<string, Array<Nodes>>} */
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
|
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
if (
|
if (
|
||||||
node.type === 'heading' ||
|
node.type === 'heading' ||
|
||||||
((node.type === 'mdxJsxFlowElement' ||
|
((node.type === 'mdxJsxFlowElement' ||
|
||||||
@ -115,22 +113,30 @@ const remarkLintNoDuplicateHeadings = lintRule(
|
|||||||
node.name &&
|
node.name &&
|
||||||
jsxNameRe.test(node.name))
|
jsxNameRe.test(node.name))
|
||||||
) {
|
) {
|
||||||
const place = position(node)
|
const ancestors = [...parents, node]
|
||||||
const start = pointStart(node)
|
const value = toString(node).toLowerCase()
|
||||||
|
const duplicateAncestors = map.get(value)
|
||||||
|
|
||||||
if (place && start) {
|
if (node.position && duplicateAncestors) {
|
||||||
const value = toString(node).toLowerCase()
|
const duplicate = duplicateAncestors.at(-1)
|
||||||
const duplicate = map.get(value)
|
assert(duplicate) // Always defined.
|
||||||
|
|
||||||
if (duplicate) {
|
file.message(
|
||||||
file.message(
|
'Unexpected heading with equivalent text, expected unique headings',
|
||||||
'Do not use headings with similar content (' + duplicate + ')',
|
{
|
||||||
node
|
ancestors,
|
||||||
)
|
cause: new VFileMessage('Equivalent heading text defined here', {
|
||||||
}
|
ancestors: duplicateAncestors,
|
||||||
|
place: duplicate.position,
|
||||||
map.set(value, stringifyPosition(start))
|
source: 'remark-lint',
|
||||||
|
ruleId: 'no-duplicate-headings'
|
||||||
|
}),
|
||||||
|
place: node.position
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
map.set(value, ancestors)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,12 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
"mdast-util-mdx": "^3.0.0",
|
"mdast-util-mdx": "^3.0.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0",
|
||||||
"unist-util-stringify-position": "^4.0.0",
|
"vfile-message": "^4.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -151,9 +151,9 @@ which makes linking to them prone to changes.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Foo
|
# Mercury
|
||||||
|
|
||||||
## Bar
|
## Venus
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -165,18 +165,18 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Foo
|
# Mercury
|
||||||
|
|
||||||
## Foo
|
## Mercury
|
||||||
|
|
||||||
## [Foo](http://foo.com/bar)
|
## [Mercury](http://example.com/mercury/)
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
3:1-3:7: Do not use headings with similar content (1:1)
|
3:1-3:11: Unexpected heading with equivalent text, expected unique headings
|
||||||
5:1-5:29: Do not use headings with similar content (3:1)
|
5:1-5:42: Unexpected heading with equivalent text, expected unique headings
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `mdx.mdx`
|
##### `mdx.mdx`
|
||||||
@ -187,16 +187,14 @@ No messages.
|
|||||||
> MDX ([`remark-mdx`][github-remark-mdx]).
|
> MDX ([`remark-mdx`][github-remark-mdx]).
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
MDX is supported too.
|
<h1>Mercury</h1>
|
||||||
|
<h2>Mercury</h2>
|
||||||
<h1>Alpha</h1>
|
|
||||||
<h2>Alpha</h2>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
4:1-4:15: Do not use headings with similar content (3:1)
|
2:1-2:17: Unexpected heading with equivalent text, expected unique headings
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -38,38 +38,40 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* # Foo
|
* # Mercury
|
||||||
*
|
*
|
||||||
* Bar.
|
* **Mercury** is the first planet from the Sun and the smallest in the Solar
|
||||||
|
* System.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* *Foo*
|
* **Mercury**
|
||||||
*
|
*
|
||||||
* Bar.
|
* **Mercury** is the first planet from the Sun and the smallest in the Solar
|
||||||
|
* System.
|
||||||
*
|
*
|
||||||
* __Qux__
|
* *Venus*
|
||||||
*
|
|
||||||
* Quux.
|
|
||||||
*
|
*
|
||||||
|
* **Venus** is the second planet from the Sun.
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:6: Don’t use emphasis to introduce a section, use a heading
|
* 1:1-1:12: Unexpected strong introducing a section, expected a heading instead
|
||||||
* 5:1-5:8: Don’t use emphasis to introduce a section, use a heading
|
* 6:1-6:8: Unexpected emphasis introducing a section, expected a heading instead
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('mdast').Root} Root
|
* @typedef {import('mdast').Root} Root
|
||||||
|
* @typedef {import('mdast').RootContent} RootContent
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {visit} from 'unist-util-visit'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {position} from 'unist-util-position'
|
|
||||||
|
|
||||||
const remarkLintNoEmphasisAsHeading = lintRule(
|
const remarkLintNoEmphasisAsHeading = lintRule(
|
||||||
{
|
{
|
||||||
@ -83,31 +85,37 @@ const remarkLintNoEmphasisAsHeading = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
visit(tree, 'paragraph', function (node, index, parent) {
|
visitParents(tree, 'paragraph', function (node, parents) {
|
||||||
|
const parent = parents.at(-1)
|
||||||
|
|
||||||
|
if (!node.position || !parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next sibling needs to be a paragraph.
|
||||||
|
const siblings = /** @type {Array<RootContent>} */ (parent.children)
|
||||||
|
const next = parent.children[siblings.indexOf(node) + 1]
|
||||||
|
|
||||||
|
if (!next || next.type !== 'paragraph') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only child is emphasis/strong.
|
||||||
const head = node.children[0]
|
const head = node.children[0]
|
||||||
const place = position(node)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
place &&
|
node.children.length !== 1 ||
|
||||||
parent &&
|
(head.type !== 'emphasis' && head.type !== 'strong')
|
||||||
typeof index === 'number' &&
|
|
||||||
node.children.length === 1 &&
|
|
||||||
(head.type === 'emphasis' || head.type === 'strong')
|
|
||||||
) {
|
) {
|
||||||
const previous = parent.children[index - 1]
|
return
|
||||||
const next = parent.children[index + 1]
|
|
||||||
|
|
||||||
if (
|
|
||||||
(!previous || previous.type !== 'heading') &&
|
|
||||||
next &&
|
|
||||||
next.type === 'paragraph'
|
|
||||||
) {
|
|
||||||
file.message(
|
|
||||||
'Don’t use emphasis to introduce a section, use a heading',
|
|
||||||
place
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.message(
|
||||||
|
'Unexpected ' +
|
||||||
|
head.type +
|
||||||
|
' introducing a section, expected a heading instead',
|
||||||
|
{ancestors: [...parents, node, head], place: node.position}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -33,8 +33,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -147,9 +147,10 @@ It’s recommended to use actual headings instead.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Foo
|
# Mercury
|
||||||
|
|
||||||
Bar.
|
**Mercury** is the first planet from the Sun and the smallest in the Solar
|
||||||
|
System.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -161,20 +162,21 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
*Foo*
|
**Mercury**
|
||||||
|
|
||||||
Bar.
|
**Mercury** is the first planet from the Sun and the smallest in the Solar
|
||||||
|
System.
|
||||||
|
|
||||||
__Qux__
|
*Venus*
|
||||||
|
|
||||||
Quux.
|
**Venus** is the second planet from the Sun.
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:6: Don’t use emphasis to introduce a section, use a heading
|
1:1-1:12: Unexpected strong introducing a section, expected a heading instead
|
||||||
5:1-5:8: Don’t use emphasis to introduce a section, use a heading
|
6:1-6:8: Unexpected emphasis introducing a section, expected a heading instead
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -38,32 +38,31 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "ok.md"}
|
* {"name": "ok.md"}
|
||||||
*
|
*
|
||||||
* [alpha](http://bravo.com).
|
* [Mercury](http://example.com/mercury/).
|
||||||
*
|
*
|
||||||
* ![charlie](http://delta.com/echo.png "foxtrot").
|
* ![Venus](http://example.com/venus/ "Go to Venus").
|
||||||
*
|
*
|
||||||
* [golf][hotel].
|
* [earth]: http://example.com/earth/
|
||||||
*
|
|
||||||
* [india]: http://juliett.com
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "input"}
|
* {"label": "input", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* [alpha]().
|
* [Mercury]().
|
||||||
*
|
*
|
||||||
* ![bravo](#).
|
* ![Venus](#).
|
||||||
*
|
*
|
||||||
* [charlie]: <>
|
* [earth]: <>
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "not-ok.md", "label": "output"}
|
* {"label": "output", "name": "not-ok.md"}
|
||||||
*
|
*
|
||||||
* 1:1-1:10: Don’t use links without URL
|
* 1:1-1:12: Unexpected empty link URL referencing the current document, expected URL
|
||||||
* 3:1-3:12: Don’t use images without URL
|
* 3:1-3:12: Unexpected empty image URL referencing the current document, expected URL
|
||||||
* 5:1-5:14: Don’t use definitions without URL
|
* 5:1-5:12: Unexpected empty definition URL referencing the current document, expected URL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,8 +70,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {lintRule} from 'unified-lint-rule'
|
import {lintRule} from 'unified-lint-rule'
|
||||||
import {position} from 'unist-util-position'
|
import {visitParents} from 'unist-util-visit-parents'
|
||||||
import {visit} from 'unist-util-visit'
|
|
||||||
|
|
||||||
const remarkLintNoEmptyUrl = lintRule(
|
const remarkLintNoEmptyUrl = lintRule(
|
||||||
{
|
{
|
||||||
@ -86,17 +84,20 @@ const remarkLintNoEmptyUrl = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (tree, file) {
|
function (tree, file) {
|
||||||
visit(tree, function (node) {
|
visitParents(tree, function (node, parents) {
|
||||||
const place = position(node)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(node.type === 'definition' ||
|
(node.type === 'definition' ||
|
||||||
node.type === 'image' ||
|
node.type === 'image' ||
|
||||||
node.type === 'link') &&
|
node.type === 'link') &&
|
||||||
place &&
|
node.position &&
|
||||||
(!node.url || node.url === '#' || node.url === '?')
|
(!node.url || node.url === '#' || node.url === '?')
|
||||||
) {
|
) {
|
||||||
file.message('Don’t use ' + node.type + 's without URL', place)
|
file.message(
|
||||||
|
'Unexpected empty ' +
|
||||||
|
node.type +
|
||||||
|
' URL referencing the current document, expected URL',
|
||||||
|
{ancestors: [...parents, node], place: node.position}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0",
|
"@types/mdast": "^4.0.0",
|
||||||
"unified-lint-rule": "^2.0.0",
|
"unified-lint-rule": "^2.0.0",
|
||||||
"unist-util-position": "^5.0.0",
|
"unist-util-visit-parents": "^6.0.0"
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"typeCoverage": {
|
"typeCoverage": {
|
||||||
|
@ -143,13 +143,11 @@ It’s recommended to fill them out.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[alpha](http://bravo.com).
|
[Mercury](http://example.com/mercury/).
|
||||||
|
|
||||||
![charlie](http://delta.com/echo.png "foxtrot").
|
![Venus](http://example.com/venus/ "Go to Venus").
|
||||||
|
|
||||||
[golf][hotel].
|
[earth]: http://example.com/earth/
|
||||||
|
|
||||||
[india]: http://juliett.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
@ -161,19 +159,19 @@ No messages.
|
|||||||
###### In
|
###### In
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
[alpha]().
|
[Mercury]().
|
||||||
|
|
||||||
![bravo](#).
|
![Venus](#).
|
||||||
|
|
||||||
[charlie]: <>
|
[earth]: <>
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1-1:10: Don’t use links without URL
|
1:1-1:12: Unexpected empty link URL referencing the current document, expected URL
|
||||||
3:1-3:12: Don’t use images without URL
|
3:1-3:12: Unexpected empty image URL referencing the current document, expected URL
|
||||||
5:1-5:14: Don’t use definitions without URL
|
5:1-5:12: Unexpected empty definition URL referencing the current document, expected URL
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -30,28 +30,24 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "title.md"}
|
* {"name": "title.md"}
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "a-title.md", "label": "output", "positionless": true}
|
* {"label": "output", "name": "a-title.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Do not start file names with `a`
|
* 1:1: Unexpected file name starting with `a`, remove it
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "the-title.md", "label": "output", "positionless": true}
|
* {"label": "output", "name": "the-title.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Do not start file names with `the`
|
* 1:1: Unexpected file name starting with `the`, remove it
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "teh-title.md", "label": "output", "positionless": true}
|
* {"label": "output", "name": "an-article.md", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Do not start file names with `teh`
|
* 1:1: Unexpected file name starting with `an`, remove it
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* {"name": "an-article.md", "label": "output", "positionless": true}
|
|
||||||
*
|
|
||||||
* 1:1: Do not start file names with `an`
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,10 +68,12 @@ const remarkLintNoFileNameArticles = lintRule(
|
|||||||
* Nothing.
|
* Nothing.
|
||||||
*/
|
*/
|
||||||
function (_, file) {
|
function (_, file) {
|
||||||
const match = file.stem && file.stem.match(/^(the|teh|an?)\b/i)
|
const match = file.stem && file.stem.match(/^(?:the|teh|an?)\b/i)
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
file.message('Do not start file names with `' + match[0] + '`')
|
file.message(
|
||||||
|
'Unexpected file name starting with `' + match[0] + '`, remove it'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -144,7 +144,7 @@ No messages.
|
|||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Do not start file names with `a`
|
1:1: Unexpected file name starting with `a`, remove it
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `the-title.md`
|
##### `the-title.md`
|
||||||
@ -152,15 +152,7 @@ No messages.
|
|||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Do not start file names with `the`
|
1:1: Unexpected file name starting with `the`, remove it
|
||||||
```
|
|
||||||
|
|
||||||
##### `teh-title.md`
|
|
||||||
|
|
||||||
###### Out
|
|
||||||
|
|
||||||
```text
|
|
||||||
1:1: Do not start file names with `teh`
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `an-article.md`
|
##### `an-article.md`
|
||||||
@ -168,7 +160,7 @@ No messages.
|
|||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Do not start file names with `an`
|
1:1: Unexpected file name starting with `an`, remove it
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
@ -30,13 +30,14 @@
|
|||||||
* @author Titus Wormer
|
* @author Titus Wormer
|
||||||
* @copyright 2015 Titus Wormer
|
* @copyright 2015 Titus Wormer
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "plug-ins.md"}
|
* {"name": "plug-ins.md"}
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* {"name": "plug--ins.md", "label": "output", "positionless": true}
|
* {"name": "plug--ins.md", "label": "output", "positionless": true}
|
||||||
*
|
*
|
||||||
* 1:1: Do not use consecutive dashes in a file name
|
* 1:1: Unexpected consecutive dashes in a file name, expected `-`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +59,7 @@ const remarkLintNoFileNameConsecutiveDashes = lintRule(
|
|||||||
*/
|
*/
|
||||||
function (_, file) {
|
function (_, file) {
|
||||||
if (file.stem && /-{2,}/.test(file.stem)) {
|
if (file.stem && /-{2,}/.test(file.stem)) {
|
||||||
file.message('Do not use consecutive dashes in a file name')
|
file.message('Unexpected consecutive dashes in a file name, expected `-`')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -144,7 +144,7 @@ No messages.
|
|||||||
###### Out
|
###### Out
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1:1: Do not use consecutive dashes in a file name
|
1:1: Unexpected consecutive dashes in a file name, expected `-`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user