no-undefined-references: add support for regex as option

Closes GH-296.
Closes GH-297.

Reviewed-by: JounQin <admin@1stg.me>
Reviewed-by: Titus Wormer <tituswormer@gmail.com>
This commit is contained in:
Sigurd Spieckermann 2022-09-09 19:58:38 +02:00 committed by GitHub
parent 37041219cb
commit 1ab55098cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 121 additions and 12 deletions

View File

@ -8,9 +8,12 @@
* The following options (default: `undefined`) are accepted:
*
* * `Object` with the following fields:
* * `allow` (`Array<string>`, default: `[]`)
* text that you want to allowed between `[` and `]` even though its
* undefined
* * `allow` (`Array<string | RegExp | { source: string }>`,
* default: `[]`)
* text or regex that you want to be allowed between `[` and `]`
* even though its undefined; regex is provided via a `RegExp` object
* or via a `{ source: string }` object where `source` is the source
* text of a case-insensitive regex
*
* ## Recommendation
*
@ -61,6 +64,15 @@
* > Eliding a portion of a quoted passage [] is acceptable.
*
* @example
* {"name": "ok-allow.md", "config": {"allow": ["a", {"source": "^b\\."}]}}
*
* [foo][b.c]
*
* [bar][a]
*
* Matching is case-insensitive: [bar][B.C]
*
* @example
* {"name": "not-ok.md", "label": "input"}
*
* [bar]
@ -93,6 +105,19 @@
* 15:13-15:25: Found reference to undefined definition
* 17:17-17:23: Found reference to undefined definition
* 17:23-17:26: Found reference to undefined definition
*
* @example
* {"name": "not-ok.md", "label": "input", "config": {"allow": ["a", {"source": "^b\\."}]}}
*
* [foo][a.c]
*
* [bar][b]
*
* @example
* {"name": "not-ok.md", "label": "output", "config": {"allow": ["a", {"source": "^b\\."}]}}
*
* 1:1-1:11: Found reference to undefined definition
* 3:1-3:9: Found reference to undefined definition
*/
/**
@ -101,7 +126,7 @@
* @typedef {import('mdast').Paragraph} Paragraph
*
* @typedef Options
* @property {Array<string>} [allow]
* @property {Array<string | RegExp | { source: string }>} [allow]
*
* @typedef {Array<number>} Range
*/
@ -123,12 +148,28 @@ const remarkLintNoUndefinedReferences = lintRule(
const contents = String(file)
const loc = location(file)
const lineEnding = /(\r?\n|\r)[\t ]*(>[\t ]*)*/g
const allow = new Set(
(option.allow || []).map((d) => normalizeIdentifier(d))
)
/** @type {Record<string, boolean>} */
const map = Object.create(null)
const allow = option.allow || []
/** @type {Array<RegExp>} */
const regexes = []
/** @type {Set<string>} */
const strings = new Set()
let index = -1
while (++index < allow.length) {
const value = allow[index]
if (typeof value === 'string') {
strings.add(normalizeIdentifier(value))
} else if (value instanceof RegExp) {
regexes.push(value)
} else {
regexes.push(new RegExp(value.source, 'i'))
}
}
visit(tree, (node) => {
if (
(node.type === 'definition' || node.type === 'footnoteDefinition') &&
@ -148,7 +189,7 @@ const remarkLintNoUndefinedReferences = lintRule(
node.type === 'footnoteReference') &&
!generated(node) &&
!(normalizeIdentifier(node.identifier) in map) &&
!allow.has(normalizeIdentifier(node.identifier))
!isAllowed(node.identifier)
) {
file.message('Found reference to undefined definition', node)
}
@ -305,12 +346,24 @@ const remarkLintNoUndefinedReferences = lintRule(
if (
!generated({position: pos}) &&
!(normalizeIdentifier(id) in map) &&
!allow.has(normalizeIdentifier(id))
!isAllowed(id)
) {
file.message('Found reference to undefined definition', pos)
}
}
}
/**
* @param {string} id
* @returns {boolean}
*/
function isAllowed(id) {
const normalized = normalizeIdentifier(id)
return (
strings.has(normalized) ||
regexes.some((regex) => regex.test(normalized))
)
}
}
)

View File

@ -125,9 +125,12 @@ This rule supports standard configuration that all remark lint rules accept
The following options (default: `undefined`) are accepted:
* `Object` with the following fields:
* `allow` (`Array<string>`, default: `[]`)
— text that you want to allowed between `[` and `]` even though its
undefined
* `allow` (`Array<string | RegExp | { source: string }>`,
default: `[]`)
— text or regex that you want to be allowed between `[` and `]`
even though its undefined; regex is provided via a `RegExp` object
or via a `{ source: string }` object where `source` is the source
text of a case-insensitive regex
## Recommendation
@ -227,6 +230,43 @@ When configured with `{ allow: [ '...', '…' ] }`.
No messages.
##### `ok-allow.md`
When configured with `{ allow: [ 'a', { source: '^b\\.' } ] }`.
###### In
```markdown
[foo][b.c]
[bar][a]
Matching is case-insensitive: [bar][B.C]
```
###### Out
No messages.
##### `not-ok.md`
When configured with `{ allow: [ 'a', { source: '^b\\.' } ] }`.
###### In
```markdown
[foo][a.c]
[bar][b]
```
###### Out
```text
1:1-1:11: Found reference to undefined definition
3:1-3:9: Found reference to undefined definition
```
## Compatibility
Projects maintained by the unified collective are compatible with all maintained

16
test.js
View File

@ -21,6 +21,7 @@ import {characters} from './script/characters.js'
import lint from './packages/remark-lint/index.js'
import noHeadingPunctuation from './packages/remark-lint-no-heading-punctuation/index.js'
import noMultipleToplevelHeadings from './packages/remark-lint-no-multiple-toplevel-headings/index.js'
import noUndefinedReferences from './packages/remark-lint-no-undefined-references/index.js'
import finalNewline from './packages/remark-lint-final-newline/index.js'
const own = {}.hasOwnProperty
@ -226,6 +227,21 @@ test('core', async (t) => {
'should fail on incorrect severities (too low)'
)
file = await remark()
.use(noUndefinedReferences, {allow: [/^b\./i]})
.process(
toVFile({
path: 'virtual.md',
value: ['[foo][b.c]', '', '[bar][b]'].join('\n')
})
)
t.deepEqual(
asStrings(file.messages),
['virtual.md:3:1-3:9: Found reference to undefined definition'],
'no-undefined-references allow option should work with native regex'
)
file = await remark()
.use(
lintRule('test:rule', (tree, file) => {