Enable custom rules to use the micromark parser, export micromark helpers for reuse.

This commit is contained in:
David Anson 2024-10-01 22:41:10 -07:00
parent 264da24dae
commit 5cc40c54b7
16 changed files with 4109 additions and 113 deletions

View file

@ -13,22 +13,31 @@ npm install markdownlint --save-dev
## Overview
The [Markdown](https://en.wikipedia.org/wiki/Markdown) markup language
is designed to be easy to read, write, and understand. It succeeds -
and its flexibility is both a benefit and a drawback. Many styles are
possible, so formatting can be inconsistent. Some constructs don't
work well in all parsers and should be avoided. The
[CommonMark](https://commonmark.org/) specification standardizes
parsers - but not authors.
The [Markdown][markdown] markup language is designed to be easy to read, write,
and understand. It succeeds - and its flexibility is both a benefit and a
drawback. Many styles are possible, so formatting can be inconsistent; some
constructs don't work well in all parsers and should be avoided.
`markdownlint` is a
[static analysis](https://en.wikipedia.org/wiki/Static_program_analysis)
tool for [Node.js](https://nodejs.org/) with a library of rules
to enforce standards and consistency for Markdown files. It was
inspired by - and heavily influenced by - Mark Harrison's
[markdownlint](https://github.com/markdownlint/markdownlint) for
[Ruby](https://www.ruby-lang.org/). The initial rules, rule documentation,
and test cases came directly from that project.
`markdownlint` is a [static analysis][static-analysis] tool for
[Node.js][nodejs] with a library of rules to enforce standards and consistency
for Markdown files. It was inspired by - and heavily influenced by - Mark
Harrison's [markdownlint][markdownlint-ruby] for Ruby. The initial rules, rule
documentation, and test cases came from that project.
`markdownlint` uses the [`micromark`][micromark] parser and honors the
[CommonMark][commonmark] specification for Markdown. It additionally supports
popular [GitHub Flavored Markdown (GFM)][gfm] syntax like autolinks and tables
as well as directives, footnotes, and math syntax - all implemented by
[`micromark` extensions][micromark-extensions].
[commonmark]: https://commonmark.org/
[gfm]: https://github.github.com/gfm/
[markdown]: https://en.wikipedia.org/wiki/Markdown
[markdownlint-ruby]: https://github.com/markdownlint/markdownlint
[micromark]: https://github.com/micromark/micromark
[micromark-extensions]: https://github.com/micromark/micromark?tab=readme-ov-file#list-of-extensions
[nodejs]: https://nodejs.org/
[static-analysis]: https://en.wikipedia.org/wiki/Static_program_analysis
### Related
@ -565,7 +574,7 @@ Type: `Array` of `Array` of `Function` and plugin parameters
Specifies additional [`markdown-it` plugins][markdown-it-plugin] to use when
parsing input. Plugins can be used to support additional syntax and features for
advanced scenarios.
advanced scenarios. *Deprecated.*
[markdown-it-plugin]: https://www.npmjs.com/search?q=keywords:markdown-it-plugin
@ -601,23 +610,20 @@ Specifies which version of the `result` object to return (see the "Usage"
section below for examples).
Passing a `resultVersion` of `0` corresponds to the original, simple format
where each error is identified by rule name and line number. *This is
deprecated.*
where each error is identified by rule name and line number. *Deprecated*
Passing a `resultVersion` of `1` corresponds to a detailed format where each
error includes information about the line number, rule name, alias, description,
as well as any additional detail or context that is available. *This is
deprecated.*
as well as any additional detail or context that is available. *Deprecated*
Passing a `resultVersion` of `2` corresponds to a detailed format where each
error includes information about the line number, rule names, description, as
well as any additional detail or context that is available. *This is
deprecated.*
well as any additional detail or context that is available. *Deprecated*
Passing a `resultVersion` of `3` corresponds to the detailed version `2` format
with additional information about how to fix automatically-fixable errors. In
this mode, all errors that occur on each line are reported (other versions
report only the first error for each rule). *This is the default.*
report only the first error for each rule). This is the default behavior.
##### options.strings
@ -946,10 +952,11 @@ Generate normal and minified scripts with:
npm run build-demo
```
Then reference `markdown-it` and `markdownlint`:
Then reference `markdownlint` and `micromark` scripts:
```html
<script src="demo/markdown-it.min.js"></script>
<script src="demo/micromark-browser.js"></script>
<script src="demo/micromark-html-browser.js"></script>
<script src="demo/markdownlint-browser.min.js"></script>
```

View file

@ -1710,7 +1710,7 @@ function validateRuleList(ruleList, synchronous) {
!result &&
(rule.parser !== undefined) &&
(rule.parser !== "markdownit") &&
!((customIndex < 0) && (rule.parser === "micromark")) &&
(rule.parser !== "micromark") &&
(rule.parser !== "none")
) {
result = newError("parser", rule.parser);

View file

@ -14,9 +14,8 @@ built-in rules.
For simple requirements like disallowing certain characters or patterns,
the community-developed
[markdownlint-rule-search-replace][markdownlint-rule-search-replace]
plug-in can be used.
This plug-in allows anyone to create a set of simple text-replacement rules in
JSON without needing to write any code.
plug-in can be used. This plug-in allows anyone to create a set of simple
text-replacement rules without needing to write code.
[markdownlint-rule-search-replace]: https://www.npmjs.com/package/markdownlint-rule-search-replace
@ -27,29 +26,62 @@ to more information, one or more tags, and a function that implements the rule's
behavior. That function is called once for each file/string input and is passed
the parsed input and a function to log any violations.
A simple rule implementation looks like:
Custom rules can (should) operate on a structured set of tokens based on the
[`micromark`][micromark] `parser` (this is preferred). Alternatively, custom
rules can operate on a structured set of tokens based on the
[`markdown-it`][markdown-it] `parser` (legacy support). Finally, custom rules
can operate directly on text with the `none` `parser`.
A simple rule implementation using the `micromark` parser to report a violation
for any use of blockquotes might look like:
```javascript
/** @type import("markdownlint").Rule */
module.exports = {
"names": [ "any-blockquote" ],
"names": [ "any-blockquote-micromark" ],
"description": "Rule that reports an error for any blockquote",
"information": new URL("https://example.com/rules/any-blockquote"),
"tags": [ "test" ],
"parser": "micromark",
"function": (params, onError) => {
const blockquotes = params.parsers.micromark.tokens
.filter(((token) => token.type === "blockQuote"));
for (const blockquote of blockquotes) {
const lines = blockquote.endLine - blockquote.startLine + 1;
onError({
"lineNumber": blockquote.startLine,
"detail": "Blockquote spans " + lines + " line(s).",
"context": params.lines[blockquote.startLine - 1]
});
}
}
}
```
That same rule implemented using the `markdown-it` parser might look like:
```javascript
/** @type import("markdownlint").Rule */
module.exports = {
"names": [ "any-blockquote-markdown-it" ],
"description": "Rule that reports an error for any blockquote",
"information": new URL("https://example.com/rules/any-blockquote"),
"tags": [ "test" ],
"parser": "markdownit",
"function": function rule(params, onError) {
params.parsers.markdownit.tokens.filter(function filterToken(token) {
return token.type === "blockquote_open";
}).forEach(function forToken(blockquote) {
var lines = blockquote.map[1] - blockquote.map[0];
"function": (params, onError) => {
const blockquotes = params.parsers.markdownit.tokens
.filter(((token) => token.type === "blockquote_open"));
for (const blockquote of blockquotes) {
const [ startIndex, endIndex ] = blockquote.map;
const lines = endIndex - startIndex;
onError({
"lineNumber": blockquote.lineNumber,
"detail": "Blockquote spans " + lines + " line(s).",
"context": blockquote.line.substr(0, 7)
"context": blockquote.line
});
});
}
}
};
}
```
A rule is implemented as an `Object`:
@ -62,9 +94,8 @@ A rule is implemented as an `Object`:
about the rule.
- `tags` is a required `Array` of `String` values that groups related rules for
easier customization.
- `parser` is a required `String` value `"markdownit" | "none"` that specifies
the parser data used via `params.parsers` (see below).
- Note: The value `"micromark"` is valid but is NOT currently supported.
- `parser` is a required `String` value `"markdownit" | "micromark" | "none"`
that specifies the parser data used via `params.parsers` (see below).
- `asynchronous` is an optional `Boolean` value that indicates whether the rule
returns a `Promise` and runs asynchronously.
- `function` is a required `Function` that implements the rule and is passed two
@ -79,7 +110,10 @@ A rule is implemented as an `Object`:
- `tokens` is an `Array` of [`markdown-it` `Token`s][markdown-it-token]
with added `line` and `lineNumber` properties. (This property was
previously on the `params` object.)
- Samples for `tokens` are available via [test snapshots][tokens].
- `micromark` is an `Object` that provides access to output from the
[`micromark`][micromark] parser.
- `tokens` is an `Array` of [`MicromarkToken`][micromark-token] objects.
- Samples for both `tokens` are available via [test snapshots][tokens].
- `lines` is an `Array` of `String` values corresponding to the lines of the
input file/string.
- `frontMatterLines` is an `Array` of `String` values corresponding to any
@ -152,6 +186,8 @@ exception.
[markdown-it]: https://github.com/markdown-it/markdown-it
[markdown-it-token]: https://markdown-it.github.io/markdown-it/#Token
[markdownlint-rule]: https://www.npmjs.com/search?q=keywords:markdownlint-rule
[micromark]: https://github.com/micromark/micromark
[micromark-token]: ../lib/markdownlint.d.ts
[rule-helpers]: https://www.npmjs.com/package/markdownlint-rule-helpers
[options-custom-rules]: ../README.md#optionscustomrules
[test-rules]: ../test/rules

1
helpers/.npmignore Normal file
View file

@ -0,0 +1 @@
test.js

View file

@ -3,7 +3,10 @@
"version": "0.26.0",
"description": "A collection of markdownlint helper functions for custom rules",
"main": "./helpers.js",
"exports": "./helpers.js",
"exports": {
".": "./helpers.js",
"./micromark": "./micromark-helpers.cjs"
},
"author": "David Anson (https://dlaa.me/)",
"license": "MIT",
"homepage": "https://github.com/DavidAnson/markdownlint",

28
helpers/test.cjs Normal file
View file

@ -0,0 +1,28 @@
// @ts-check
"use strict";
// eslint-disable-next-line n/no-extraneous-require
const test = require("ava").default;
const { "exports": packageExports, name } = require("../helpers/package.json");
const exportMappings = new Map([
[ ".", "../helpers/helpers.js" ],
[ "./micromark", "../helpers/micromark-helpers.cjs" ]
]);
test("exportMappings", (t) => {
t.deepEqual(
Object.keys(packageExports),
[ ...exportMappings.keys() ]
);
});
for (const [ exportName, exportPath ] of exportMappings) {
test(exportName, (t) => {
t.is(
require(exportName.replace(/^\./u, name)),
require(exportPath)
);
});
}

View file

@ -58,7 +58,7 @@ function validateRuleList(ruleList, synchronous) {
!result &&
(rule.parser !== undefined) &&
(rule.parser !== "markdownit") &&
!((customIndex < 0) && (rule.parser === "micromark")) &&
(rule.parser !== "micromark") &&
(rule.parser !== "none")
) {
result = newError("parser", rule.parser);

View file

@ -53,7 +53,7 @@
"lint-test-repos": "ava --timeout=10m test/markdownlint-test-repos-*.js",
"serial-config-docs": "npm run build-config && npm run build-docs",
"serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration",
"test": "ava --timeout=30s test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js",
"test": "ava --timeout=30s test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js helpers/test.cjs",
"test-cover": "c8 --100 npm test",
"test-declaration": "cd example/typescript && tsc --module nodenext && tsc --module commonjs && node type-check.js",
"test-extra": "ava --timeout=10m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js",

View file

@ -7,6 +7,7 @@ const test = require("ava").default;
const markdownlint = require("../lib/markdownlint");
const customRules = require("./rules/rules.js");
const { homepage, version } = require("../package.json");
const { newLineRe } = require("../helpers/helpers.js");
test("customRulesV0", (t) => new Promise((resolve) => {
t.plan(4);
@ -22,7 +23,8 @@ test("customRulesV0", (t) => new Promise((resolve) => {
t.falsy(err);
const expectedResult = {};
expectedResult[customRulesMd] = {
"any-blockquote": [ 12 ],
"any-blockquote-markdown-it": [ 12 ],
"any-blockquote-micromark": [ 12 ],
"every-n-lines": [ 2, 4, 6, 10, 12 ],
"first-line": [ 1 ],
"letters-E-X": [ 3, 7 ]
@ -31,7 +33,9 @@ test("customRulesV0", (t) => new Promise((resolve) => {
// @ts-ignore
let actualMessage = actualResult.toString();
let expectedMessage =
"./test/custom-rules.md: 12: any-blockquote" +
"./test/custom-rules.md: 12: any-blockquote-markdown-it" +
" Rule that reports an error for any blockquote\n" +
"./test/custom-rules.md: 12: any-blockquote-micromark" +
" Rule that reports an error for any blockquote\n" +
"./test/custom-rules.md: 2: every-n-lines" +
" Rule that reports an error every N lines\n" +
@ -53,7 +57,9 @@ test("customRulesV0", (t) => new Promise((resolve) => {
// @ts-ignore
actualMessage = actualResult.toString(true);
expectedMessage =
"./test/custom-rules.md: 12: any-blockquote" +
"./test/custom-rules.md: 12: any-blockquote-markdown-it" +
" Rule that reports an error for any blockquote\n" +
"./test/custom-rules.md: 12: any-blockquote-micromark" +
" Rule that reports an error for any blockquote\n" +
"./test/custom-rules.md: 2: every-n-lines" +
" Rule that reports an error every N lines\n" +
@ -91,13 +97,22 @@ test("customRulesV1", (t) => new Promise((resolve) => {
const expectedResult = {};
expectedResult[customRulesMd] = [
{ "lineNumber": 12,
"ruleName": "any-blockquote",
"ruleAlias": "any-blockquote",
"ruleName": "any-blockquote-markdown-it",
"ruleAlias": "any-blockquote-markdown-it",
"ruleDescription": "Rule that reports an error for any blockquote",
"ruleInformation":
`${homepage}/blob/main/test/rules/any-blockquote.js`,
"errorDetail": "Blockquote spans 1 line(s).",
"errorContext": "> Block",
"errorContext": "> Blockquote",
"errorRange": null },
{ "lineNumber": 12,
"ruleName": "any-blockquote-micromark",
"ruleAlias": "any-blockquote-micromark",
"ruleDescription": "Rule that reports an error for any blockquote",
"ruleInformation":
`${homepage}/blob/main/test/rules/any-blockquote.js`,
"errorDetail": "Blockquote spans 1 line(s).",
"errorContext": "> Blockquote",
"errorRange": null },
{ "lineNumber": 2,
"ruleName": "every-n-lines",
@ -170,9 +185,12 @@ test("customRulesV1", (t) => new Promise((resolve) => {
// @ts-ignore
const actualMessage = actualResult.toString();
const expectedMessage =
"./test/custom-rules.md: 12: any-blockquote/any-blockquote" +
"./test/custom-rules.md: 12: any-blockquote-markdown-it/any-blockquote-markdown-it" +
" Rule that reports an error for any blockquote" +
" [Blockquote spans 1 line(s).] [Context: \"> Block\"]\n" +
" [Blockquote spans 1 line(s).] [Context: \"> Blockquote\"]\n" +
"./test/custom-rules.md: 12: any-blockquote-micromark/any-blockquote-micromark" +
" Rule that reports an error for any blockquote" +
" [Blockquote spans 1 line(s).] [Context: \"> Blockquote\"]\n" +
"./test/custom-rules.md: 2: every-n-lines/every-n-lines" +
" Rule that reports an error every N lines [Line number 2]\n" +
"./test/custom-rules.md: 4: every-n-lines/every-n-lines" +
@ -211,12 +229,20 @@ test("customRulesV2", (t) => new Promise((resolve) => {
const expectedResult = {};
expectedResult[customRulesMd] = [
{ "lineNumber": 12,
"ruleNames": [ "any-blockquote" ],
"ruleNames": [ "any-blockquote-markdown-it" ],
"ruleDescription": "Rule that reports an error for any blockquote",
"ruleInformation":
`${homepage}/blob/main/test/rules/any-blockquote.js`,
"errorDetail": "Blockquote spans 1 line(s).",
"errorContext": "> Block",
"errorContext": "> Blockquote",
"errorRange": null },
{ "lineNumber": 12,
"ruleNames": [ "any-blockquote-micromark" ],
"ruleDescription": "Rule that reports an error for any blockquote",
"ruleInformation":
`${homepage}/blob/main/test/rules/any-blockquote.js`,
"errorDetail": "Blockquote spans 1 line(s).",
"errorContext": "> Blockquote",
"errorRange": null },
{ "lineNumber": 2,
"ruleNames": [ "every-n-lines" ],
@ -281,9 +307,12 @@ test("customRulesV2", (t) => new Promise((resolve) => {
// @ts-ignore
const actualMessage = actualResult.toString();
const expectedMessage =
"./test/custom-rules.md: 12: any-blockquote" +
"./test/custom-rules.md: 12: any-blockquote-markdown-it" +
" Rule that reports an error for any blockquote" +
" [Blockquote spans 1 line(s).] [Context: \"> Block\"]\n" +
" [Blockquote spans 1 line(s).] [Context: \"> Blockquote\"]\n" +
"./test/custom-rules.md: 12: any-blockquote-micromark" +
" Rule that reports an error for any blockquote" +
" [Blockquote spans 1 line(s).] [Context: \"> Blockquote\"]\n" +
"./test/custom-rules.md: 2: every-n-lines" +
" Rule that reports an error every N lines [Line number 2]\n" +
"./test/custom-rules.md: 4: every-n-lines" +
@ -328,7 +357,8 @@ test("customRulesConfig", (t) => new Promise((resolve) => {
t.falsy(err);
const expectedResult = {};
expectedResult[customRulesMd] = {
"any-blockquote": [ 12 ],
"any-blockquote-markdown-it": [ 12 ],
"any-blockquote-micromark": [ 12 ],
"every-n-lines": [ 3, 6, 12 ],
"first-line": [ 1 ],
"letters-E-X": [ 7 ]
@ -402,7 +432,7 @@ test("customRulesBadProperty", (t) => {
]) {
const { propertyName, propertyValues } = testCase;
for (const propertyValue of propertyValues) {
const badRule = { ...customRules.anyBlockquote };
const badRule = { ...customRules.firstLine };
badRule[propertyName] = propertyValue;
// eslint-disable-next-line jsdoc/valid-types
/** @type import("../lib/markdownlint").Options */
@ -592,7 +622,7 @@ test("customRulesParserMarkdownIt", (t) => {
});
test("customRulesParserMicromark", (t) => {
t.plan(1);
t.plan(5);
// eslint-disable-next-line jsdoc/valid-types
/** @type import("../lib/markdownlint").Options */
const options = {
@ -603,12 +633,12 @@ test("customRulesParserMicromark", (t) => {
"tags": [ "tag" ],
"parser": "micromark",
"function":
() => {
// t.false(Object.keys(params).includes("tokens"));
// t.is(Object.keys(params.parsers).length, 1);
// t.truthy(params.parsers.micromark);
// t.is(Object.keys(params.parsers.micromark).length, 1);
// t.truthy(params.parsers.micromark.tokens);
(params) => {
t.false(Object.keys(params).includes("tokens"));
t.is(Object.keys(params.parsers).length, 1);
t.truthy(params.parsers.micromark);
t.is(Object.keys(params.parsers.micromark).length, 1);
t.truthy(params.parsers.micromark.tokens);
}
}
],
@ -616,10 +646,7 @@ test("customRulesParserMicromark", (t) => {
"string": "# Heading\n"
}
};
return markdownlint.promises.markdownlint(options).catch((error) => {
// parser "micromark" currently unsupported for custom rules
t.is(error.message, "Property 'parser' of custom rule at index 0 is incorrect: 'micromark'.");
});
return markdownlint.promises.markdownlint(options).then(() => null);
});
test("customRulesMarkdownItParamsTokensSameObject", (t) => {
@ -664,10 +691,41 @@ test("customRulesMarkdownItTokensSnapshot", (t) => {
}
}
],
"files": "./test/every-markdown-syntax.md",
"noInlineConfig": true
};
return markdownlint.promises.markdownlint(options).then(() => null);
return fs
.readFile("./test/every-markdown-syntax.md", "utf8")
.then((content) => {
options.strings = { "content": content.split(newLineRe).join("\n") };
return markdownlint.promises.markdownlint(options).then(() => null);
});
});
test("customRulesMicromarkTokensSnapshot", (t) => {
t.plan(1);
// eslint-disable-next-line jsdoc/valid-types
/** @type import("../lib/markdownlint").Options */
const options = {
"customRules": [
{
"names": [ "name" ],
"description": "description",
"tags": [ "tag" ],
"parser": "micromark",
"function":
(params) => {
t.snapshot(params.parsers.micromark.tokens, "Unexpected tokens");
}
}
],
"noInlineConfig": true
};
return fs
.readFile("./test/every-markdown-syntax.md", "utf8")
.then((content) => {
options.strings = { "content": content.split(newLineRe).join("\n") };
return markdownlint.promises.markdownlint(options).then(() => null);
});
});
test("customRulesDefinitionStatic", (t) => new Promise((resolve) => {

View file

@ -17,26 +17,9 @@ const testTokens = new Promise((resolve, reject) => {
testContent.then(parse).then(resolve, reject);
});
const cloneToken = (token) => {
for (const child of token.children) {
const expectedParent = (token.type ? token : null);
if (child.parent !== expectedParent) {
throw new Error("Unexpected parent.");
}
}
const clone = { ...token };
delete clone.parent;
clone.children = clone.children.map(cloneToken);
return clone;
};
const cloneTokens = (tokens) => (
cloneToken({ "children": tokens }).children
);
test("parse", async(t) => {
t.plan(1);
t.snapshot(cloneTokens(await testTokens), "Unexpected tokens");
t.snapshot(await testTokens, "Unexpected tokens");
});
test("getEvents/filterByPredicate", async(t) => {

View file

@ -2,26 +2,54 @@
"use strict";
/** @type import("../../lib/markdownlint").Rule */
module.exports = {
"names": [ "any-blockquote" ],
"description": "Rule that reports an error for any blockquote",
"information": new URL(
"https://github.com/DavidAnson/markdownlint" +
"/blob/main/test/rules/any-blockquote.js"
),
"tags": [ "test" ],
"parser": "markdownit",
"function": (params, onError) => {
const blockquotes = params.parsers.markdownit.tokens
.filter(((token) => token.type === "blockquote_open"));
for (const blockquote of blockquotes) {
const lines = blockquote.map[1] - blockquote.map[0];
onError({
"lineNumber": blockquote.lineNumber,
"detail": "Blockquote spans " + lines + " line(s).",
"context": blockquote.line.substr(0, 7)
});
/** @type import("../../lib/markdownlint").Rule[] */
module.exports = [
// micromark parser (preferred)
{
"names": [ "any-blockquote-micromark" ],
"description": "Rule that reports an error for any blockquote",
"information": new URL(
"https://github.com/DavidAnson/markdownlint/blob/main/test/rules/any-blockquote.js"
),
"tags": [ "test" ],
"parser": "micromark",
"function": (params, onError) => {
const blockquotes = params.parsers.micromark.tokens
.filter(((token) => token.type === "blockQuote"));
for (const blockquote of blockquotes) {
const lines = blockquote.endLine - blockquote.startLine + 1;
onError({
"lineNumber": blockquote.startLine,
"detail": "Blockquote spans " + lines + " line(s).",
"context": params.lines[blockquote.startLine - 1]
});
}
}
},
// markdown-it parser (legacy)
{
"names": [ "any-blockquote-markdown-it" ],
"description": "Rule that reports an error for any blockquote",
"information": new URL(
"https://github.com/DavidAnson/markdownlint/blob/main/test/rules/any-blockquote.js"
),
"tags": [ "test" ],
"parser": "markdownit",
"function": (params, onError) => {
const blockquotes = params.parsers.markdownit.tokens
.filter(((token) => token.type === "blockquote_open"));
for (const blockquote of blockquotes) {
const [ startIndex, endIndex ] = blockquote.map;
const lines = endIndex - startIndex;
onError({
"lineNumber": blockquote.lineNumber,
"detail": "Blockquote spans " + lines + " line(s).",
"context": blockquote.line
});
}
}
}
};
];

View file

@ -21,7 +21,7 @@ const validateJson = require("./validate-json");
module.exports.validateJson = validateJson;
module.exports.all = [
anyBlockquote,
...anyBlockquote,
everyNLines,
firstLine,
lettersEX,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff