mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2026-01-06 16:08:49 +01:00
Merge branch 'next' into main
This commit is contained in:
commit
02707cf270
91 changed files with 3640 additions and 1502 deletions
|
|
@ -1,6 +0,0 @@
|
|||
demo/markdown-it.min.js
|
||||
demo/markdownlint-browser.js
|
||||
demo/markdownlint-browser.min.js
|
||||
demo/markdownlint-rule-helpers-browser.js
|
||||
demo/markdownlint-rule-helpers-browser.min.js
|
||||
example/typescript/type-check.js
|
||||
106
.eslintrc.json
106
.eslintrc.json
|
|
@ -1,27 +1,60 @@
|
|||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2019
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:all",
|
||||
"plugin:jsdoc/recommended"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"demo/markdown-it.min.js",
|
||||
"demo/markdownlint-browser.js",
|
||||
"demo/markdownlint-browser.min.js",
|
||||
"demo/markdownlint-rule-helpers-browser.js",
|
||||
"demo/markdownlint-rule-helpers-browser.min.js",
|
||||
"example/typescript/type-check.js",
|
||||
"test-repos/"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"demo/*.js"
|
||||
],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"jsdoc/require-jsdoc": "off",
|
||||
"unicorn/prefer-query-selector": "off",
|
||||
"unicorn/prefer-add-event-listener": "off",
|
||||
"no-console": "off",
|
||||
"no-shadow": "off",
|
||||
"no-var": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"example/*.js"
|
||||
],
|
||||
"rules": {
|
||||
"node/no-missing-require": "off",
|
||||
"node/no-extraneous-require": "off",
|
||||
"no-console": "off",
|
||||
"no-invalid-this": "off",
|
||||
"no-shadow": "off",
|
||||
"object-property-newline": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"plugins": [
|
||||
"jsdoc",
|
||||
"node",
|
||||
"unicorn"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:all",
|
||||
"plugin:jsdoc/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"jsdoc": {
|
||||
"preferredTypes": {
|
||||
"object": "Object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reportUnusedDisableDirectives": true,
|
||||
"rules": {
|
||||
"array-bracket-spacing": ["error", "always"],
|
||||
|
|
@ -29,7 +62,7 @@
|
|||
"capitalized-comments": "off",
|
||||
"complexity": "off",
|
||||
"dot-location": ["error", "property"],
|
||||
"func-style": ["error", "declaration"],
|
||||
"func-style": "off",
|
||||
"function-call-argument-newline": "off",
|
||||
"function-paren-newline": "off",
|
||||
"global-require": "off",
|
||||
|
|
@ -72,7 +105,7 @@
|
|||
|
||||
"jsdoc/check-access": "error",
|
||||
"jsdoc/check-alignment": "error",
|
||||
"jsdoc/check-examples": "error",
|
||||
"jsdoc/check-examples": "off",
|
||||
"jsdoc/check-indentation": "error",
|
||||
"jsdoc/check-line-alignment": "error",
|
||||
"jsdoc/check-param-names": "error",
|
||||
|
|
@ -174,11 +207,14 @@
|
|||
"unicorn/no-array-method-this-argument": "error",
|
||||
"unicorn/no-array-push-push": "error",
|
||||
"unicorn/no-array-reduce": "error",
|
||||
"unicorn/no-await-expression-member": "error",
|
||||
"unicorn/no-console-spaces": "error",
|
||||
"unicorn/no-document-cookie": "error",
|
||||
"unicorn/no-empty-file": "error",
|
||||
"unicorn/no-for-loop": "error",
|
||||
"unicorn/no-hex-escape": "error",
|
||||
"unicorn/no-instanceof-array": "error",
|
||||
"unicorn/no-invalid-remove-event-listener": "error",
|
||||
"unicorn/no-keyword-prefix": "off",
|
||||
"unicorn/no-lonely-if": "error",
|
||||
"unicorn/no-nested-ternary": "error",
|
||||
|
|
@ -192,6 +228,7 @@
|
|||
"unicorn/no-unreadable-array-destructuring": "error",
|
||||
"unicorn/no-unsafe-regex": "off",
|
||||
"unicorn/no-unused-properties": "error",
|
||||
"unicorn/no-useless-fallback-in-spread": "error",
|
||||
"unicorn/no-useless-length-check": "error",
|
||||
"unicorn/no-useless-spread": "error",
|
||||
"unicorn/no-useless-undefined": "error",
|
||||
|
|
@ -205,12 +242,14 @@
|
|||
"unicorn/prefer-array-index-of": "error",
|
||||
"unicorn/prefer-array-some": "error",
|
||||
"unicorn/prefer-at": "off",
|
||||
"unicorn/prefer-code-point": "error",
|
||||
"unicorn/prefer-date-now": "error",
|
||||
"unicorn/prefer-default-parameters": "error",
|
||||
"unicorn/prefer-dom-node-append": "error",
|
||||
"unicorn/prefer-dom-node-dataset": "error",
|
||||
"unicorn/prefer-dom-node-remove": "error",
|
||||
"unicorn/prefer-dom-node-text-content": "error",
|
||||
"unicorn/prefer-export-from": "error",
|
||||
"unicorn/prefer-includes": "error",
|
||||
"unicorn/prefer-keyboard-event-key": "error",
|
||||
"unicorn/prefer-math-trunc": "error",
|
||||
|
|
@ -241,37 +280,14 @@
|
|||
"unicorn/require-number-to-fixed-digits-argument": "error",
|
||||
"unicorn/require-post-message-target-origin": "error",
|
||||
"unicorn/string-content": "error",
|
||||
"unicorn/template-indent": "error",
|
||||
"unicorn/throw-new-error": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"demo/*.js"
|
||||
],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"jsdoc/require-jsdoc": "off",
|
||||
"unicorn/prefer-query-selector": "off",
|
||||
"unicorn/prefer-add-event-listener": "off",
|
||||
"no-console": "off",
|
||||
"no-shadow": "off",
|
||||
"no-var": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"example/*.js"
|
||||
],
|
||||
"rules": {
|
||||
"node/no-missing-require": "off",
|
||||
"node/no-extraneous-require": "off",
|
||||
"no-console": "off",
|
||||
"no-invalid-this": "off",
|
||||
"no-shadow": "off",
|
||||
"object-property-newline": "off"
|
||||
"settings": {
|
||||
"jsdoc": {
|
||||
"preferredTypes": {
|
||||
"object": "Object"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -27,8 +27,8 @@ jobs:
|
|||
- name: Install Dependencies
|
||||
run: npm install --no-package-lock
|
||||
- name: Run All Validations
|
||||
if: ${{ matrix.node-version != '10.x' && matrix.node-version != '12.x' }}
|
||||
if: ${{ matrix.node-version != '12.x' }}
|
||||
run: npm run ci
|
||||
- name: Run Tests Only
|
||||
if: ${{ matrix.node-version == '10.x' || matrix.node-version == '12.x' }}
|
||||
if: ${{ matrix.node-version == '12.x' }}
|
||||
run: npm run test
|
||||
|
|
|
|||
2
.github/workflows/test-repos.yml
vendored
2
.github/workflows/test-repos.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [12.x]
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Try to break the new code now, or else it will get broken later.
|
|||
|
||||
Run tests before sending a pull request via `npm test` in the [usual manner](https://docs.npmjs.com/misc/scripts).
|
||||
Tests should all pass on all platforms.
|
||||
The test runner is [tape](https://www.npmjs.com/package/tape) and test cases are located in `test/markdownlint-test*.js`.
|
||||
The test runner is [AVA](https://github.com/avajs/ava) and test cases are located in `test/markdownlint-test*.js`.
|
||||
When running tests, `test/*.md` files are enumerated, linted, and fail if any violations are missing a corresponding `{MD###}` marker in the test file.
|
||||
For example, the line `### Heading {MD001}` is expected to trigger the rule `MD001`.
|
||||
For cases where the marker text can not be present on the same line, the syntax `{MD###:#}` can be used to include a line number.
|
||||
|
|
@ -33,7 +33,8 @@ Lint before sending a pull request by running `npm run lint`.
|
|||
There should be no issues.
|
||||
|
||||
Run a full continuous integration pass before sending a pull request via `npm run ci`.
|
||||
Code coverage should remain at 100%.
|
||||
Code coverage should always be 100%.
|
||||
As part of a continuous integration run, generated files may get updated and fail the run - commit them to the repository and rerun continuous integration.
|
||||
|
||||
Pull requests should contain a single commit.
|
||||
If necessary, squash multiple commits before creating the pull request and when making changes.
|
||||
|
|
@ -43,9 +44,18 @@ Open pull requests against the `next` branch.
|
|||
That's where the latest changes are staged for the next release.
|
||||
Include the text "(fixes #??)" at the end of the commit message so the pull request will be associated with the relevant issue.
|
||||
End commit messages with a period (`.`).
|
||||
Do not include `package-lock.json` in the pull request.
|
||||
Once accepted, the tag `fixed in next` will be added to the issue.
|
||||
When the commit is merged to the main branch during the release process, the issue will be closed automatically.
|
||||
(See [Closing issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/) for details.)
|
||||
|
||||
Please refrain from using slang or meaningless placeholder words.
|
||||
Sample content can be "text", "code", "heading", or the like.
|
||||
Sample URLs should use [example.com](https://en.wikipedia.org/wiki/Example.com) which is safe for this purpose.
|
||||
Profanity is not allowed.
|
||||
|
||||
In order to maintain the permissive MIT license this project uses, all contributions must be your own and released under that license.
|
||||
Code you add should be an original work and should not be copied from elsewhere.
|
||||
Taking code from a different project, Stack Overflow, or the like is not allowed.
|
||||
The use of tools such as GitHub Copilot that generate code from other projects is not allowed.
|
||||
|
||||
Thank you!
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -33,8 +33,10 @@ and test cases came directly from that project.
|
|||
### Related
|
||||
|
||||
* CLI
|
||||
* [markdownlint-cli command-line interface for Node.js](https://github.com/igorshubovych/markdownlint-cli)
|
||||
* [markdownlint-cli2 command-line interface for Node.js](https://github.com/DavidAnson/markdownlint-cli2)
|
||||
* [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli)
|
||||
command-line interface for Node.js ([works with pre-commit](https://github.com/igorshubovych/markdownlint-cli#use-with-pre-commit))
|
||||
* [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2)
|
||||
command-line interface for Node.js ([works with pre-commit](https://github.com/DavidAnson/markdownlint-cli2#pre-commit))
|
||||
* GitHub
|
||||
* [GitHub Super-Linter Action](https://github.com/github/super-linter)
|
||||
* [GitHub Actions problem matcher for markdownlint-cli](https://github.com/xt0rted/markdownlint-problem-matcher)
|
||||
|
|
@ -102,6 +104,8 @@ playground for learning and exploring.
|
|||
* **[MD046](doc/Rules.md#md046)** *code-block-style* - Code block style
|
||||
* **[MD047](doc/Rules.md#md047)** *single-trailing-newline* - Files should end with a single newline character
|
||||
* **[MD048](doc/Rules.md#md048)** *code-fence-style* - Code fence style
|
||||
* **[MD049](doc/Rules.md#md049)** *emphasis-style* - Emphasis style should be consistent
|
||||
* **[MD050](doc/Rules.md#md050)** *strong-style* - Strong style should be consistent
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
|
|
@ -125,7 +129,7 @@ rules at once.
|
|||
* **blockquote** - MD027, MD028
|
||||
* **bullet** - MD004, MD005, MD006, MD007, MD032
|
||||
* **code** - MD014, MD031, MD038, MD040, MD046, MD048
|
||||
* **emphasis** - MD036, MD037
|
||||
* **emphasis** - MD036, MD037, MD049, MD050
|
||||
* **hard_tab** - MD010
|
||||
* **headers** - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022,
|
||||
MD023, MD024, MD025, MD026, MD036, MD041, MD043
|
||||
|
|
@ -163,10 +167,12 @@ appear in the final markup):
|
|||
|
||||
* Disable all rules: `<!-- markdownlint-disable -->`
|
||||
* Enable all rules: `<!-- markdownlint-enable -->`
|
||||
* Disable all rules for the next line only: `<!-- markdownlint-disable-next-line -->`
|
||||
* Disable all rules for the next line only:
|
||||
`<!-- markdownlint-disable-next-line -->`
|
||||
* Disable one or more rules by name: `<!-- markdownlint-disable MD001 MD005 -->`
|
||||
* Enable one or more rules by name: `<!-- markdownlint-enable MD001 MD005 -->`
|
||||
* Disable one or more rules by name for the next line only: `<!-- markdownlint-disable-next-line MD001 MD005 -->`
|
||||
* Disable one or more rules by name for the next line only:
|
||||
`<!-- markdownlint-disable-next-line MD001 MD005 -->`
|
||||
* Capture the current rule configuration: `<!-- markdownlint-capture -->`
|
||||
* Restore the captured rule configuration: `<!-- markdownlint-restore -->`
|
||||
|
||||
|
|
@ -229,7 +235,7 @@ more rules for a file, the following more advanced syntax is supported:
|
|||
For example:
|
||||
|
||||
```markdown
|
||||
<!-- markdownlint-configure-file { "MD013": { "line_length": 70 } } -->
|
||||
<!-- markdownlint-configure-file { "MD013": { "code_blocks": false } } -->
|
||||
```
|
||||
|
||||
or
|
||||
|
|
@ -859,7 +865,7 @@ const results = window.markdownlint.sync(options).toString();
|
|||
## Examples
|
||||
|
||||
For ideas how to integrate `markdownlint` into your workflow, refer to the
|
||||
following projects or one of the tools in the [Related section](#Related):
|
||||
following projects or one of the tools in the [Related section](#related):
|
||||
|
||||
* [.NET Documentation](https://docs.microsoft.com/en-us/dotnet/) ([Search repository](https://github.com/dotnet/docs/search?q=markdownlint))
|
||||
* [ally.js](https://allyjs.io/) ([Search repository](https://github.com/medialize/ally.js/search?q=markdownlint))
|
||||
|
|
@ -870,6 +876,7 @@ following projects or one of the tools in the [Related section](#Related):
|
|||
* [MDN Web Docs](https://developer.mozilla.org/) ([Search repository](https://github.com/mdn/content/search?q=markdownlint))
|
||||
* [MkDocs](https://www.mkdocs.org/) ([Search repository](https://github.com/mkdocs/mkdocs/search?q=markdownlint))
|
||||
* [Mocha](https://mochajs.org/) ([Search repository](https://github.com/mochajs/mocha/search?q=markdownlint))
|
||||
* [Pi-hole documentation](https://docs.pi-hole.net) ([Search repository](https://github.com/pi-hole/docs/search?q=markdownlint))
|
||||
* [Reactable](https://glittershark.github.io/reactable/) ([Search repository](https://github.com/glittershark/reactable/search?q=markdownlint))
|
||||
* [Sinon.JS](https://sinonjs.org/) ([Search repository](https://github.com/sinonjs/sinon/search?q=markdownlint))
|
||||
* [TestCafe](https://devexpress.github.io/testcafe/) ([Search repository](https://github.com/DevExpress/testcafe/search?q=markdownlint))
|
||||
|
|
@ -968,6 +975,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
|
|||
* 0.24.0 - Remove support for end-of-life Node version 10, add support for custom file system
|
||||
module, improve MD010/MD011/MD037/MD043/MD044, improve TypeScript declaration file
|
||||
and JSON schema, update dependencies.
|
||||
* 0.25.0 - Add MD049/MD050 for consistent emphasis/strong style (both auto-fixable), improve
|
||||
MD007/MD010/MD032/MD033/MD035/MD037/MD039, support asynchronous custom rules,
|
||||
improve performance, improve CI process, reduce dependencies, update dependencies.
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/markdownlint.svg
|
||||
[npm-url]: https://www.npmjs.com/package/markdownlint
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "unused",
|
||||
"resolveJsonModule": true
|
||||
"outDir": "unused"
|
||||
},
|
||||
"include": [
|
||||
"../lib/*.js"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ function config(options) {
|
|||
{
|
||||
"loader": "ts-loader",
|
||||
"options": {
|
||||
"configFile": "../demo/tsconfig.json"
|
||||
"configFile": "../demo/tsconfig.json",
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -43,7 +44,6 @@ function config(options) {
|
|||
"resolve": {
|
||||
"fallback": {
|
||||
"fs": false,
|
||||
"os": false,
|
||||
"path": false,
|
||||
"util": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ A rule is implemented as an `Object` with one optional and four required propert
|
|||
- `description` is a required `String` value that describes the rule in output messages.
|
||||
- `information` is an optional (absolute) `URL` of a link to more information about the rule.
|
||||
- `tags` is a required `Array` of `String` values that groups related rules for easier customization.
|
||||
- `function` is a required synchronous `Function` that implements the rule and is passed two parameters:
|
||||
- `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 parameters:
|
||||
- `params` is an `Object` with properties that describe the content being analyzed:
|
||||
- `name` is a `String` that identifies the input file/string.
|
||||
- `tokens` is an `Array` of [`markdown-it` `Token` objects](https://markdown-it.github.io/markdown-it/#Token)
|
||||
|
|
@ -51,7 +52,7 @@ A rule is implemented as an `Object` with one optional and four required propert
|
|||
- `config` is an `Object` corresponding to the rule's entry in `options.config` (if present).
|
||||
- `onError` is a function that takes a single `Object` parameter with one required and four optional properties:
|
||||
- `lineNumber` is a required `Number` specifying the 1-based line number of the error.
|
||||
- `details` is an optional `String` with information about what caused the error.
|
||||
- `detail` is an optional `String` with information about what caused the error.
|
||||
- `context` is an optional `String` with relevant text surrounding the error location.
|
||||
- `range` is an optional `Array` with two `Number` values identifying the 1-based column and length of the error.
|
||||
- `fixInfo` is an optional `Object` with information about how to fix the error (all properties are optional, but
|
||||
|
|
@ -66,15 +67,25 @@ A rule is implemented as an `Object` with one optional and four required propert
|
|||
The collection of helper functions shared by the built-in rules is available for use by custom rules in the
|
||||
[markdownlint-rule-helpers package](https://www.npmjs.com/package/markdownlint-rule-helpers).
|
||||
|
||||
### Asynchronous Rules
|
||||
|
||||
If a rule needs to perform asynchronous operations (such as fetching a network resource), it can specify the value `true` for its `asynchronous` property.
|
||||
Asynchronous rules should return a `Promise` from their `function` implementation that is resolved when the rule completes.
|
||||
(The value passed to `resolve(...)` is ignored.)
|
||||
Linting violations from asynchronous rules are reported via the `onError` function just like for synchronous rules.
|
||||
|
||||
**Note**: Asynchronous rules cannot be referenced in a synchronous calling context (i.e., `markdownlint.sync(...)`).
|
||||
Attempting to do so throws an exception.
|
||||
|
||||
## Examples
|
||||
|
||||
- [Simple rules used by the project's test cases](../test/rules)
|
||||
- [Code for all `markdownlint` built-in rules](../lib)
|
||||
- [Package configuration for publishing to npm](../test/rules/npm)
|
||||
- Packages should export a single rule object or an `Array` of rule objects
|
||||
- [Custom rules from the Microsoft/vscode-docs-authoring repository](https://github.com/microsoft/vscode-docs-authoring/tree/master/packages/docs-linting/markdownlint-custom-rules)
|
||||
- [Custom rules from the Microsoft/vscode-docs-authoring repository](https://github.com/microsoft/vscode-docs-authoring/tree/main/packages/docs-linting/markdownlint-custom-rules)
|
||||
- [Custom rules from the axibase/docs-util repository](https://github.com/axibase/docs-util/tree/master/linting-rules)
|
||||
- [Custom rules from the webhintio/hint repository](https://github.com/webhintio/hint/blob/master/scripts/lint-markdown.js)
|
||||
- [Custom rules from the webhintio/hint repository](https://github.com/webhintio/hint/blob/main/scripts/lint-markdown.js)
|
||||
|
||||
## References
|
||||
|
||||
|
|
|
|||
99
doc/Rules.md
99
doc/Rules.md
|
|
@ -292,7 +292,11 @@ Tags: bullet, ul, indentation
|
|||
|
||||
Aliases: ul-indent
|
||||
|
||||
Parameters: indent, start_indented (number; default 2, boolean; default false)
|
||||
<!-- markdownlint-disable line-length -->
|
||||
|
||||
Parameters: indent, start_indented, start_indent (number; default 2, boolean; default false, number; defaults to indent)
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
Fixable: Most violations can be fixed by tooling
|
||||
|
||||
|
|
@ -319,7 +323,9 @@ rule).
|
|||
|
||||
The `start_indented` parameter allows the first level of lists to be indented by
|
||||
the configured number of spaces rather than starting at zero (the inverse of
|
||||
MD006).
|
||||
MD006). The `start_indent` parameter allows the first level of lists to be indented
|
||||
by a different number of spaces than the rest (ignored when `start_indented` is not
|
||||
set).
|
||||
|
||||
Rationale: Indenting by 2 spaces allows the content of a nested list to be in
|
||||
line with the start of the content of the parent list when a single space is
|
||||
|
|
@ -418,12 +424,14 @@ Some text
|
|||
* Spaces used to indent the list item instead
|
||||
```
|
||||
|
||||
You have the option to exclude this rule for code blocks. To do so, set the
|
||||
`code_blocks` parameter to `false`. Code blocks are included by default since
|
||||
handling of tabs by tools is often inconsistent (ex: using 4 vs. 8 spaces).
|
||||
You have the option to exclude this rule for code blocks and spans. To do so,
|
||||
set the `code_blocks` parameter to `false`. Code blocks and spans are included
|
||||
by default since handling of tabs by Markdown tools can be inconsistent (e.g.,
|
||||
using 4 vs. 8 spaces).
|
||||
|
||||
If you would like the fixer to change tabs to x spaces, then configure the `spaces_per_tab`
|
||||
parameter to the number x. The default value would be 1.
|
||||
By default, violations of this rule are fixed by replacing the tab with 1 space
|
||||
character. To use a different number of spaces, set the `spaces_per_tab`
|
||||
parameter to the desired value.
|
||||
|
||||
Rationale: Hard tabs are often rendered inconsistently by different editors and
|
||||
can be harder to work with than spaces.
|
||||
|
|
@ -1331,20 +1339,20 @@ For more information, see <https://www.example.com/>.
|
|||
```
|
||||
|
||||
Note: To use a bare URL without it being converted into a link, enclose it in
|
||||
a code block, otherwise in some markdown parsers it _will_ be converted:
|
||||
a code block, otherwise in some markdown parsers it *will* be converted:
|
||||
|
||||
```markdown
|
||||
`https://www.example.com`
|
||||
```
|
||||
|
||||
Note: The following scenario does _not_ trigger this rule to avoid conflicts
|
||||
Note: The following scenario does *not* trigger this rule to avoid conflicts
|
||||
with `MD011`/`no-reversed-links`:
|
||||
|
||||
```markdown
|
||||
[https://www.example.com]
|
||||
```
|
||||
|
||||
The use of quotes around a bare link will _not_ trigger this rule, either:
|
||||
The use of quotes around a bare link will *not* trigger this rule, either:
|
||||
|
||||
```markdown
|
||||
"https://www.example.com"
|
||||
|
|
@ -1362,8 +1370,8 @@ Tags: hr
|
|||
|
||||
Aliases: hr-style
|
||||
|
||||
Parameters: style ("consistent", "---", "***", or other string specifying the
|
||||
horizontal rule; default "consistent")
|
||||
Parameters: style ("consistent", "---", "***", "___", or other string specifying
|
||||
the horizontal rule; default "consistent")
|
||||
|
||||
This rule is triggered when inconsistent styles of horizontal rules are used
|
||||
in the document:
|
||||
|
|
@ -1762,7 +1770,8 @@ the proper capitalization, specify the desired letter case in the `names` array:
|
|||
]
|
||||
```
|
||||
|
||||
Set the `code_blocks` parameter to `false` to disable this rule for code blocks.
|
||||
Set the `code_blocks` parameter to `false` to disable this rule for code blocks
|
||||
and spans.
|
||||
|
||||
Rationale: Incorrect capitalization of proper names is usually a mistake.
|
||||
|
||||
|
|
@ -1911,3 +1920,67 @@ The configured list style can be a specific symbol to use (backtick, tilde), or
|
|||
can require that usage be consistent within the document.
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
|
||||
<a name="md049"></a>
|
||||
|
||||
## MD049 - Emphasis style should be consistent
|
||||
|
||||
Tags: emphasis
|
||||
|
||||
Aliases: emphasis-style
|
||||
|
||||
Parameters: style ("consistent", "asterisk", "underscore"; default "consistent")
|
||||
|
||||
Fixable: Most violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when the symbols used in the document for emphasis do not
|
||||
match the configured emphasis style:
|
||||
|
||||
```markdown
|
||||
*Text*
|
||||
_Text_
|
||||
```
|
||||
|
||||
To fix this issue, use the configured emphasis style throughout the document:
|
||||
|
||||
```markdown
|
||||
*Text*
|
||||
*Text*
|
||||
```
|
||||
|
||||
The configured emphasis style can be a specific symbol to use ("asterisk",
|
||||
"underscore"), or can require that usage be consistent within the document.
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
|
||||
<a name="md050"></a>
|
||||
|
||||
## MD050 - Strong style should be consistent
|
||||
|
||||
Tags: emphasis
|
||||
|
||||
Aliases: strong-style
|
||||
|
||||
Parameters: style ("consistent", "asterisk", "underscore"; default "consistent")
|
||||
|
||||
Fixable: Most violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when the symbols used in the document for strong do not
|
||||
match the configured strong style:
|
||||
|
||||
```markdown
|
||||
**Text**
|
||||
__Text__
|
||||
```
|
||||
|
||||
To fix this issue, use the configured strong style throughout the document:
|
||||
|
||||
```markdown
|
||||
**Text**
|
||||
**Text**
|
||||
```
|
||||
|
||||
The configured strong style can be a specific symbol to use ("asterisk",
|
||||
"underscore"), or can require that usage be consistent within the document.
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ const testRule = {
|
|||
let ruleOnErrorInfo: markdownlint.RuleOnErrorInfo;
|
||||
ruleOnErrorInfo = {
|
||||
"lineNumber": 1,
|
||||
"details": "details",
|
||||
"detail": "detail",
|
||||
"context": "context",
|
||||
"range": [ 1, 2 ],
|
||||
"fixInfo": {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const os = require("os");
|
||||
|
||||
// Regular expression for matching common newline characters
|
||||
// See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
|
||||
const newLineRe = /\r\n?|\n/g;
|
||||
|
|
@ -63,12 +61,20 @@ module.exports.isObject = function isObject(obj) {
|
|||
};
|
||||
|
||||
// Returns true iff the input line is blank (no content)
|
||||
// Example: Contains nothing, whitespace, or comments
|
||||
const blankLineRe = />|(?:<!--.*?-->)/g;
|
||||
// Example: Contains nothing, whitespace, or comment (unclosed start/end okay)
|
||||
module.exports.isBlankLine = function isBlankLine(line) {
|
||||
// Call to String.replace follows best practices and is not a security check
|
||||
// False-positive for js/incomplete-multi-character-sanitization
|
||||
return !line || !line.trim() || !line.replace(blankLineRe, "").trim();
|
||||
return (
|
||||
!line ||
|
||||
!line.trim() ||
|
||||
!line
|
||||
.replace(/<!--.*?-->/g, "")
|
||||
.replace(/<!--.*$/g, "")
|
||||
.replace(/^.*-->/g, "")
|
||||
.replace(/>/g, "")
|
||||
.trim()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -181,6 +187,22 @@ module.exports.fencedCodeBlockStyleFor =
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the string representation of a emphasis or strong markup character.
|
||||
*
|
||||
* @param {string} markup Emphasis or strong string.
|
||||
* @returns {string} String representation.
|
||||
*/
|
||||
module.exports.emphasisOrStrongStyleFor =
|
||||
function emphasisOrStrongStyleFor(markup) {
|
||||
switch (markup[0]) {
|
||||
case "*":
|
||||
return "asterisk";
|
||||
default:
|
||||
return "underscore";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the number of characters of indent for a token.
|
||||
*
|
||||
|
|
@ -252,6 +274,7 @@ function isMathBlock(token) {
|
|||
!token.type.endsWith("_end")
|
||||
);
|
||||
}
|
||||
module.exports.isMathBlock = isMathBlock;
|
||||
|
||||
// Get line metadata array
|
||||
module.exports.getLineMetadata = function getLineMetadata(params) {
|
||||
|
|
@ -293,14 +316,20 @@ module.exports.getLineMetadata = function getLineMetadata(params) {
|
|||
return lineMetadata;
|
||||
};
|
||||
|
||||
// Calls the provided function for each line (with context)
|
||||
module.exports.forEachLine = function forEachLine(lineMetadata, handler) {
|
||||
/**
|
||||
* Calls the provided function for each line.
|
||||
*
|
||||
* @param {Object} lineMetadata Line metadata object.
|
||||
* @param {Function} handler Function taking (line, lineIndex, inCode, onFence,
|
||||
* inTable, inItem, inBreak, inMath).
|
||||
* @returns {void}
|
||||
*/
|
||||
function forEachLine(lineMetadata, handler) {
|
||||
lineMetadata.forEach(function forMetadata(metadata) {
|
||||
// Parameters:
|
||||
// line, lineIndex, inCode, onFence, inTable, inItem, inBreak, inMath
|
||||
handler(...metadata);
|
||||
});
|
||||
};
|
||||
}
|
||||
module.exports.forEachLine = forEachLine;
|
||||
|
||||
// Returns (nested) lists as a flat array (in order)
|
||||
module.exports.flattenLists = function flattenLists(tokens) {
|
||||
|
|
@ -311,10 +340,6 @@ module.exports.flattenLists = function flattenLists(tokens) {
|
|||
const nestingStack = [];
|
||||
let lastWithMap = { "map": [ 0, 1 ] };
|
||||
tokens.forEach((token) => {
|
||||
if (isMathBlock(token) && token.map[1]) {
|
||||
// markdown-it-texmath plugin does not account for math_block_end
|
||||
token.map[1]++;
|
||||
}
|
||||
if ((token.type === "bullet_list_open") ||
|
||||
(token.type === "ordered_list_open")) {
|
||||
// Save current context and start a new one
|
||||
|
|
@ -386,7 +411,8 @@ module.exports.forEachHeading = function forEachHeading(params, handler) {
|
|||
* Calls the provided function for each inline code span's content.
|
||||
*
|
||||
* @param {string} input Markdown content.
|
||||
* @param {Function} handler Callback function.
|
||||
* @param {Function} handler Callback function taking (code, lineIndex,
|
||||
* columnIndex, ticks).
|
||||
* @returns {void}
|
||||
*/
|
||||
function forEachInlineCodeSpan(input, handler) {
|
||||
|
|
@ -521,26 +547,39 @@ module.exports.addErrorContext = function addErrorContext(
|
|||
};
|
||||
|
||||
/**
|
||||
* Returns an array of code span ranges.
|
||||
* Returns an array of code block and span content ranges.
|
||||
*
|
||||
* @param {string[]} lines Lines to scan for code span ranges.
|
||||
* @returns {number[][]} Array of ranges (line, index, length).
|
||||
* @param {Object} params RuleParams instance.
|
||||
* @param {Object} lineMetadata Line metadata object.
|
||||
* @returns {number[][]} Array of ranges (lineIndex, columnIndex, length).
|
||||
*/
|
||||
module.exports.inlineCodeSpanRanges = (lines) => {
|
||||
module.exports.codeBlockAndSpanRanges = (params, lineMetadata) => {
|
||||
const exclusions = [];
|
||||
forEachInlineCodeSpan(
|
||||
lines.join("\n"),
|
||||
(code, lineIndex, columnIndex) => {
|
||||
const codeLines = code.split(newLineRe);
|
||||
// eslint-disable-next-line unicorn/no-for-loop
|
||||
for (let i = 0; i < codeLines.length; i++) {
|
||||
exclusions.push(
|
||||
[ lineIndex + i, columnIndex, codeLines[i].length ]
|
||||
);
|
||||
columnIndex = 0;
|
||||
}
|
||||
// Add code block ranges (excludes fences)
|
||||
forEachLine(lineMetadata, (line, lineIndex, inCode, onFence) => {
|
||||
if (inCode && !onFence) {
|
||||
exclusions.push([ lineIndex, 0, line.length ]);
|
||||
}
|
||||
);
|
||||
});
|
||||
// Add code span ranges (excludes ticks)
|
||||
filterTokens(params, "inline", (token) => {
|
||||
if (token.children.some((child) => child.type === "code_inline")) {
|
||||
const tokenLines = params.lines.slice(token.map[0], token.map[1]);
|
||||
forEachInlineCodeSpan(
|
||||
tokenLines.join("\n"),
|
||||
(code, lineIndex, columnIndex) => {
|
||||
const codeLines = code.split(newLineRe);
|
||||
for (const [ i, line ] of codeLines.entries()) {
|
||||
exclusions.push([
|
||||
token.lineNumber - 1 + lineIndex + i,
|
||||
i ? 0 : columnIndex,
|
||||
line.length
|
||||
]);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
return exclusions;
|
||||
};
|
||||
|
||||
|
|
@ -596,6 +635,18 @@ module.exports.frontMatterHasTitle =
|
|||
function emphasisMarkersInContent(params) {
|
||||
const { lines } = params;
|
||||
const byLine = new Array(lines.length);
|
||||
// Search links
|
||||
lines.forEach((tokenLine, tokenLineIndex) => {
|
||||
const inLine = [];
|
||||
let linkMatch = null;
|
||||
while ((linkMatch = linkRe.exec(tokenLine))) {
|
||||
let markerMatch = null;
|
||||
while ((markerMatch = emphasisMarkersRe.exec(linkMatch[0]))) {
|
||||
inLine.push(linkMatch.index + markerMatch.index);
|
||||
}
|
||||
}
|
||||
byLine[tokenLineIndex] = inLine;
|
||||
});
|
||||
// Search code spans
|
||||
filterTokens(params, "inline", (token) => {
|
||||
const { children, lineNumber, map } = token;
|
||||
|
|
@ -606,31 +657,19 @@ function emphasisMarkersInContent(params) {
|
|||
(code, lineIndex, column, tickCount) => {
|
||||
const codeLines = code.split(newLineRe);
|
||||
codeLines.forEach((codeLine, codeLineIndex) => {
|
||||
const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex;
|
||||
const inLine = byLine[byLineIndex];
|
||||
const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount;
|
||||
let match = null;
|
||||
while ((match = emphasisMarkersRe.exec(codeLine))) {
|
||||
const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex;
|
||||
const inLine = byLine[byLineIndex] || [];
|
||||
const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount;
|
||||
inLine.push(codeLineOffset + match.index);
|
||||
byLine[byLineIndex] = inLine;
|
||||
}
|
||||
byLine[byLineIndex] = inLine;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
// Search links
|
||||
lines.forEach((tokenLine, tokenLineIndex) => {
|
||||
let linkMatch = null;
|
||||
while ((linkMatch = linkRe.exec(tokenLine))) {
|
||||
let markerMatch = null;
|
||||
while ((markerMatch = emphasisMarkersRe.exec(linkMatch[0]))) {
|
||||
const inLine = byLine[tokenLineIndex] || [];
|
||||
inLine.push(linkMatch.index + markerMatch.index);
|
||||
byLine[tokenLineIndex] = inLine;
|
||||
}
|
||||
}
|
||||
});
|
||||
return byLine;
|
||||
}
|
||||
module.exports.emphasisMarkersInContent = emphasisMarkersInContent;
|
||||
|
|
@ -639,9 +678,10 @@ module.exports.emphasisMarkersInContent = emphasisMarkersInContent;
|
|||
* Gets the most common line ending, falling back to the platform default.
|
||||
*
|
||||
* @param {string} input Markdown content to analyze.
|
||||
* @param {string} [platform] Platform identifier (process.platform).
|
||||
* @returns {string} Preferred line ending.
|
||||
*/
|
||||
function getPreferredLineEnding(input) {
|
||||
function getPreferredLineEnding(input, platform) {
|
||||
let cr = 0;
|
||||
let lf = 0;
|
||||
let crlf = 0;
|
||||
|
|
@ -662,7 +702,8 @@ function getPreferredLineEnding(input) {
|
|||
});
|
||||
let preferredLineEnding = null;
|
||||
if (!cr && !lf && !crlf) {
|
||||
preferredLineEnding = os.EOL;
|
||||
preferredLineEnding =
|
||||
((platform || process.platform) === "win32") ? "\r\n" : "\n";
|
||||
} else if ((lf >= crlf) && (lf >= cr)) {
|
||||
preferredLineEnding = "\n";
|
||||
} else if (crlf >= cr) {
|
||||
|
|
@ -777,3 +818,80 @@ module.exports.applyFixes = function applyFixes(input, errors) {
|
|||
// Return corrected input
|
||||
return lines.filter((line) => line !== null).join(lineEnding);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the range and fixInfo values for reporting an error if the expected
|
||||
* text is found on the specified line.
|
||||
*
|
||||
* @param {string[]} lines Lines of Markdown content.
|
||||
* @param {number} lineIndex Line index to check.
|
||||
* @param {string} search Text to search for.
|
||||
* @param {string} replace Text to replace with.
|
||||
* @returns {Object} Range and fixInfo wrapper.
|
||||
*/
|
||||
function getRangeAndFixInfoIfFound(lines, lineIndex, search, replace) {
|
||||
let range = null;
|
||||
let fixInfo = null;
|
||||
const searchIndex = lines[lineIndex].indexOf(search);
|
||||
if (searchIndex !== -1) {
|
||||
const column = searchIndex + 1;
|
||||
const length = search.length;
|
||||
range = [ column, length ];
|
||||
fixInfo = {
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": replace
|
||||
};
|
||||
}
|
||||
return {
|
||||
range,
|
||||
fixInfo
|
||||
};
|
||||
}
|
||||
module.exports.getRangeAndFixInfoIfFound = getRangeAndFixInfoIfFound;
|
||||
|
||||
/**
|
||||
* Gets the next (subsequent) child token if it is of the expected type.
|
||||
*
|
||||
* @param {Object} parentToken Parent token.
|
||||
* @param {Object} childToken Child token basis.
|
||||
* @param {string} nextType Token type of next token.
|
||||
* @param {string} nextNextType Token type of next-next token.
|
||||
* @returns {Object} Next token.
|
||||
*/
|
||||
function getNextChildToken(parentToken, childToken, nextType, nextNextType) {
|
||||
const { children } = parentToken;
|
||||
const index = children.indexOf(childToken);
|
||||
if (
|
||||
(index !== -1) &&
|
||||
(children.length > index + 2) &&
|
||||
(children[index + 1].type === nextType) &&
|
||||
(children[index + 2].type === nextNextType)
|
||||
) {
|
||||
return children[index + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
module.exports.getNextChildToken = getNextChildToken;
|
||||
|
||||
/**
|
||||
* Calls Object.freeze() on an object and its children.
|
||||
*
|
||||
* @param {Object} obj Object to deep freeze.
|
||||
* @returns {Object} Object passed to the function.
|
||||
*/
|
||||
function deepFreeze(obj) {
|
||||
const pending = [ obj ];
|
||||
let current = null;
|
||||
while ((current = pending.shift())) {
|
||||
Object.freeze(current);
|
||||
for (const name of Object.getOwnPropertyNames(current)) {
|
||||
const value = current[name];
|
||||
if (value && (typeof value === "object")) {
|
||||
pending.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
module.exports.deepFreeze = deepFreeze;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "markdownlint-rule-helpers",
|
||||
"version": "0.15.0",
|
||||
"version": "0.16.0",
|
||||
"description": "A collection of markdownlint helper functions for custom rules",
|
||||
"main": "helpers.js",
|
||||
"author": "David Anson (https://dlaa.me/)",
|
||||
|
|
|
|||
18
lib/cache.js
18
lib/cache.js
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
let codeBlockAndSpanRanges = null;
|
||||
module.exports.codeBlockAndSpanRanges = (value) => {
|
||||
if (value) {
|
||||
codeBlockAndSpanRanges = value;
|
||||
}
|
||||
return codeBlockAndSpanRanges;
|
||||
};
|
||||
|
||||
let flattenedLists = null;
|
||||
module.exports.flattenedLists = (value) => {
|
||||
if (value) {
|
||||
|
|
@ -10,14 +18,6 @@ module.exports.flattenedLists = (value) => {
|
|||
return flattenedLists;
|
||||
};
|
||||
|
||||
let inlineCodeSpanRanges = null;
|
||||
module.exports.inlineCodeSpanRanges = (value) => {
|
||||
if (value) {
|
||||
inlineCodeSpanRanges = value;
|
||||
}
|
||||
return inlineCodeSpanRanges;
|
||||
};
|
||||
|
||||
let lineMetadata = null;
|
||||
module.exports.lineMetadata = (value) => {
|
||||
if (value) {
|
||||
|
|
@ -27,7 +27,7 @@ module.exports.lineMetadata = (value) => {
|
|||
};
|
||||
|
||||
module.exports.clear = () => {
|
||||
codeBlockAndSpanRanges = null;
|
||||
flattenedLists = null;
|
||||
inlineCodeSpanRanges = null;
|
||||
lineMetadata = null;
|
||||
};
|
||||
|
|
|
|||
7
lib/constants.js
Normal file
7
lib/constants.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
module.exports.deprecatedRuleNames = [ "MD002", "MD006" ];
|
||||
module.exports.homepage = "https://github.com/DavidAnson/markdownlint";
|
||||
module.exports.version = "0.25.0";
|
||||
8
lib/markdownlint.d.ts
vendored
8
lib/markdownlint.d.ts
vendored
|
|
@ -206,9 +206,9 @@ type RuleOnErrorInfo = {
|
|||
*/
|
||||
lineNumber: number;
|
||||
/**
|
||||
* Details about the error.
|
||||
* Detail about the error.
|
||||
*/
|
||||
details?: string;
|
||||
detail?: string;
|
||||
/**
|
||||
* Context for the error.
|
||||
*/
|
||||
|
|
@ -263,6 +263,10 @@ type Rule = {
|
|||
* Rule tag(s).
|
||||
*/
|
||||
tags: string[];
|
||||
/**
|
||||
* True if asynchronous.
|
||||
*/
|
||||
asynchronous?: boolean;
|
||||
/**
|
||||
* Rule implementation.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
const path = require("path");
|
||||
const { promisify } = require("util");
|
||||
const markdownIt = require("markdown-it");
|
||||
const { deprecatedRuleNames } = require("./constants");
|
||||
const rules = require("./rules");
|
||||
const helpers = require("../helpers");
|
||||
const cache = require("./cache");
|
||||
|
|
@ -14,15 +15,14 @@ const cache = require("./cache");
|
|||
const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? require : /* c8 ignore next */ __non_webpack_require__;
|
||||
// Capture native require implementation for dynamic loading of modules
|
||||
|
||||
const deprecatedRuleNames = [ "MD002", "MD006" ];
|
||||
|
||||
/**
|
||||
* Validate the list of rules for structure and reuse.
|
||||
*
|
||||
* @param {Rule[]} ruleList List of rules.
|
||||
* @param {boolean} synchronous Whether to execute synchronously.
|
||||
* @returns {string} Error message if validation fails.
|
||||
*/
|
||||
function validateRuleList(ruleList) {
|
||||
function validateRuleList(ruleList, synchronous) {
|
||||
let result = null;
|
||||
if (ruleList.length === rules.length) {
|
||||
// No need to validate if only using built-in rules
|
||||
|
|
@ -62,6 +62,19 @@ function validateRuleList(ruleList) {
|
|||
) {
|
||||
result = newError("information");
|
||||
}
|
||||
if (
|
||||
!result &&
|
||||
(rule.asynchronous !== undefined) &&
|
||||
(typeof rule.asynchronous !== "boolean")
|
||||
) {
|
||||
result = newError("asynchronous");
|
||||
}
|
||||
if (!result && rule.asynchronous && synchronous) {
|
||||
result = new Error(
|
||||
"Custom rule " + rule.names.join("/") + " at index " + customIndex +
|
||||
" is asynchronous and can not be used in a synchronous context."
|
||||
);
|
||||
}
|
||||
if (!result) {
|
||||
rule.names.forEach(function forName(name) {
|
||||
const nameUpper = name.toUpperCase();
|
||||
|
|
@ -182,27 +195,21 @@ function removeFrontMatter(content, frontMatter) {
|
|||
* @returns {void}
|
||||
*/
|
||||
function annotateTokens(tokens, lines) {
|
||||
let tableMap = null;
|
||||
let trMap = null;
|
||||
tokens.forEach(function forToken(token) {
|
||||
// Handle missing maps for table head/body
|
||||
if (
|
||||
(token.type === "thead_open") ||
|
||||
(token.type === "tbody_open")
|
||||
) {
|
||||
tableMap = [ ...token.map ];
|
||||
} else if (
|
||||
(token.type === "tr_close") &&
|
||||
tableMap
|
||||
) {
|
||||
tableMap[0]++;
|
||||
} else if (
|
||||
(token.type === "thead_close") ||
|
||||
(token.type === "tbody_close")
|
||||
) {
|
||||
tableMap = null;
|
||||
// Provide missing maps for table content
|
||||
if (token.type === "tr_open") {
|
||||
trMap = token.map;
|
||||
} else if (token.type === "tr_close") {
|
||||
trMap = null;
|
||||
}
|
||||
if (tableMap && !token.map) {
|
||||
token.map = [ ...tableMap ];
|
||||
if (!token.map && trMap) {
|
||||
token.map = [ ...trMap ];
|
||||
}
|
||||
// Adjust maps for math blocks
|
||||
if (helpers.isMathBlock(token) && token.map[1]) {
|
||||
// markdown-it-texmath plugin does not account for math_block_end
|
||||
token.map[1]++;
|
||||
}
|
||||
// Update token metadata
|
||||
if (token.map) {
|
||||
|
|
@ -335,8 +342,7 @@ function getEnabledRulesPerLineNumber(
|
|||
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
|
||||
// Helper functions
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function handleInlineConfig(perLine, forEachMatch, forEachLine) {
|
||||
const input = perLine ? lines : [ lines.join("\n") ];
|
||||
function handleInlineConfig(input, forEachMatch, forEachLine) {
|
||||
input.forEach((line, lineIndex) => {
|
||||
if (!noInlineConfig) {
|
||||
let match = null;
|
||||
|
|
@ -367,6 +373,7 @@ function getEnabledRulesPerLineNumber(
|
|||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function applyEnableDisable(action, parameter, state) {
|
||||
state = { ...state };
|
||||
const enabled = (action.startsWith("ENABLE"));
|
||||
const items = parameter ?
|
||||
parameter.trim().toUpperCase().split(/\s+/) :
|
||||
|
|
@ -376,40 +383,42 @@ function getEnabledRulesPerLineNumber(
|
|||
state[ruleName] = enabled;
|
||||
});
|
||||
});
|
||||
return state;
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function enableDisableFile(action, parameter) {
|
||||
if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
|
||||
applyEnableDisable(action, parameter, enabledRules);
|
||||
enabledRules = applyEnableDisable(action, parameter, enabledRules);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function captureRestoreEnableDisable(action, parameter) {
|
||||
if (action === "CAPTURE") {
|
||||
capturedRules = { ...enabledRules };
|
||||
capturedRules = enabledRules;
|
||||
} else if (action === "RESTORE") {
|
||||
enabledRules = { ...capturedRules };
|
||||
enabledRules = capturedRules;
|
||||
} else if ((action === "ENABLE") || (action === "DISABLE")) {
|
||||
enabledRules = { ...enabledRules };
|
||||
applyEnableDisable(action, parameter, enabledRules);
|
||||
enabledRules = applyEnableDisable(action, parameter, enabledRules);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function updateLineState() {
|
||||
enabledRulesPerLineNumber.push({ ...enabledRules });
|
||||
enabledRulesPerLineNumber.push(enabledRules);
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function disableNextLine(action, parameter, lineNumber) {
|
||||
if (action === "DISABLE-NEXT-LINE") {
|
||||
applyEnableDisable(
|
||||
action,
|
||||
parameter,
|
||||
enabledRulesPerLineNumber[lineNumber + 1] || {}
|
||||
);
|
||||
const nextLineNumber = frontMatterLines.length + lineNumber + 1;
|
||||
enabledRulesPerLineNumber[nextLineNumber] =
|
||||
applyEnableDisable(
|
||||
action,
|
||||
parameter,
|
||||
enabledRulesPerLineNumber[nextLineNumber] || {}
|
||||
);
|
||||
}
|
||||
}
|
||||
// Handle inline comments
|
||||
handleInlineConfig(false, configureFile);
|
||||
handleInlineConfig([ lines.join("\n") ], configureFile);
|
||||
const effectiveConfig = getEffectiveConfig(
|
||||
ruleList, config, aliasToRuleNames);
|
||||
ruleList.forEach((rule) => {
|
||||
|
|
@ -418,9 +427,9 @@ function getEnabledRulesPerLineNumber(
|
|||
enabledRules[ruleName] = !!effectiveConfig[ruleName];
|
||||
});
|
||||
capturedRules = enabledRules;
|
||||
handleInlineConfig(true, enableDisableFile);
|
||||
handleInlineConfig(true, captureRestoreEnableDisable, updateLineState);
|
||||
handleInlineConfig(true, disableNextLine);
|
||||
handleInlineConfig(lines, enableDisableFile);
|
||||
handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
|
||||
handleInlineConfig(lines, disableNextLine);
|
||||
// Return results
|
||||
return {
|
||||
effectiveConfig,
|
||||
|
|
@ -428,38 +437,6 @@ function getEnabledRulesPerLineNumber(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare function for Array.prototype.sort for ascending order of errors.
|
||||
*
|
||||
* @param {LintError} a First error.
|
||||
* @param {LintError} b Second error.
|
||||
* @returns {number} Positive value if a>b, negative value if b<a, 0 otherwise.
|
||||
*/
|
||||
function lineNumberComparison(a, b) {
|
||||
return a.lineNumber - b.lineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter function to include everything.
|
||||
*
|
||||
* @returns {boolean} True.
|
||||
*/
|
||||
function filterAllValues() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to return unique values from a sorted errors array.
|
||||
*
|
||||
* @param {LintError} value Error instance.
|
||||
* @param {number} index Index in array.
|
||||
* @param {LintError[]} array Array of errors.
|
||||
* @returns {boolean} Filter value.
|
||||
*/
|
||||
function uniqueFilterForSortedErrors(value, index, array) {
|
||||
return (index === 0) || (value.lineNumber > array[index - 1].lineNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lints a string containing Markdown content.
|
||||
*
|
||||
|
|
@ -509,28 +486,28 @@ function lintContent(
|
|||
);
|
||||
// Create parameters for rules
|
||||
const params = {
|
||||
name,
|
||||
tokens,
|
||||
lines,
|
||||
frontMatterLines
|
||||
"name": helpers.deepFreeze(name),
|
||||
"tokens": helpers.deepFreeze(tokens),
|
||||
"lines": helpers.deepFreeze(lines),
|
||||
"frontMatterLines": helpers.deepFreeze(frontMatterLines)
|
||||
};
|
||||
cache.lineMetadata(helpers.getLineMetadata(params));
|
||||
cache.flattenedLists(helpers.flattenLists(params.tokens));
|
||||
cache.inlineCodeSpanRanges(helpers.inlineCodeSpanRanges(params.lines));
|
||||
cache.codeBlockAndSpanRanges(
|
||||
helpers.codeBlockAndSpanRanges(params, cache.lineMetadata())
|
||||
);
|
||||
// Function to run for each rule
|
||||
const result = (resultVersion === 0) ? {} : [];
|
||||
let results = [];
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function forRule(rule) {
|
||||
// Configure rule
|
||||
const ruleNameFriendly = rule.names[0];
|
||||
const ruleName = ruleNameFriendly.toUpperCase();
|
||||
const ruleName = rule.names[0].toUpperCase();
|
||||
params.config = effectiveConfig[ruleName];
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function throwError(property) {
|
||||
throw new Error(
|
||||
"Property '" + property + "' of onError parameter is incorrect.");
|
||||
}
|
||||
const errors = [];
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function onError(errorInfo) {
|
||||
if (!errorInfo ||
|
||||
|
|
@ -539,6 +516,10 @@ function lintContent(
|
|||
(errorInfo.lineNumber > lines.length)) {
|
||||
throwError("lineNumber");
|
||||
}
|
||||
const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
|
||||
if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
|
||||
return;
|
||||
}
|
||||
if (errorInfo.detail &&
|
||||
!helpers.isString(errorInfo.detail)) {
|
||||
throwError("detail");
|
||||
|
|
@ -549,12 +530,12 @@ function lintContent(
|
|||
}
|
||||
if (errorInfo.range &&
|
||||
(!Array.isArray(errorInfo.range) ||
|
||||
(errorInfo.range.length !== 2) ||
|
||||
!helpers.isNumber(errorInfo.range[0]) ||
|
||||
(errorInfo.range[0] < 1) ||
|
||||
!helpers.isNumber(errorInfo.range[1]) ||
|
||||
(errorInfo.range[1] < 1) ||
|
||||
((errorInfo.range[0] + errorInfo.range[1] - 1) >
|
||||
(errorInfo.range.length !== 2) ||
|
||||
!helpers.isNumber(errorInfo.range[0]) ||
|
||||
(errorInfo.range[0] < 1) ||
|
||||
!helpers.isNumber(errorInfo.range[1]) ||
|
||||
(errorInfo.range[1] < 1) ||
|
||||
((errorInfo.range[0] + errorInfo.range[1] - 1) >
|
||||
lines[errorInfo.lineNumber - 1].length))) {
|
||||
throwError("range");
|
||||
}
|
||||
|
|
@ -599,78 +580,114 @@ function lintContent(
|
|||
cleanFixInfo.insertText = fixInfo.insertText;
|
||||
}
|
||||
}
|
||||
errors.push({
|
||||
"lineNumber": errorInfo.lineNumber + frontMatterLines.length,
|
||||
"detail": errorInfo.detail || null,
|
||||
"context": errorInfo.context || null,
|
||||
"range": errorInfo.range ? [ ...errorInfo.range ] : null,
|
||||
results.push({
|
||||
lineNumber,
|
||||
"ruleName": rule.names[0],
|
||||
"ruleNames": rule.names,
|
||||
"ruleDescription": rule.description,
|
||||
"ruleInformation": rule.information ? rule.information.href : null,
|
||||
"errorDetail": errorInfo.detail || null,
|
||||
"errorContext": errorInfo.context || null,
|
||||
"errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
|
||||
"fixInfo": fixInfo ? cleanFixInfo : null
|
||||
});
|
||||
}
|
||||
// Call (possibly external) rule function
|
||||
if (handleRuleFailures) {
|
||||
try {
|
||||
rule.function(params, onError);
|
||||
} catch (error) {
|
||||
onError({
|
||||
"lineNumber": 1,
|
||||
"detail": `This rule threw an exception: ${error.message}`
|
||||
});
|
||||
// Call (possibly external) rule function to report errors
|
||||
const catchCallsOnError = (error) => onError({
|
||||
"lineNumber": 1,
|
||||
"detail": `This rule threw an exception: ${error.message || error}`
|
||||
});
|
||||
const invokeRuleFunction = () => rule.function(params, onError);
|
||||
if (rule.asynchronous) {
|
||||
// Asynchronous rule, ensure it returns a Promise
|
||||
const ruleFunctionPromise =
|
||||
Promise.resolve().then(invokeRuleFunction);
|
||||
return handleRuleFailures ?
|
||||
ruleFunctionPromise.catch(catchCallsOnError) :
|
||||
ruleFunctionPromise;
|
||||
}
|
||||
// Synchronous rule
|
||||
try {
|
||||
invokeRuleFunction();
|
||||
} catch (error) {
|
||||
if (handleRuleFailures) {
|
||||
catchCallsOnError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function formatResults() {
|
||||
// Sort results by rule name by line number
|
||||
results.sort((a, b) => (
|
||||
a.ruleName.localeCompare(b.ruleName) ||
|
||||
a.lineNumber - b.lineNumber
|
||||
));
|
||||
if (resultVersion < 3) {
|
||||
// Remove fixInfo and multiple errors for the same rule and line number
|
||||
const noPrevious = {
|
||||
"ruleName": null,
|
||||
"lineNumber": -1
|
||||
};
|
||||
results = results.filter((error, index, array) => {
|
||||
delete error.fixInfo;
|
||||
const previous = array[index - 1] || noPrevious;
|
||||
return (
|
||||
(error.ruleName !== previous.ruleName) ||
|
||||
(error.lineNumber !== previous.lineNumber)
|
||||
);
|
||||
});
|
||||
}
|
||||
if (resultVersion === 0) {
|
||||
// Return a dictionary of rule->[line numbers]
|
||||
const dictionary = {};
|
||||
for (const error of results) {
|
||||
const ruleLines = dictionary[error.ruleName] || [];
|
||||
ruleLines.push(error.lineNumber);
|
||||
dictionary[error.ruleName] = ruleLines;
|
||||
}
|
||||
// @ts-ignore
|
||||
results = dictionary;
|
||||
} else if (resultVersion === 1) {
|
||||
// Use ruleAlias instead of ruleNames
|
||||
for (const error of results) {
|
||||
error.ruleAlias = error.ruleNames[1] || error.ruleName;
|
||||
delete error.ruleNames;
|
||||
}
|
||||
} else {
|
||||
rule.function(params, onError);
|
||||
}
|
||||
// Record any errors (significant performance benefit from length check)
|
||||
if (errors.length > 0) {
|
||||
errors.sort(lineNumberComparison);
|
||||
const filteredErrors = errors
|
||||
.filter((resultVersion === 3) ?
|
||||
filterAllValues :
|
||||
uniqueFilterForSortedErrors)
|
||||
.filter(function removeDisabledRules(error) {
|
||||
return enabledRulesPerLineNumber[error.lineNumber][ruleName];
|
||||
})
|
||||
.map(function formatResults(error) {
|
||||
if (resultVersion === 0) {
|
||||
return error.lineNumber;
|
||||
}
|
||||
const errorObject = {};
|
||||
errorObject.lineNumber = error.lineNumber;
|
||||
if (resultVersion === 1) {
|
||||
errorObject.ruleName = ruleNameFriendly;
|
||||
errorObject.ruleAlias = rule.names[1] || rule.names[0];
|
||||
} else {
|
||||
errorObject.ruleNames = rule.names;
|
||||
}
|
||||
errorObject.ruleDescription = rule.description;
|
||||
errorObject.ruleInformation =
|
||||
rule.information ? rule.information.href : null;
|
||||
errorObject.errorDetail = error.detail;
|
||||
errorObject.errorContext = error.context;
|
||||
errorObject.errorRange = error.range;
|
||||
if (resultVersion === 3) {
|
||||
errorObject.fixInfo = error.fixInfo;
|
||||
}
|
||||
return errorObject;
|
||||
});
|
||||
if (filteredErrors.length > 0) {
|
||||
if (resultVersion === 0) {
|
||||
result[ruleNameFriendly] = filteredErrors;
|
||||
} else {
|
||||
Array.prototype.push.apply(result, filteredErrors);
|
||||
}
|
||||
// resultVersion 2 or 3: Remove unwanted ruleName
|
||||
for (const error of results) {
|
||||
delete error.ruleName;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// Run all rules
|
||||
const ruleListAsync = ruleList.filter((rule) => rule.asynchronous);
|
||||
const ruleListSync = ruleList.filter((rule) => !rule.asynchronous);
|
||||
const ruleListAsyncFirst = [
|
||||
...ruleListAsync,
|
||||
...ruleListSync
|
||||
];
|
||||
const callbackSuccess = () => callback(null, formatResults());
|
||||
const callbackError =
|
||||
(error) => callback(error instanceof Error ? error : new Error(error));
|
||||
try {
|
||||
ruleList.forEach(forRule);
|
||||
const ruleResults = ruleListAsyncFirst.map(forRule);
|
||||
if (ruleListAsync.length > 0) {
|
||||
Promise.all(ruleResults.slice(0, ruleListAsync.length))
|
||||
.then(callbackSuccess)
|
||||
.catch(callbackError);
|
||||
} else {
|
||||
callbackSuccess();
|
||||
}
|
||||
} catch (error) {
|
||||
callbackError(error);
|
||||
} finally {
|
||||
cache.clear();
|
||||
return callback(error);
|
||||
}
|
||||
cache.clear();
|
||||
return callback(null, result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -731,7 +748,7 @@ function lintInput(options, synchronous, callback) {
|
|||
callback = callback || function noop() {};
|
||||
// eslint-disable-next-line unicorn/prefer-spread
|
||||
const ruleList = rules.concat(options.customRules || []);
|
||||
const ruleErr = validateRuleList(ruleList);
|
||||
const ruleErr = validateRuleList(ruleList, synchronous);
|
||||
if (ruleErr) {
|
||||
return callback(ruleErr);
|
||||
}
|
||||
|
|
@ -759,62 +776,32 @@ function lintInput(options, synchronous, callback) {
|
|||
const fs = options.fs || require("fs");
|
||||
const results = newResults(ruleList);
|
||||
let done = false;
|
||||
// Linting of strings is always synchronous
|
||||
let syncItem = null;
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function syncCallback(err, result) {
|
||||
if (err) {
|
||||
done = true;
|
||||
return callback(err);
|
||||
}
|
||||
results[syncItem] = result;
|
||||
return null;
|
||||
}
|
||||
while (!done && (syncItem = stringsKeys.shift())) {
|
||||
lintContent(
|
||||
ruleList,
|
||||
syncItem,
|
||||
strings[syncItem] || "",
|
||||
md,
|
||||
config,
|
||||
frontMatter,
|
||||
handleRuleFailures,
|
||||
noInlineConfig,
|
||||
resultVersion,
|
||||
syncCallback
|
||||
);
|
||||
}
|
||||
if (synchronous) {
|
||||
// Lint files synchronously
|
||||
while (!done && (syncItem = files.shift())) {
|
||||
lintFile(
|
||||
ruleList,
|
||||
syncItem,
|
||||
md,
|
||||
config,
|
||||
frontMatter,
|
||||
handleRuleFailures,
|
||||
noInlineConfig,
|
||||
resultVersion,
|
||||
fs,
|
||||
synchronous,
|
||||
syncCallback
|
||||
);
|
||||
}
|
||||
return done || callback(null, results);
|
||||
}
|
||||
// Lint files asynchronously
|
||||
let concurrency = 0;
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function lintConcurrently() {
|
||||
const asyncItem = files.shift();
|
||||
function lintWorker() {
|
||||
let currentItem = null;
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function lintWorkerCallback(err, result) {
|
||||
concurrency--;
|
||||
if (err) {
|
||||
done = true;
|
||||
return callback(err);
|
||||
}
|
||||
results[currentItem] = result;
|
||||
if (!synchronous) {
|
||||
lintWorker();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (done) {
|
||||
// Nothing to do
|
||||
} else if (asyncItem) {
|
||||
// Abort for error or nothing left to do
|
||||
} else if (files.length > 0) {
|
||||
// Lint next file
|
||||
concurrency++;
|
||||
currentItem = files.shift();
|
||||
lintFile(
|
||||
ruleList,
|
||||
asyncItem,
|
||||
currentItem,
|
||||
md,
|
||||
config,
|
||||
frontMatter,
|
||||
|
|
@ -823,34 +810,48 @@ function lintInput(options, synchronous, callback) {
|
|||
resultVersion,
|
||||
fs,
|
||||
synchronous,
|
||||
(err, result) => {
|
||||
concurrency--;
|
||||
if (err) {
|
||||
done = true;
|
||||
return callback(err);
|
||||
}
|
||||
results[asyncItem] = result;
|
||||
lintConcurrently();
|
||||
return null;
|
||||
}
|
||||
lintWorkerCallback
|
||||
);
|
||||
} else if (stringsKeys.length > 0) {
|
||||
// Lint next string
|
||||
concurrency++;
|
||||
currentItem = stringsKeys.shift();
|
||||
lintContent(
|
||||
ruleList,
|
||||
currentItem,
|
||||
strings[currentItem] || "",
|
||||
md,
|
||||
config,
|
||||
frontMatter,
|
||||
handleRuleFailures,
|
||||
noInlineConfig,
|
||||
resultVersion,
|
||||
lintWorkerCallback
|
||||
);
|
||||
} else if (concurrency === 0) {
|
||||
// Finish
|
||||
done = true;
|
||||
return callback(null, results);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access
|
||||
// delay suggests that a concurrency factor of 8 can eliminate the impact
|
||||
// of that delay (i.e., total time is the same as with no delay).
|
||||
lintConcurrently();
|
||||
lintConcurrently();
|
||||
lintConcurrently();
|
||||
lintConcurrently();
|
||||
lintConcurrently();
|
||||
lintConcurrently();
|
||||
lintConcurrently();
|
||||
lintConcurrently();
|
||||
if (synchronous) {
|
||||
while (!done) {
|
||||
lintWorker();
|
||||
}
|
||||
} else {
|
||||
// Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access
|
||||
// delay suggests that a concurrency factor of 8 can eliminate the impact
|
||||
// of that delay (i.e., total time is the same as with no delay).
|
||||
lintWorker();
|
||||
lintWorker();
|
||||
lintWorker();
|
||||
lintWorker();
|
||||
lintWorker();
|
||||
lintWorker();
|
||||
lintWorker();
|
||||
lintWorker();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -906,12 +907,13 @@ function parseConfiguration(name, content, parsers) {
|
|||
let config = null;
|
||||
let message = "";
|
||||
const errors = [];
|
||||
let index = 0;
|
||||
// Try each parser
|
||||
(parsers || [ JSON.parse ]).every((parser) => {
|
||||
try {
|
||||
config = parser(content);
|
||||
} catch (error) {
|
||||
errors.push(error.message);
|
||||
errors.push(`Parser ${index++}: ${error.message}`);
|
||||
}
|
||||
return !config;
|
||||
});
|
||||
|
|
@ -1102,7 +1104,7 @@ function readConfigSync(file, parsers, fs) {
|
|||
* @returns {string} SemVer string.
|
||||
*/
|
||||
function getVersion() {
|
||||
return require("../package.json").version;
|
||||
return require("./constants").version;
|
||||
}
|
||||
|
||||
// Export a/synchronous/Promise APIs
|
||||
|
|
@ -1172,7 +1174,7 @@ module.exports = markdownlint;
|
|||
*
|
||||
* @typedef {Object} RuleOnErrorInfo
|
||||
* @property {number} lineNumber Line number (1-based).
|
||||
* @property {string} [details] Details about the error.
|
||||
* @property {string} [detail] Detail about the error.
|
||||
* @property {string} [context] Context for the error.
|
||||
* @property {number[]} [range] Column number (1-based) and length.
|
||||
* @property {RuleOnErrorFixInfo} [fixInfo] Fix information.
|
||||
|
|
@ -1196,6 +1198,7 @@ module.exports = markdownlint;
|
|||
* @property {string} description Rule description.
|
||||
* @property {URL} [information] Link to more information.
|
||||
* @property {string[]} tags Rule tag(s).
|
||||
* @property {boolean} [asynchronous] True if asynchronous.
|
||||
* @property {RuleFunction} function Rule implementation.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ module.exports = {
|
|||
"function": function MD007(params, onError) {
|
||||
const indent = Number(params.config.indent || 2);
|
||||
const startIndented = !!params.config.start_indented;
|
||||
const startIndent = Number(params.config.start_indent || indent);
|
||||
flattenedLists().forEach((list) => {
|
||||
if (list.unordered && list.parentsUnordered) {
|
||||
list.items.forEach((item) => {
|
||||
const { lineNumber, line } = item;
|
||||
const expectedNesting = list.nesting + (startIndented ? 1 : 0);
|
||||
const expectedIndent = expectedNesting * indent;
|
||||
const expectedIndent =
|
||||
(startIndented ? startIndent : 0) +
|
||||
(list.nesting * indent);
|
||||
const actualIndent = indentFor(item);
|
||||
let range = null;
|
||||
let editColumn = 1;
|
||||
|
|
|
|||
37
lib/md010.js
37
lib/md010.js
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addError, forEachLine } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
const { addError, forEachLine, overlapsAnyRange } = require("../helpers");
|
||||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
|
||||
|
||||
const tabRe = /\t+/g;
|
||||
|
||||
|
|
@ -13,28 +13,33 @@ module.exports = {
|
|||
"tags": [ "whitespace", "hard_tab" ],
|
||||
"function": function MD010(params, onError) {
|
||||
const codeBlocks = params.config.code_blocks;
|
||||
const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
|
||||
const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks;
|
||||
const spacesPerTab = params.config.spaces_per_tab;
|
||||
const spaceMultiplier = (spacesPerTab === undefined) ?
|
||||
1 :
|
||||
Math.max(0, Number(spacesPerTab));
|
||||
const exclusions = includeCode ? [] : codeBlockAndSpanRanges();
|
||||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
||||
if (!inCode || includeCodeBlocks) {
|
||||
if (includeCode || !inCode) {
|
||||
let match = null;
|
||||
while ((match = tabRe.exec(line)) !== null) {
|
||||
const column = match.index + 1;
|
||||
const { index } = match;
|
||||
const column = index + 1;
|
||||
const length = match[0].length;
|
||||
addError(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
"Column: " + column,
|
||||
null,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": "".padEnd(length * spaceMultiplier)
|
||||
});
|
||||
if (!overlapsAnyRange(exclusions, lineIndex, index, length)) {
|
||||
addError(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
"Column: " + column,
|
||||
null,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": "".padEnd(length * spaceMultiplier)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
const { addError, forEachLine, overlapsAnyRange } = require("../helpers");
|
||||
const { inlineCodeSpanRanges, lineMetadata } = require("./cache");
|
||||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
|
||||
|
||||
const reversedLinkRe =
|
||||
/(^|[^\\])\(([^)]+)\)\[([^\]^][^\]]*)](?!\()/g;
|
||||
|
|
@ -13,7 +13,7 @@ module.exports = {
|
|||
"description": "Reversed link syntax",
|
||||
"tags": [ "links" ],
|
||||
"function": function MD011(params, onError) {
|
||||
const exclusions = inlineCodeSpanRanges();
|
||||
const exclusions = codeBlockAndSpanRanges();
|
||||
forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence) => {
|
||||
if (!inCode && !onFence) {
|
||||
let match = null;
|
||||
|
|
|
|||
20
lib/md033.js
20
lib/md033.js
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addError, forEachLine, unescapeMarkdown } = require("../helpers");
|
||||
const { lineMetadata } = require("./cache");
|
||||
const {
|
||||
addError, forEachLine, overlapsAnyRange, unescapeMarkdown
|
||||
} = require("../helpers");
|
||||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
|
||||
|
||||
const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g;
|
||||
const linkDestinationRe = /]\(\s*$/;
|
||||
const inlineCodeRe = /^[^`]*(`+[^`]+`+[^`]+)*`+[^`]*$/;
|
||||
// See https://spec.commonmark.org/0.29/#autolinks
|
||||
const emailAddressRe =
|
||||
// eslint-disable-next-line max-len
|
||||
|
|
@ -21,19 +22,22 @@ module.exports = {
|
|||
let allowedElements = params.config.allowed_elements;
|
||||
allowedElements = Array.isArray(allowedElements) ? allowedElements : [];
|
||||
allowedElements = allowedElements.map((element) => element.toLowerCase());
|
||||
const exclusions = codeBlockAndSpanRanges();
|
||||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
||||
let match = null;
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) {
|
||||
const [ tag, content, element ] = match;
|
||||
if (!allowedElements.includes(element.toLowerCase()) &&
|
||||
if (
|
||||
!allowedElements.includes(element.toLowerCase()) &&
|
||||
!tag.endsWith("\\>") &&
|
||||
!emailAddressRe.test(content)) {
|
||||
!emailAddressRe.test(content) &&
|
||||
!overlapsAnyRange(exclusions, lineIndex, match.index, match[0].length)
|
||||
) {
|
||||
const prefix = line.substring(0, match.index);
|
||||
if (!linkDestinationRe.test(prefix) && !inlineCodeRe.test(prefix)) {
|
||||
if (!linkDestinationRe.test(prefix)) {
|
||||
const unescaped = unescapeMarkdown(prefix + "<", "_");
|
||||
if (!unescaped.endsWith("_") &&
|
||||
((unescaped + "`").match(/`/g).length % 2)) {
|
||||
if (!unescaped.endsWith("_")) {
|
||||
addError(onError, lineIndex + 1, "Element: " + element,
|
||||
null, [ match.index + 1, tag.length ]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ module.exports = {
|
|||
"tags": [ "hr" ],
|
||||
"function": function MD035(params, onError) {
|
||||
let style = String(params.config.style || "consistent");
|
||||
filterTokens(params, "hr", function forToken(token) {
|
||||
const lineTrim = token.line.trim();
|
||||
filterTokens(params, "hr", (token) => {
|
||||
const { lineNumber, markup } = token;
|
||||
if (style === "consistent") {
|
||||
style = lineTrim;
|
||||
style = markup;
|
||||
}
|
||||
addErrorDetailIf(onError, token.lineNumber, style, lineTrim);
|
||||
addErrorDetailIf(onError, lineNumber, style, markup);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const { addErrorContext, emphasisMarkersInContent, forEachLine, isBlankLine } =
|
|||
const { lineMetadata } = require("./cache");
|
||||
|
||||
const emphasisRe = /(^|[^\\]|\\\\)(?:(\*\*?\*?)|(__?_?))/g;
|
||||
const embeddedUnderscoreRe = /([A-Za-z0-9])_([A-Za-z0-9])/g;
|
||||
const asteriskListItemMarkerRe = /^([\s>]*)\*(\s+)/;
|
||||
const leftSpaceRe = /^\s+/;
|
||||
const rightSpaceRe = /\s+$/;
|
||||
|
|
@ -98,14 +99,15 @@ module.exports = {
|
|||
// Emphasis has no meaning here
|
||||
return;
|
||||
}
|
||||
let patchedLine = line.replace(embeddedUnderscoreRe, "$1 $2");
|
||||
if (onItemStart) {
|
||||
// Trim overlapping '*' list item marker
|
||||
line = line.replace(asteriskListItemMarkerRe, "$1 $2");
|
||||
patchedLine = patchedLine.replace(asteriskListItemMarkerRe, "$1 $2");
|
||||
}
|
||||
let match = null;
|
||||
// Match all emphasis-looking runs in the line...
|
||||
while ((match = emphasisRe.exec(line))) {
|
||||
const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex] || [];
|
||||
while ((match = emphasisRe.exec(patchedLine))) {
|
||||
const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex];
|
||||
const matchIndex = match.index + match[1].length;
|
||||
if (ignoreMarkersForLine.includes(matchIndex)) {
|
||||
// Ignore emphasis markers inside code spans and links
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
const { addErrorContext, filterTokens } = require("../helpers");
|
||||
|
||||
const spaceInLinkRe = /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=\(\S*\))/;
|
||||
const spaceInLinkRe =
|
||||
/\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=((?:\([^)]*\))|(?:\[[^\]]*\])))/;
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD039", "no-space-in-links" ],
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ module.exports = {
|
|||
let matchAny = false;
|
||||
let hasError = false;
|
||||
let anyHeadings = false;
|
||||
// eslint-disable-next-line func-style
|
||||
const getExpected = () => requiredHeadings[i++] || "[None]";
|
||||
forEachHeading(params, (heading, content) => {
|
||||
if (!hasError) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
const { addErrorDetailIf, bareUrlRe, escapeForRegExp, forEachLine,
|
||||
overlapsAnyRange, linkRe, linkReferenceRe } = require("../helpers");
|
||||
const { inlineCodeSpanRanges, lineMetadata } = require("./cache");
|
||||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD044", "proper-names" ],
|
||||
|
|
@ -36,7 +36,7 @@ module.exports = {
|
|||
}
|
||||
});
|
||||
if (!includeCodeBlocks) {
|
||||
exclusions.push(...inlineCodeSpanRanges());
|
||||
exclusions.push(...codeBlockAndSpanRanges());
|
||||
}
|
||||
for (const name of names) {
|
||||
const escapedName = escapeForRegExp(name);
|
||||
|
|
|
|||
45
lib/md049.js
Normal file
45
lib/md049.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addError, emphasisOrStrongStyleFor, forEachInlineChild,
|
||||
getNextChildToken, getRangeAndFixInfoIfFound } = require("../helpers");
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD049", "emphasis-style" ],
|
||||
"description": "Emphasis style should be consistent",
|
||||
"tags": [ "emphasis" ],
|
||||
"function": function MD049(params, onError) {
|
||||
let expectedStyle = String(params.config.style || "consistent");
|
||||
forEachInlineChild(params, "em_open", (token, parent) => {
|
||||
const { lineNumber, markup } = token;
|
||||
const markupStyle = emphasisOrStrongStyleFor(markup);
|
||||
if (expectedStyle === "consistent") {
|
||||
expectedStyle = markupStyle;
|
||||
}
|
||||
if (expectedStyle !== markupStyle) {
|
||||
let rangeAndFixInfo = {};
|
||||
const contentToken = getNextChildToken(
|
||||
parent, token, "text", "em_close"
|
||||
);
|
||||
if (contentToken) {
|
||||
const { content } = contentToken;
|
||||
const actual = `${markup}${content}${markup}`;
|
||||
const expectedMarkup = (expectedStyle === "asterisk") ? "*" : "_";
|
||||
const expected = `${expectedMarkup}${content}${expectedMarkup}`;
|
||||
rangeAndFixInfo = getRangeAndFixInfoIfFound(
|
||||
params.lines, lineNumber - 1, actual, expected
|
||||
);
|
||||
}
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
`Expected: ${expectedStyle}; Actual: ${markupStyle}`,
|
||||
null,
|
||||
rangeAndFixInfo.range,
|
||||
rangeAndFixInfo.fixInfo
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
45
lib/md050.js
Normal file
45
lib/md050.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addError, emphasisOrStrongStyleFor, forEachInlineChild,
|
||||
getNextChildToken, getRangeAndFixInfoIfFound } = require("../helpers");
|
||||
|
||||
module.exports = {
|
||||
"names": [ "MD050", "strong-style" ],
|
||||
"description": "Strong style should be consistent",
|
||||
"tags": [ "emphasis" ],
|
||||
"function": function MD050(params, onError) {
|
||||
let expectedStyle = String(params.config.style || "consistent");
|
||||
forEachInlineChild(params, "strong_open", (token, parent) => {
|
||||
const { lineNumber, markup } = token;
|
||||
const markupStyle = emphasisOrStrongStyleFor(markup);
|
||||
if (expectedStyle === "consistent") {
|
||||
expectedStyle = markupStyle;
|
||||
}
|
||||
if (expectedStyle !== markupStyle) {
|
||||
let rangeAndFixInfo = {};
|
||||
const contentToken = getNextChildToken(
|
||||
parent, token, "text", "strong_close"
|
||||
);
|
||||
if (contentToken) {
|
||||
const { content } = contentToken;
|
||||
const actual = `${markup}${content}${markup}`;
|
||||
const expectedMarkup = (expectedStyle === "asterisk") ? "**" : "__";
|
||||
const expected = `${expectedMarkup}${content}${expectedMarkup}`;
|
||||
rangeAndFixInfo = getRangeAndFixInfoIfFound(
|
||||
params.lines, lineNumber - 1, actual, expected
|
||||
);
|
||||
}
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
`Expected: ${expectedStyle}; Actual: ${markupStyle}`,
|
||||
null,
|
||||
rangeAndFixInfo.range,
|
||||
rangeAndFixInfo.fixInfo
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const packageJson = require("../package.json");
|
||||
const homepage = packageJson.homepage;
|
||||
const version = packageJson.version;
|
||||
const { homepage, version } = require("./constants");
|
||||
|
||||
const rules = [
|
||||
require("./md001"),
|
||||
|
|
@ -50,7 +48,9 @@ const rules = [
|
|||
require("./md045"),
|
||||
require("./md046"),
|
||||
require("./md047"),
|
||||
require("./md048")
|
||||
require("./md048"),
|
||||
require("./md049"),
|
||||
require("./md050")
|
||||
];
|
||||
rules.forEach((rule) => {
|
||||
const name = rule.names[0].toLowerCase();
|
||||
|
|
|
|||
41
package.json
41
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "markdownlint",
|
||||
"version": "0.24.0",
|
||||
"version": "0.25.0",
|
||||
"description": "A Node.js style checker and lint tool for Markdown/CommonMark files.",
|
||||
"main": "lib/markdownlint.js",
|
||||
"types": "lib/markdownlint.d.ts",
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'",
|
||||
"build-demo": "node scripts copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats",
|
||||
"build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2",
|
||||
"ci": "npm-run-all --continue-on-error --parallel declaration lint --parallel build-config build-demo test-cover && git diff --exit-code",
|
||||
"ci": "npm-run-all --continue-on-error --parallel build-config lint serial-declaration-demo test-cover && git diff --exit-code",
|
||||
"clone-test-repos-dotnet-docs": "cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet",
|
||||
"clone-test-repos-eslint-eslint": "cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet",
|
||||
"clone-test-repos-mkdocs-mkdocs": "cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet",
|
||||
|
|
@ -32,41 +32,46 @@
|
|||
"clone-test-repos-large": "npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev",
|
||||
"declaration": "npm run build-declaration && npm run test-declaration",
|
||||
"example": "cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint",
|
||||
"docker-npm-install": "docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install",
|
||||
"docker-npm-run-upgrade": "docker run --rm --tty --name npm-run-upgrade --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm run upgrade",
|
||||
"lint": "eslint --max-warnings 0 .",
|
||||
"lint-test-repos": "ava --timeout=5m test/markdownlint-test-repos.js",
|
||||
"test": "ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js",
|
||||
"serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration",
|
||||
"test": "ava test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js",
|
||||
"test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test",
|
||||
"test-declaration": "cd example/typescript && tsc && node type-check.js",
|
||||
"test-extra": "ava --timeout=5m test/markdownlint-test-extra.js"
|
||||
"test-extra": "ava --timeout=5m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js",
|
||||
"upgrade": "npx npm-check-updates --upgrade"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12"
|
||||
},
|
||||
"dependencies": {
|
||||
"markdown-it": "12.2.0"
|
||||
"markdown-it": "12.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "~3.15.0",
|
||||
"c8": "~7.8.0",
|
||||
"eslint": "~7.32.0",
|
||||
"eslint-plugin-jsdoc": "~36.0.7",
|
||||
"c8": "~7.10.0",
|
||||
"eslint": "~8.5.0",
|
||||
"eslint-plugin-jsdoc": "~37.4.0",
|
||||
"eslint-plugin-node": "~11.1.0",
|
||||
"eslint-plugin-unicorn": "~35.0.0",
|
||||
"globby": "~11.0.4",
|
||||
"eslint-plugin-unicorn": "~39.0.0",
|
||||
"globby": "~12.0.2",
|
||||
"js-yaml": "~4.1.0",
|
||||
"markdown-it-for-inline": "~0.1.1",
|
||||
"markdown-it-sub": "~1.0.0",
|
||||
"markdown-it-sup": "~1.0.0",
|
||||
"markdown-it-texmath": "~0.9.1",
|
||||
"markdownlint-rule-helpers": "~0.14.0",
|
||||
"markdown-it-texmath": "~0.9.7",
|
||||
"markdownlint-rule-github-internal-links": "~0.1.0",
|
||||
"markdownlint-rule-helpers": "~0.15.0",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"strip-json-comments": "~3.1.1",
|
||||
"strip-json-comments": "~4.0.0",
|
||||
"toml": "~3.0.0",
|
||||
"ts-loader": "~9.2.5",
|
||||
"ts-loader": "~9.2.6",
|
||||
"tv4": "~1.3.0",
|
||||
"typescript": "~4.3.5",
|
||||
"webpack": "~5.51.1",
|
||||
"webpack-cli": "~4.8.0"
|
||||
"typescript": "~4.5.4",
|
||||
"webpack": "~5.65.0",
|
||||
"webpack-cli": "~4.9.1"
|
||||
},
|
||||
"keywords": [
|
||||
"markdown",
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@
|
|||
// Spaces for indent
|
||||
"indent": 2,
|
||||
// Whether to indent the first level of the list
|
||||
"start_indented": false
|
||||
"start_indented": false,
|
||||
// Spaces for first level indent (when start_indented is set)
|
||||
"start_indent": 2
|
||||
},
|
||||
|
||||
// MD009/no-trailing-spaces - Trailing spaces
|
||||
|
|
@ -248,5 +250,17 @@
|
|||
"MD048": {
|
||||
// Code fence style
|
||||
"style": "consistent"
|
||||
},
|
||||
|
||||
// MD049/emphasis-style - Emphasis style should be consistent
|
||||
"MD049": {
|
||||
// Emphasis style should be consistent
|
||||
"style": "consistent"
|
||||
},
|
||||
|
||||
// MD050/strong-style - Strong style should be consistent
|
||||
"MD050": {
|
||||
// Strong style should be consistent
|
||||
"style": "consistent"
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,8 @@ MD007:
|
|||
indent: 2
|
||||
# Whether to indent the first level of the list
|
||||
start_indented: false
|
||||
# Spaces for first level indent (when start_indented is set)
|
||||
start_indent: 2
|
||||
|
||||
# MD009/no-trailing-spaces - Trailing spaces
|
||||
MD009:
|
||||
|
|
@ -224,4 +226,14 @@ MD047: true
|
|||
# MD048/code-fence-style - Code fence style
|
||||
MD048:
|
||||
# Code fence style
|
||||
style: "consistent"
|
||||
|
||||
# MD049/emphasis-style - Emphasis style should be consistent
|
||||
MD049:
|
||||
# Emphasis style should be consistent
|
||||
style: "consistent"
|
||||
|
||||
# MD050/strong-style - Strong style should be consistent
|
||||
MD050:
|
||||
# Strong style should be consistent
|
||||
style: "consistent"
|
||||
|
|
@ -107,6 +107,12 @@ rules.forEach(function forRule(rule) {
|
|||
"description": "Whether to indent the first level of the list",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"start_indent": {
|
||||
"description":
|
||||
"Spaces for first level indent (when start_indented is set)",
|
||||
"type": "integer",
|
||||
"default": 2
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
|
@ -396,6 +402,34 @@ rules.forEach(function forRule(rule) {
|
|||
}
|
||||
};
|
||||
break;
|
||||
case "MD049":
|
||||
scheme.properties = {
|
||||
"style": {
|
||||
"description": "Emphasis style should be consistent",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"consistent",
|
||||
"asterisk",
|
||||
"underscore"
|
||||
],
|
||||
"default": "consistent"
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "MD050":
|
||||
scheme.properties = {
|
||||
"style": {
|
||||
"description": "Strong style should be consistent",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"consistent",
|
||||
"asterisk",
|
||||
"underscore"
|
||||
],
|
||||
"default": "consistent"
|
||||
}
|
||||
};
|
||||
break;
|
||||
default:
|
||||
custom = false;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -238,6 +238,11 @@
|
|||
"description": "Whether to indent the first level of the list",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"start_indent": {
|
||||
"description": "Spaces for first level indent (when start_indented is set)",
|
||||
"type": "integer",
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
@ -259,6 +264,11 @@
|
|||
"description": "Whether to indent the first level of the list",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"start_indent": {
|
||||
"description": "Spaces for first level indent (when start_indented is set)",
|
||||
"type": "integer",
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
@ -1439,6 +1449,90 @@
|
|||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"MD049": {
|
||||
"description": "MD049/emphasis-style - Emphasis style should be consistent",
|
||||
"type": [
|
||||
"boolean",
|
||||
"object"
|
||||
],
|
||||
"default": true,
|
||||
"properties": {
|
||||
"style": {
|
||||
"description": "Emphasis style should be consistent",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"consistent",
|
||||
"asterisk",
|
||||
"underscore"
|
||||
],
|
||||
"default": "consistent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"emphasis-style": {
|
||||
"description": "MD049/emphasis-style - Emphasis style should be consistent",
|
||||
"type": [
|
||||
"boolean",
|
||||
"object"
|
||||
],
|
||||
"default": true,
|
||||
"properties": {
|
||||
"style": {
|
||||
"description": "Emphasis style should be consistent",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"consistent",
|
||||
"asterisk",
|
||||
"underscore"
|
||||
],
|
||||
"default": "consistent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"MD050": {
|
||||
"description": "MD050/strong-style - Strong style should be consistent",
|
||||
"type": [
|
||||
"boolean",
|
||||
"object"
|
||||
],
|
||||
"default": true,
|
||||
"properties": {
|
||||
"style": {
|
||||
"description": "Strong style should be consistent",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"consistent",
|
||||
"asterisk",
|
||||
"underscore"
|
||||
],
|
||||
"default": "consistent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"strong-style": {
|
||||
"description": "MD050/strong-style - Strong style should be consistent",
|
||||
"type": [
|
||||
"boolean",
|
||||
"object"
|
||||
],
|
||||
"default": true,
|
||||
"properties": {
|
||||
"style": {
|
||||
"description": "Strong style should be consistent",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"consistent",
|
||||
"asterisk",
|
||||
"underscore"
|
||||
],
|
||||
"default": "consistent"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"headings": {
|
||||
"description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043",
|
||||
"type": "boolean",
|
||||
|
|
@ -1535,7 +1629,7 @@
|
|||
"default": true
|
||||
},
|
||||
"emphasis": {
|
||||
"description": "emphasis - MD036, MD037",
|
||||
"description": "emphasis - MD036, MD037, MD049, MD050",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,20 +2,27 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const globby = require("globby");
|
||||
const fs = require("fs").promises;
|
||||
|
||||
const [ command, ...args ] = process.argv.slice(2);
|
||||
|
||||
if (command === "copy") {
|
||||
const [ src, dest ] = args;
|
||||
fs.copyFileSync(src, dest);
|
||||
} else if (command === "delete") {
|
||||
for (const arg of args) {
|
||||
for (const file of globby.sync(arg)) {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||
(async() => {
|
||||
if (command === "copy") {
|
||||
const [ src, dest ] = args;
|
||||
await fs.copyFile(src, dest);
|
||||
} else if (command === "delete") {
|
||||
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
||||
const { globby } = await import("globby");
|
||||
await Promise.all(
|
||||
args.flatMap(
|
||||
(glob) => globby(glob)
|
||||
.then(
|
||||
(files) => files.map((file) => fs.unlink(file))
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Unsupported command: ${command}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unsupported command: ${command}`);
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ https://example.com/page {MD034}
|
|||
|
||||
_Section {MD036} Heading_
|
||||
|
||||
Emphasis *with * space {MD037}
|
||||
Emphasis _with _ space {MD037}
|
||||
|
||||
Code `with ` space {MD038}
|
||||
|
||||
|
|
@ -85,4 +85,12 @@ markdownLint {MD044}
|
|||
 {MD045}
|
||||
## Heading 10 {MD022}
|
||||
|
||||
Emphasis _with_ underscore style
|
||||
|
||||
Emphasis *with* different style {MD049}
|
||||
|
||||
Strong __with__ underscore style
|
||||
|
||||
Strong **with** different style {MD050}
|
||||
|
||||
EOF {MD047}
|
||||
38
test/code-blocks-and-spans.md
Normal file
38
test/code-blocks-and-spans.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Code Blocks and Spans {MD044}
|
||||
|
||||
Text CODE text {MD044}
|
||||
|
||||
Text `CODE` text
|
||||
|
||||
```lang
|
||||
CODE
|
||||
|
||||
CODE
|
||||
```
|
||||
|
||||
`CODE` text `CODE`
|
||||
|
||||
CODE
|
||||
|
||||
CODE
|
||||
|
||||
Text `CODE
|
||||
CODE` text
|
||||
text text
|
||||
text `CODE
|
||||
CODE CODE
|
||||
CODE` text
|
||||
|
||||
Text `CODE {MD044}
|
||||
|
||||
Text `CODE {MD044}
|
||||
|
||||
<!-- markdownlint-configure-file {
|
||||
"proper-names": {
|
||||
"names": [
|
||||
"code"
|
||||
],
|
||||
"code_blocks": false
|
||||
},
|
||||
"code-block-style": false
|
||||
} -->
|
||||
33
test/code-with-tabs-allowed.md
Normal file
33
test/code-with-tabs-allowed.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Code With Tabs Allowed
|
||||
|
||||
Text text {MD010}
|
||||
|
||||
Text `code code` text
|
||||
|
||||
Text ` code` text
|
||||
|
||||
Text `code ` text
|
||||
|
||||
Text `code code
|
||||
code code
|
||||
code code` text
|
||||
|
||||
console.log(" ");
|
||||
|
||||
```js
|
||||
console.log(" ");
|
||||
```
|
||||
|
||||
```j s {MD010}
|
||||
console.log(" ");
|
||||
```
|
||||
|
||||
console.log("");
|
||||
|
||||
<!-- markdownlint-configure-file {
|
||||
"code-block-style": false,
|
||||
"no-space-in-code": false,
|
||||
"no-hard-tabs": {
|
||||
"code_blocks": false
|
||||
}
|
||||
} -->
|
||||
33
test/code-with-tabs-blocked.md
Normal file
33
test/code-with-tabs-blocked.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Code With Tabs Blocked
|
||||
|
||||
Text text {MD010}
|
||||
|
||||
Text `code code` text {MD010}
|
||||
|
||||
Text ` code` text {MD010}
|
||||
|
||||
Text `code ` text {MD010}
|
||||
|
||||
Text `code code
|
||||
code code {MD010}
|
||||
code code` text
|
||||
|
||||
console.log(" "); // {MD010}
|
||||
|
||||
```js
|
||||
console.log(" "); // {MD010}
|
||||
```
|
||||
|
||||
```j s {MD010}
|
||||
console.log(" "); // {MD010}
|
||||
```
|
||||
|
||||
console.log(""); // {MD010}
|
||||
|
||||
<!-- markdownlint-configure-file {
|
||||
"code-block-style": false,
|
||||
"no-space-in-code": false,
|
||||
"no-hard-tabs": {
|
||||
"code_blocks": true
|
||||
}
|
||||
} -->
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"default": true,
|
||||
"MD041": false
|
||||
"MD041": false,
|
||||
"MD049": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,4 +28,18 @@ Fenced code
|
|||
Fenced code
|
||||
~~~
|
||||
|
||||
Mixed *emphasis* on _this_ line *with* multiple _issues_
|
||||
|
||||
Mixed __strong emphasis__ on **this** line __with__ multiple **issues**
|
||||
|
||||
Inconsistent
|
||||
emphasis _text
|
||||
spanning_ many
|
||||
lines
|
||||
|
||||
Inconsistent
|
||||
strong **emphasis
|
||||
spanning** many
|
||||
lines
|
||||
|
||||
Missing newline character
|
||||
|
|
@ -28,4 +28,18 @@ Fenced code
|
|||
Fenced code
|
||||
~~~
|
||||
|
||||
Mixed *emphasis* on *this* line *with* multiple *issues*
|
||||
|
||||
Mixed __strong emphasis__ on __this__ line __with__ multiple __issues__
|
||||
|
||||
Inconsistent
|
||||
emphasis _text
|
||||
spanning_ many
|
||||
lines
|
||||
|
||||
Inconsistent
|
||||
strong **emphasis
|
||||
spanning** many
|
||||
lines
|
||||
|
||||
Missing newline character
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
"fixInfo": null
|
||||
},
|
||||
{
|
||||
"lineNumber": 31,
|
||||
"lineNumber": 45,
|
||||
"ruleNames": [
|
||||
"MD043",
|
||||
"required-headings",
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
"fixInfo": null
|
||||
},
|
||||
{
|
||||
"lineNumber": 31,
|
||||
"lineNumber": 45,
|
||||
"ruleNames": [
|
||||
"MD047",
|
||||
"single-trailing-newline"
|
||||
|
|
@ -208,5 +208,111 @@
|
|||
"errorContext": null,
|
||||
"errorRange": null,
|
||||
"fixInfo": null
|
||||
},
|
||||
{
|
||||
"errorContext": null,
|
||||
"errorDetail": "Expected: asterisk; Actual: underscore",
|
||||
"errorRange": [
|
||||
21,
|
||||
6
|
||||
],
|
||||
"fixInfo": {
|
||||
"deleteCount": 6,
|
||||
"editColumn": 21,
|
||||
"insertText": "*this*"
|
||||
},
|
||||
"lineNumber": 31,
|
||||
"ruleDescription": "Emphasis style should be consistent",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049",
|
||||
"ruleNames": [
|
||||
"MD049",
|
||||
"emphasis-style"
|
||||
]
|
||||
},
|
||||
{
|
||||
"errorContext": null,
|
||||
"errorDetail": "Expected: asterisk; Actual: underscore",
|
||||
"errorRange": [
|
||||
49,
|
||||
8
|
||||
],
|
||||
"fixInfo": {
|
||||
"deleteCount": 8,
|
||||
"editColumn": 49,
|
||||
"insertText": "*issues*"
|
||||
},
|
||||
"lineNumber": 31,
|
||||
"ruleDescription": "Emphasis style should be consistent",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049",
|
||||
"ruleNames": [
|
||||
"MD049",
|
||||
"emphasis-style"
|
||||
]
|
||||
},
|
||||
{
|
||||
"errorContext": null,
|
||||
"errorDetail": "Expected: asterisk; Actual: underscore",
|
||||
"errorRange": null,
|
||||
"fixInfo": null,
|
||||
"lineNumber": 36,
|
||||
"ruleDescription": "Emphasis style should be consistent",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049",
|
||||
"ruleNames": [
|
||||
"MD049",
|
||||
"emphasis-style"
|
||||
]
|
||||
},
|
||||
{
|
||||
"errorContext": null,
|
||||
"errorDetail": "Expected: underscore; Actual: asterisk",
|
||||
"errorRange": [
|
||||
30,
|
||||
8
|
||||
],
|
||||
"fixInfo": {
|
||||
"deleteCount": 8,
|
||||
"editColumn": 30,
|
||||
"insertText": "__this__"
|
||||
},
|
||||
"lineNumber": 33,
|
||||
"ruleDescription": "Strong style should be consistent",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050",
|
||||
"ruleNames": [
|
||||
"MD050",
|
||||
"strong-style"
|
||||
]
|
||||
},
|
||||
{
|
||||
"errorContext": null,
|
||||
"errorDetail": "Expected: underscore; Actual: asterisk",
|
||||
"errorRange": [
|
||||
62,
|
||||
10
|
||||
],
|
||||
"fixInfo": {
|
||||
"deleteCount": 10,
|
||||
"editColumn": 62,
|
||||
"insertText": "__issues__"
|
||||
},
|
||||
"lineNumber": 33,
|
||||
"ruleDescription": "Strong style should be consistent",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050",
|
||||
"ruleNames": [
|
||||
"MD050",
|
||||
"strong-style"
|
||||
]
|
||||
},
|
||||
{
|
||||
"errorContext": null,
|
||||
"errorDetail": "Expected: underscore; Actual: asterisk",
|
||||
"errorRange": null,
|
||||
"fixInfo": null,
|
||||
"lineNumber": 41,
|
||||
"ruleDescription": "Strong style should be consistent",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050",
|
||||
"ruleNames": [
|
||||
"MD050",
|
||||
"strong-style"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -28,3 +28,17 @@ Text <https://example.com/same> more \* text https://example.com/same more \[ te
|
|||
Text https://example.com/first more text https://example.com/second still more text https://example.com/third done
|
||||
|
||||
(Incorrect link syntax)[https://www.example.com/]
|
||||
|
||||
Text [link ](https://example.com/) text.
|
||||
|
||||
Text [ link](https://example.com/) text.
|
||||
|
||||
Text [ link ](https://example.com/) text.
|
||||
|
||||
Text [link ][reference] text.
|
||||
|
||||
Text [ link][reference] text.
|
||||
|
||||
Text [ link ][reference] text.
|
||||
|
||||
[reference]: https://example.com/
|
||||
|
|
|
|||
|
|
@ -28,3 +28,17 @@ Text <https://example.com/same> more \* text https://example.com/same more \[ te
|
|||
Text <https://example.com/first> more text <https://example.com/second> still more text <https://example.com/third> done
|
||||
|
||||
[Incorrect link syntax](https://www.example.com/)
|
||||
|
||||
Text [link](https://example.com/) text.
|
||||
|
||||
Text [link](https://example.com/) text.
|
||||
|
||||
Text [link](https://example.com/) text.
|
||||
|
||||
Text [link][reference] text.
|
||||
|
||||
Text [link][reference] text.
|
||||
|
||||
Text [link][reference] text.
|
||||
|
||||
[reference]: https://example.com/
|
||||
|
|
|
|||
|
|
@ -271,5 +271,125 @@
|
|||
"deleteCount": 25,
|
||||
"insertText": "<https://example.com/third>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 32,
|
||||
"ruleNames": [
|
||||
"MD039",
|
||||
"no-space-in-links"
|
||||
],
|
||||
"ruleDescription": "Spaces inside link text",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[link ]",
|
||||
"errorRange": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 5,
|
||||
"insertText": "link"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 34,
|
||||
"ruleNames": [
|
||||
"MD039",
|
||||
"no-space-in-links"
|
||||
],
|
||||
"ruleDescription": "Spaces inside link text",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[ link]",
|
||||
"errorRange": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 5,
|
||||
"insertText": "link"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 36,
|
||||
"ruleNames": [
|
||||
"MD039",
|
||||
"no-space-in-links"
|
||||
],
|
||||
"ruleDescription": "Spaces inside link text",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[ link ]",
|
||||
"errorRange": [
|
||||
6,
|
||||
8
|
||||
],
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 6,
|
||||
"insertText": "link"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 38,
|
||||
"ruleNames": [
|
||||
"MD039",
|
||||
"no-space-in-links"
|
||||
],
|
||||
"ruleDescription": "Spaces inside link text",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[link ]",
|
||||
"errorRange": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 5,
|
||||
"insertText": "link"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 40,
|
||||
"ruleNames": [
|
||||
"MD039",
|
||||
"no-space-in-links"
|
||||
],
|
||||
"ruleDescription": "Spaces inside link text",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[ link]",
|
||||
"errorRange": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 5,
|
||||
"insertText": "link"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 42,
|
||||
"ruleNames": [
|
||||
"MD039",
|
||||
"no-space-in-links"
|
||||
],
|
||||
"ruleDescription": "Spaces inside link text",
|
||||
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039",
|
||||
"errorDetail": null,
|
||||
"errorContext": "[ link ]",
|
||||
"errorRange": [
|
||||
6,
|
||||
8
|
||||
],
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 6,
|
||||
"insertText": "link"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -8,11 +8,11 @@ Text
|
|||
|
||||
Text
|
||||
|
||||
> *Text*
|
||||
> *Text* {MD049}
|
||||
|
||||
Text
|
||||
|
||||
> *Text text text*
|
||||
> *Text text text* {MD049}
|
||||
|
||||
Text
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
|
|||
eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
|
||||
in culpa qui officia deserunt mollit anim id est laborum.
|
||||
|
||||
__Section 1.1: another section {MD036}__
|
||||
__Section 1.1: another section {MD036} {MD050}__
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
|
|
@ -27,7 +27,7 @@ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
|
|||
eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
|
||||
in culpa qui officia deserunt mollit anim id est laborum.
|
||||
|
||||
_Section 3: oh no more sections {MD036}_
|
||||
_Section 3: oh no more sections {MD036} {MD049}_
|
||||
|
||||
This is a normal paragraph
|
||||
**that just happens to have emphasized text in**
|
||||
|
|
|
|||
6
test/emphasis_style_asterisk.json
Normal file
6
test/emphasis_style_asterisk.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"default": true,
|
||||
"MD049": {
|
||||
"style": "asterisk"
|
||||
}
|
||||
}
|
||||
5
test/emphasis_style_asterisk.md
Normal file
5
test/emphasis_style_asterisk.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Emphasis style asterisk
|
||||
|
||||
This is *fine*
|
||||
|
||||
This is _not_ {MD049}
|
||||
6
test/emphasis_style_underscore.json
Normal file
6
test/emphasis_style_underscore.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"default": true,
|
||||
"MD049": {
|
||||
"style": "underscore"
|
||||
}
|
||||
}
|
||||
5
test/emphasis_style_underscore.md
Normal file
5
test/emphasis_style_underscore.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Emphasis style underscore
|
||||
|
||||
This is _fine_
|
||||
|
||||
This is *not* {MD049}
|
||||
|
|
@ -8,3 +8,5 @@
|
|||
[test **test** test](www.test.com)
|
||||
[test __test__ test](www.test.com)
|
||||
[this should not raise](www.shouldnotraise.com)
|
||||
|
||||
<!-- markdownlint-disable-file MD049 MD050 -->
|
||||
|
|
|
|||
20
test/front-matter-with-disable-next-line.md
Normal file
20
test/front-matter-with-disable-next-line.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
front: matter
|
||||
---
|
||||
|
||||
# Front Matter with Disable-Next-Line
|
||||
|
||||
<!-- markdownlint-disable-next-line no-inline-html -->
|
||||
<hr/>
|
||||
|
||||
<hr/> {MD033}
|
||||
|
||||
<!-- markdownlint-disable-next-line -->
|
||||
<hr/>
|
||||
|
||||
<hr/> {MD033}
|
||||
<hr/> {MD033}
|
||||
<!-- markdownlint-disable-next-line -->
|
||||
<hr/>
|
||||
<hr/> {MD033}
|
||||
<hr/> {MD033}
|
||||
39
test/hr-in-blockquote-dash.md
Normal file
39
test/hr-in-blockquote-dash.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# HR in Blockquote, Dash
|
||||
|
||||
---
|
||||
|
||||
***
|
||||
|
||||
___
|
||||
|
||||
{MD035:5} {MD035:7}
|
||||
|
||||
> Text
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> ***
|
||||
>
|
||||
> ___
|
||||
>
|
||||
> Text
|
||||
|
||||
{MD035:15} {MD035:17}
|
||||
|
||||
- - -
|
||||
|
||||
> Text
|
||||
>
|
||||
> > Text
|
||||
> >
|
||||
> > ---
|
||||
> >
|
||||
> > ***
|
||||
> >
|
||||
> > ___
|
||||
> >
|
||||
> > Text
|
||||
>
|
||||
> Text
|
||||
|
||||
{MD035:31} {MD035:33}
|
||||
39
test/hr-in-blockquote-star.md
Normal file
39
test/hr-in-blockquote-star.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# HR in Blockquote, Star
|
||||
|
||||
***
|
||||
|
||||
___
|
||||
|
||||
---
|
||||
|
||||
{MD035:5} {MD035:7}
|
||||
|
||||
> Text
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> ***
|
||||
>
|
||||
> ___
|
||||
>
|
||||
> Text
|
||||
|
||||
{MD035:13} {MD035:17}
|
||||
|
||||
* * *
|
||||
|
||||
> Text
|
||||
>
|
||||
> > Text
|
||||
> >
|
||||
> > ---
|
||||
> >
|
||||
> > ***
|
||||
> >
|
||||
> > ___
|
||||
> >
|
||||
> > Text
|
||||
>
|
||||
> Text
|
||||
|
||||
{MD035:29} {MD035:33}
|
||||
39
test/hr-in-blockquote-under.md
Normal file
39
test/hr-in-blockquote-under.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# HR in Blockquote, Under
|
||||
|
||||
___
|
||||
|
||||
---
|
||||
|
||||
***
|
||||
|
||||
{MD035:5} {MD035:7}
|
||||
|
||||
> Text
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> ***
|
||||
>
|
||||
> ___
|
||||
>
|
||||
> Text
|
||||
|
||||
{MD035:13} {MD035:15}
|
||||
|
||||
_ _ _
|
||||
|
||||
> Text
|
||||
>
|
||||
> > Text
|
||||
> >
|
||||
> > ---
|
||||
> >
|
||||
> > ***
|
||||
> >
|
||||
> > ___
|
||||
> >
|
||||
> > Text
|
||||
>
|
||||
> Text
|
||||
|
||||
{MD035:29} {MD035:31}
|
||||
|
|
@ -20,5 +20,4 @@ _____
|
|||
|
||||
***
|
||||
|
||||
{MD035:3} {MD035:5} {MD035:7} {MD035:11} {MD035:13} {MD035:15} {MD035:17}
|
||||
{MD035:19} {MD035:21}
|
||||
{MD035:3} {MD035:5} {MD035:7} {MD035:13} {MD035:15} {MD035:17} {MD035:19} {MD035:21}
|
||||
|
|
|
|||
|
|
@ -20,5 +20,4 @@ _____
|
|||
|
||||
***
|
||||
|
||||
{MD035:5} {MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17}
|
||||
{MD035:19}
|
||||
{MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17} {MD035:19}
|
||||
|
|
|
|||
|
|
@ -20,5 +20,4 @@ _____
|
|||
|
||||
***
|
||||
|
||||
{MD035:5} {MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17}
|
||||
{MD035:19}
|
||||
{MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17} {MD035:19}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,63 @@ Text \` text `<code>` text \` text `<code>` text
|
|||
Text \`\` text `<code>` text
|
||||
Text `<code>` text \` text `<code>` text
|
||||
|
||||
## Elements in multiple line code spans
|
||||
|
||||
Text `code
|
||||
<element/>`
|
||||
|
||||
`code
|
||||
<element/>`
|
||||
|
||||
`code
|
||||
<element/>` text
|
||||
|
||||
Text `code
|
||||
code
|
||||
<element/>
|
||||
<element/>`
|
||||
|
||||
``code ``` ```` `
|
||||
<code>code
|
||||
</code>``
|
||||
|
||||
Text `code
|
||||
</element>
|
||||
code` text
|
||||
|
||||
Text `code code
|
||||
code <element>` text
|
||||
|
||||
Text `code <element>
|
||||
code code` text
|
||||
|
||||
Text `code code
|
||||
code <element> code
|
||||
code code` text
|
||||
|
||||
Text ````code code
|
||||
code <element> code
|
||||
code code```` text
|
||||
|
||||
Text `code code
|
||||
code <element>` text
|
||||
text `code code
|
||||
code code` text
|
||||
|
||||
Text `code code
|
||||
code code` text
|
||||
text `code code
|
||||
code <element>` text
|
||||
|
||||
Text `code code
|
||||
code <element>` text
|
||||
text `code code
|
||||
code <element>` text
|
||||
|
||||
Text `code code
|
||||
code` text <element> text `code {MD033}
|
||||
code code` text
|
||||
|
||||
## Slash in element name
|
||||
|
||||
Text **\<base directory>\another\directory\\<slash/directory>** text
|
||||
|
|
@ -39,3 +96,25 @@ Text **\<base directory>\another\directory\\<slash/directory>** text
|
|||
<a href="https://example.com" target="_blank">Google</a> {MD033}
|
||||
|
||||
<a href="https://example.com:9999" target="_blank">Google</a> {MD033}
|
||||
|
||||
## Unterminated code span followed by element in code span
|
||||
|
||||
Text text `text text
|
||||
|
||||
Text `<element>` text
|
||||
|
||||
Text
|
||||
text `text
|
||||
text
|
||||
|
||||
Text `code <element> code` text
|
||||
|
||||
```lang
|
||||
code {MD046:112}
|
||||
|
||||
<element>
|
||||
```
|
||||
|
||||
Text `code <element> code` text
|
||||
|
||||
Text <element> text {MD033}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
[This link has `code` and right space ](link) {MD039}
|
||||
|
||||
[ This link has _emphasis_ and left space](link) {MD039}
|
||||
[ This link has *emphasis* and left space](link) {MD039}
|
||||
|
||||
[This](link) line has [multiple](link) links.
|
||||
|
||||
|
|
|
|||
35
test/list-indentation-start-indent-indent.md
Normal file
35
test/list-indentation-start-indent-indent.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# List Indentation start_indent/indent
|
||||
|
||||
* item 1
|
||||
* item 2
|
||||
* item 2.1
|
||||
* item 2.2
|
||||
* item 2.2.1
|
||||
* item 2.3
|
||||
* item 2.3.1 {MD007}
|
||||
* item 3
|
||||
* item 4 {MD005} {MD007}
|
||||
|
||||
Text
|
||||
|
||||
* item 1 {MD007}
|
||||
* item 2 {MD007}
|
||||
* item 2.1
|
||||
* item 2.2
|
||||
* item 2.2.1
|
||||
|
||||
Text
|
||||
|
||||
* item 1
|
||||
* item 2
|
||||
* item 2.1 {MD007}
|
||||
* item 2.2 {MD007}
|
||||
* item 2.2.1 {MD007}
|
||||
|
||||
<!-- markdownlint-configure-file {
|
||||
"ul-indent": {
|
||||
"indent": 3,
|
||||
"start_indented": true,
|
||||
"start_indent": 1
|
||||
}
|
||||
} -->
|
||||
34
test/list-indentation-start-indent-no-indent.md
Normal file
34
test/list-indentation-start-indent-no-indent.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# List Indentation start_indent/no indent
|
||||
|
||||
* item 1
|
||||
* item 2
|
||||
* item 2.1
|
||||
* item 2.2
|
||||
* item 2.2.1
|
||||
* item 2.3
|
||||
* item 2.3.1 {MD007}
|
||||
* item 3
|
||||
* item 4 {MD005} {MD007}
|
||||
|
||||
Text
|
||||
|
||||
* item 1 {MD007}
|
||||
* item 2 {MD007}
|
||||
* item 2.1 {MD007}
|
||||
* item 2.2 {MD007}
|
||||
* item 2.2.1 {MD007}
|
||||
|
||||
Text
|
||||
|
||||
* item 1
|
||||
* item 2
|
||||
* item 2.1 {MD007}
|
||||
* item 2.2 {MD007}
|
||||
* item 2.2.1 {MD007}
|
||||
|
||||
<!-- markdownlint-configure-file {
|
||||
"ul-indent": {
|
||||
"start_indented": true,
|
||||
"start_indent": 1
|
||||
}
|
||||
} -->
|
||||
46
test/list-indentation-start-indented-no-indent.md
Normal file
46
test/list-indentation-start-indented-no-indent.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# List Indentation - Start Indented/No Indent
|
||||
|
||||
* item 1
|
||||
* item 2
|
||||
* item 2.1
|
||||
* item 2.2
|
||||
* item 2.2.1
|
||||
* item 2.3
|
||||
* item 3
|
||||
|
||||
## Disallowed List Indentation - Starts at Zero
|
||||
|
||||
* item 1 {MD007}
|
||||
* item 2 {MD007}
|
||||
* item 2.1 {MD007}
|
||||
* item 2.2 {MD007}
|
||||
* item 2.2.1 {MD007}
|
||||
* item 2.3 {MD007}
|
||||
* item 3 {MD007}
|
||||
|
||||
## Disallowed List Indentation - Starts at One
|
||||
|
||||
* item 1 {MD007}
|
||||
* item 2 {MD007}
|
||||
* item 2.1 {MD007}
|
||||
* item 2.2 {MD007}
|
||||
* item 2.2.1 {MD007}
|
||||
* item 2.3 {MD007}
|
||||
* item 2.3.1
|
||||
* item 3 {MD007}
|
||||
|
||||
## Disallowed List Indentation - Starts at Three
|
||||
|
||||
* item 1 {MD007}
|
||||
* item 2 {MD007}
|
||||
* item 2.1 {MD007}
|
||||
* item 2.2 {MD007}
|
||||
* item 2.2.1 {MD007}
|
||||
* item 2.3 {MD007}
|
||||
* item 3 {MD007}
|
||||
|
||||
<!-- markdownlint-configure-file {
|
||||
"ul-indent": {
|
||||
"start_indented": true
|
||||
}
|
||||
} -->
|
||||
38
test/lists-with-commented-items.md
Normal file
38
test/lists-with-commented-items.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Lists with Commented Items
|
||||
|
||||
Text
|
||||
|
||||
- item <!-- comment -->
|
||||
- item <!-- comment -->
|
||||
<!--
|
||||
- commented subitem: description
|
||||
- commented subitem: description
|
||||
-->
|
||||
- item <!-- comment -->
|
||||
- item <!-- comment -->
|
||||
|
||||
Text
|
||||
|
||||
- item <!-- comment -->
|
||||
- item <!-- comment -->
|
||||
<!-- - commented subitem: description
|
||||
- commented subitem: description -->
|
||||
- item <!-- comment -->
|
||||
- item <!-- comment -->
|
||||
|
||||
Text
|
||||
|
||||
- item <!-- comment -->
|
||||
<!-- - commented subitem: description -->
|
||||
- item <!-- comment -->
|
||||
|
||||
Text
|
||||
|
||||
- item <!-- comment -->
|
||||
- item <!-- comment -->
|
||||
<!-- - commented subitem: description -->
|
||||
<!-- - commented subitem: description -->
|
||||
- item <!-- comment -->
|
||||
- item <!-- comment -->
|
||||
|
||||
Text
|
||||
|
|
@ -32,15 +32,15 @@ This long line includes a simple [reference][label] link and is long enough to v
|
|||
|
||||
*[This long line is comprised of an emphasized link](https://example.com "This is the long link's title")*
|
||||
|
||||
_[This long line is comprised of an emphasized link](https://example.com "This is the long link's title")_
|
||||
_[This long line is comprised of an emphasized link {MD049}](https://example.com "This is the long link's title")_
|
||||
|
||||
**[This long line is comprised of a bolded link](https://example.com "This is the long link's title")**
|
||||
|
||||
__[This long line is comprised of a bolded link](https://example.com "This is the long link's title")__
|
||||
__[This long line is comprised of a bolded link {MD050}](https://example.com "This is the long link's title")__
|
||||
|
||||
_**[This long line is comprised of an emphasized and bolded link](https://example.com "This is the long link's title")**_
|
||||
_**[This long line is comprised of an emphasized and bolded link {MD049}](https://example.com "This is the long link's title")**_
|
||||
|
||||
**_[This long line is comprised of an emphasized and bolded link](https://example.com "This is the long link's title")_**
|
||||
**_[This long line is comprised of an emphasized and bolded link {MD049}](https://example.com "This is the long link's title")_**
|
||||
|
||||
*[](https://example.com "This long line is comprised of an emphasized link with empty text and a non-empty title")*
|
||||
|
||||
|
|
|
|||
484
test/markdownlint-test-config.js
Normal file
484
test/markdownlint-test-config.js
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const test = require("ava").default;
|
||||
const markdownlint = require("../lib/markdownlint");
|
||||
|
||||
test.cb("configSingle", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig("./test/config/config-child.json",
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configAbsolute", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(path.join(__dirname, "config", "config-child.json"),
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultiple", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig("./test/config/config-grandparent.json",
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultipleWithRequireResolve", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig("./test/config/config-packageparent.json",
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./node_modules/pseudo-package/config-frompackage.json"),
|
||||
...require("./config/config-packageparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configCustomFileSystem", (t) => {
|
||||
t.plan(5);
|
||||
const file = path.resolve("/dir/file.json");
|
||||
const extended = path.resolve("/dir/extended.json");
|
||||
const fileContent = {
|
||||
"extends": extended,
|
||||
"default": true,
|
||||
"MD001": false
|
||||
};
|
||||
const extendedContent = {
|
||||
"MD001": true,
|
||||
"MD002": true
|
||||
};
|
||||
const fsApi = {
|
||||
"access": (p, m, cb) => {
|
||||
t.is(p, extended);
|
||||
return (cb || m)();
|
||||
},
|
||||
"readFile": (p, o, cb) => {
|
||||
switch (p) {
|
||||
case file:
|
||||
t.is(p, file);
|
||||
return cb(null, JSON.stringify(fileContent));
|
||||
case extended:
|
||||
t.is(p, extended);
|
||||
return cb(null, JSON.stringify(extendedContent));
|
||||
default:
|
||||
return t.fail();
|
||||
}
|
||||
}
|
||||
};
|
||||
markdownlint.readConfig(
|
||||
file,
|
||||
null,
|
||||
fsApi,
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...extendedContent,
|
||||
...fileContent
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadFile", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig("./test/config/config-badfile.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad file.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
// @ts-ignore
|
||||
t.is(err.code, "ENOENT", "Error code for bad file not ENOENT.");
|
||||
t.true(!result, "Got result for bad file.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadChildFile", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig("./test/config/config-badchildfile.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child file.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
// @ts-ignore
|
||||
t.is(err.code, "ENOENT",
|
||||
"Error code for bad child file not ENOENT.");
|
||||
t.true(!result, "Got result for bad child file.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadChildPackage", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig("./test/config/config-badchildpackage.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child package.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
// @ts-ignore
|
||||
t.is(err.code, "ENOENT",
|
||||
"Error code for bad child package not ENOENT.");
|
||||
t.true(!result, "Got result for bad child package.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadJson", (t) => {
|
||||
t.plan(3);
|
||||
markdownlint.readConfig("./test/config/config-badjson.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad JSON.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.true(!result, "Got result for bad JSON.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadChildJson", (t) => {
|
||||
t.plan(3);
|
||||
markdownlint.readConfig("./test/config/config-badchildjson.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child JSON.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.true(!result, "Got result for bad child JSON.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configSingleYaml", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-child.yaml",
|
||||
// @ts-ignore
|
||||
[ require("js-yaml").load ],
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultipleYaml", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-grandparent.yaml",
|
||||
// @ts-ignore
|
||||
[ require("js-yaml").load ],
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultipleHybrid", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-grandparent-hybrid.yaml",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ],
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.like(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadHybrid", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-badcontent.txt",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ],
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child JSON.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.truthy(err.message.match(
|
||||
// eslint-disable-next-line max-len
|
||||
/^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/
|
||||
), "Error message unexpected.");
|
||||
t.true(!result, "Got result for bad child JSON.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("configSingleSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync("./test/config/config-child.json");
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configAbsoluteSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
path.join(__dirname, "config", "config-child.json"));
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configMultipleSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual =
|
||||
markdownlint.readConfigSync("./test/config/config-grandparent.json");
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configBadFileSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badFileCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badfile.json");
|
||||
},
|
||||
{
|
||||
"message": /ENOENT/
|
||||
},
|
||||
"Did not get correct exception for bad file."
|
||||
);
|
||||
});
|
||||
|
||||
test("configBadChildFileSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badChildFileCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badchildfile.json");
|
||||
},
|
||||
{
|
||||
"message": /ENOENT/
|
||||
},
|
||||
"Did not get correct exception for bad child file."
|
||||
);
|
||||
});
|
||||
|
||||
test("configBadJsonSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badJsonCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badjson.json");
|
||||
},
|
||||
{
|
||||
"message":
|
||||
// eslint-disable-next-line max-len
|
||||
/Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/
|
||||
},
|
||||
"Did not get correct exception for bad JSON."
|
||||
);
|
||||
});
|
||||
|
||||
test("configBadChildJsonSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badChildJsonCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badchildjson.json");
|
||||
},
|
||||
{
|
||||
"message":
|
||||
// eslint-disable-next-line max-len
|
||||
/Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/
|
||||
},
|
||||
"Did not get correct exception for bad child JSON."
|
||||
);
|
||||
});
|
||||
|
||||
test("configSingleYamlSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
// @ts-ignore
|
||||
"./test/config/config-child.yaml", [ require("js-yaml").load ]);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configMultipleYamlSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
// @ts-ignore
|
||||
"./test/config/config-grandparent.yaml", [ require("js-yaml").load ]);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configMultipleHybridSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
"./test/config/config-grandparent-hybrid.yaml",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ]);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.like(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configCustomFileSystemSync", (t) => {
|
||||
t.plan(4);
|
||||
const file = path.resolve("/dir/file.json");
|
||||
const extended = path.resolve("/dir/extended.json");
|
||||
const fileContent = {
|
||||
"extends": extended,
|
||||
"default": true,
|
||||
"MD001": false
|
||||
};
|
||||
const extendedContent = {
|
||||
"MD001": true,
|
||||
"MD002": true
|
||||
};
|
||||
const fsApi = {
|
||||
"accessSync": (p) => {
|
||||
t.is(p, extended);
|
||||
},
|
||||
"readFileSync": (p) => {
|
||||
switch (p) {
|
||||
case file:
|
||||
t.is(p, file);
|
||||
return JSON.stringify(fileContent);
|
||||
case extended:
|
||||
t.is(p, extended);
|
||||
return JSON.stringify(extendedContent);
|
||||
default:
|
||||
return t.fail();
|
||||
}
|
||||
}
|
||||
};
|
||||
const actual = markdownlint.readConfigSync(file, null, fsApi);
|
||||
const expected = {
|
||||
...extendedContent,
|
||||
...fileContent
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configBadHybridSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badHybridCall() {
|
||||
markdownlint.readConfigSync(
|
||||
"./test/config/config-badcontent.txt",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ]);
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line max-len
|
||||
"message": /^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/
|
||||
},
|
||||
"Did not get correct exception for bad content."
|
||||
);
|
||||
});
|
||||
|
||||
test.cb("configSinglePromise", (t) => {
|
||||
t.plan(1);
|
||||
markdownlint.promises.readConfig("./test/config/config-child.json")
|
||||
.then((actual) => {
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configCustomFileSystemPromise", (t) => {
|
||||
t.plan(4);
|
||||
const file = path.resolve("/dir/file.json");
|
||||
const extended = path.resolve("/dir/extended.json");
|
||||
const fileContent = {
|
||||
"extends": extended,
|
||||
"default": true,
|
||||
"MD001": false
|
||||
};
|
||||
const extendedContent = {
|
||||
"MD001": true,
|
||||
"MD002": true
|
||||
};
|
||||
const fsApi = {
|
||||
"access": (p, m, cb) => {
|
||||
t.is(p, extended);
|
||||
return (cb || m)();
|
||||
},
|
||||
"readFile": (p, o, cb) => {
|
||||
switch (p) {
|
||||
case file:
|
||||
t.is(p, file);
|
||||
return cb(null, JSON.stringify(fileContent));
|
||||
case extended:
|
||||
t.is(p, extended);
|
||||
return cb(null, JSON.stringify(extendedContent));
|
||||
default:
|
||||
return t.fail();
|
||||
}
|
||||
}
|
||||
};
|
||||
markdownlint.promises.readConfig(file, null, fsApi)
|
||||
.then((actual) => {
|
||||
const expected = {
|
||||
...extendedContent,
|
||||
...fileContent
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadFilePromise", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.promises.readConfig("./test/config/config-badfile.json")
|
||||
.then(
|
||||
null,
|
||||
(error) => {
|
||||
t.truthy(error, "Did not get an error for bad JSON.");
|
||||
t.true(error instanceof Error, "Error not instance of Error.");
|
||||
t.end();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs").promises;
|
||||
const test = require("ava").default;
|
||||
const packageJson = require("../package.json");
|
||||
const markdownlint = require("../lib/markdownlint");
|
||||
const customRules = require("./rules/rules.js");
|
||||
const homepage = packageJson.homepage;
|
||||
const version = packageJson.version;
|
||||
const { homepage, version } = require("../package.json");
|
||||
|
||||
test.cb("customRulesV0", (t) => {
|
||||
t.plan(4);
|
||||
|
|
@ -328,7 +327,7 @@ test.cb("customRulesConfig", (t) => {
|
|||
});
|
||||
});
|
||||
|
||||
test("customRulesNpmPackage", (t) => {
|
||||
test.cb("customRulesNpmPackage", (t) => {
|
||||
t.plan(2);
|
||||
const options = {
|
||||
"customRules": [ require("./rules/npm") ],
|
||||
|
|
@ -345,11 +344,12 @@ test("customRulesNpmPackage", (t) => {
|
|||
};
|
||||
// @ts-ignore
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesBadProperty", (t) => {
|
||||
t.plan(23);
|
||||
t.plan(27);
|
||||
[
|
||||
{
|
||||
"propertyName": "names",
|
||||
|
|
@ -364,6 +364,10 @@ test("customRulesBadProperty", (t) => {
|
|||
"propertyName": "information",
|
||||
"propertyValues": [ 10, [], "string", "https://example.com" ]
|
||||
},
|
||||
{
|
||||
"propertyName": "asynchronous",
|
||||
"propertyValues": [ null, 10, "", [] ]
|
||||
},
|
||||
{
|
||||
"propertyName": "tags",
|
||||
"propertyValues":
|
||||
|
|
@ -395,7 +399,7 @@ test("customRulesBadProperty", (t) => {
|
|||
});
|
||||
});
|
||||
|
||||
test("customRulesUsedNameName", (t) => {
|
||||
test.cb("customRulesUsedNameName", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint({
|
||||
"customRules": [
|
||||
|
|
@ -414,10 +418,11 @@ test("customRulesUsedNameName", (t) => {
|
|||
"already used as a name or tag.",
|
||||
"Incorrect message for duplicate name.");
|
||||
t.true(!result, "Got result for duplicate name.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesUsedNameTag", (t) => {
|
||||
test.cb("customRulesUsedNameTag", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint({
|
||||
"customRules": [
|
||||
|
|
@ -435,10 +440,11 @@ test("customRulesUsedNameTag", (t) => {
|
|||
"Name 'HtMl' of custom rule at index 0 is already used as a name or tag.",
|
||||
"Incorrect message for duplicate name.");
|
||||
t.true(!result, "Got result for duplicate name.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesUsedTagName", (t) => {
|
||||
test.cb("customRulesUsedTagName", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint({
|
||||
"customRules": [
|
||||
|
|
@ -463,6 +469,7 @@ test("customRulesUsedTagName", (t) => {
|
|||
"already used as a name.",
|
||||
"Incorrect message for duplicate name.");
|
||||
t.true(!result, "Got result for duplicate tag.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -517,7 +524,7 @@ test("customRulesThrowForFileSync", (t) => {
|
|||
);
|
||||
});
|
||||
|
||||
test("customRulesThrowForString", (t) => {
|
||||
test.cb("customRulesThrowForString", (t) => {
|
||||
t.plan(4);
|
||||
const exceptionMessage = "Test exception message";
|
||||
markdownlint({
|
||||
|
|
@ -540,10 +547,69 @@ test("customRulesThrowForString", (t) => {
|
|||
t.is(err.message, exceptionMessage,
|
||||
"Incorrect message for function thrown.");
|
||||
t.true(!result, "Got result for function thrown.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesOnErrorNull", (t) => {
|
||||
test("customRulesThrowForStringSync", (t) => {
|
||||
t.plan(1);
|
||||
const exceptionMessage = "Test exception message";
|
||||
t.throws(
|
||||
function customRuleThrowsCall() {
|
||||
markdownlint.sync({
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"function": function throws() {
|
||||
throw new Error(exceptionMessage);
|
||||
}
|
||||
}
|
||||
],
|
||||
"strings": {
|
||||
"string": "String"
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
"message": exceptionMessage
|
||||
},
|
||||
"Did not get correct exception for function thrown."
|
||||
);
|
||||
});
|
||||
|
||||
test.cb("customRulesOnErrorNull", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint({
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"function": function onErrorNull(params, onError) {
|
||||
onError(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
"strings": {
|
||||
"string": "String"
|
||||
}
|
||||
},
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for function thrown.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.is(
|
||||
err.message,
|
||||
"Property 'lineNumber' of onError parameter is incorrect.",
|
||||
"Did not get correct exception for null object."
|
||||
);
|
||||
t.true(!result, "Got result for function thrown.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesOnErrorNullSync", (t) => {
|
||||
t.plan(1);
|
||||
const options = {
|
||||
"customRules": [
|
||||
|
|
@ -802,7 +868,7 @@ test("customRulesOnErrorValid", (t) => {
|
|||
});
|
||||
});
|
||||
|
||||
test("customRulesOnErrorLazy", (t) => {
|
||||
test.cb("customRulesOnErrorLazy", (t) => {
|
||||
t.plan(2);
|
||||
const options = {
|
||||
"customRules": [
|
||||
|
|
@ -840,10 +906,11 @@ test("customRulesOnErrorLazy", (t) => {
|
|||
]
|
||||
};
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesOnErrorModified", (t) => {
|
||||
test.cb("customRulesOnErrorModified", (t) => {
|
||||
t.plan(2);
|
||||
const errorObject = {
|
||||
"lineNumber": 1,
|
||||
|
|
@ -900,98 +967,11 @@ test("customRulesOnErrorModified", (t) => {
|
|||
]
|
||||
};
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("customRulesThrowForFileHandled", (t) => {
|
||||
t.plan(2);
|
||||
const exceptionMessage = "Test exception message";
|
||||
markdownlint({
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"function": function throws() {
|
||||
throw new Error(exceptionMessage);
|
||||
}
|
||||
}
|
||||
],
|
||||
"files": [ "./test/custom-rules.md" ],
|
||||
"handleRuleFailures": true
|
||||
}, function callback(err, actualResult) {
|
||||
t.falsy(err);
|
||||
const expectedResult = {
|
||||
"./test/custom-rules.md": [
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "name" ],
|
||||
"ruleDescription": "description",
|
||||
"ruleInformation": null,
|
||||
"errorDetail":
|
||||
`This rule threw an exception: ${exceptionMessage}`,
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
}
|
||||
]
|
||||
};
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesThrowForStringHandled", (t) => {
|
||||
t.plan(2);
|
||||
const exceptionMessage = "Test exception message";
|
||||
const informationUrl = "https://example.com/rule";
|
||||
markdownlint({
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"information": new URL(informationUrl),
|
||||
"tags": [ "tag" ],
|
||||
"function": function throws() {
|
||||
throw new Error(exceptionMessage);
|
||||
}
|
||||
}
|
||||
],
|
||||
"strings": {
|
||||
"string": "String\n"
|
||||
},
|
||||
"handleRuleFailures": true
|
||||
}, function callback(err, actualResult) {
|
||||
t.falsy(err);
|
||||
const expectedResult = {
|
||||
"string": [
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "MD041", "first-line-heading", "first-line-h1" ],
|
||||
"ruleDescription":
|
||||
"First line in a file should be a top-level heading",
|
||||
"ruleInformation":
|
||||
`${homepage}/blob/v${version}/doc/Rules.md#md041`,
|
||||
"errorDetail": null,
|
||||
"errorContext": "String",
|
||||
"errorRange": null
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "name" ],
|
||||
"ruleDescription": "description",
|
||||
"ruleInformation": informationUrl,
|
||||
"errorDetail":
|
||||
`This rule threw an exception: ${exceptionMessage}`,
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
}
|
||||
]
|
||||
};
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesOnErrorInvalidHandled", (t) => {
|
||||
test.cb("customRulesOnErrorInvalidHandled", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint({
|
||||
"customRules": [
|
||||
|
|
@ -1001,8 +981,7 @@ test("customRulesOnErrorInvalidHandled", (t) => {
|
|||
"tags": [ "tag" ],
|
||||
"function": function onErrorInvalid(params, onError) {
|
||||
onError({
|
||||
"lineNumber": 13,
|
||||
"details": "N/A"
|
||||
"lineNumber": 13
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1028,9 +1007,48 @@ test("customRulesOnErrorInvalidHandled", (t) => {
|
|||
]
|
||||
};
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesOnErrorInvalidHandledSync", (t) => {
|
||||
t.plan(1);
|
||||
const actualResult = markdownlint.sync({
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"function": function onErrorInvalid(params, onError) {
|
||||
onError({
|
||||
"lineNumber": 13,
|
||||
"detail": "N/A"
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
"strings": {
|
||||
"string": "# Heading\n"
|
||||
},
|
||||
"handleRuleFailures": true
|
||||
});
|
||||
const expectedResult = {
|
||||
"string": [
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "name" ],
|
||||
"ruleDescription": "description",
|
||||
"ruleInformation": null,
|
||||
"errorDetail": "This rule threw an exception: " +
|
||||
"Property 'lineNumber' of onError parameter is incorrect.",
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
}
|
||||
]
|
||||
};
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
});
|
||||
|
||||
test.cb("customRulesFileName", (t) => {
|
||||
t.plan(2);
|
||||
const options = {
|
||||
|
|
@ -1052,7 +1070,7 @@ test.cb("customRulesFileName", (t) => {
|
|||
});
|
||||
});
|
||||
|
||||
test("customRulesStringName", (t) => {
|
||||
test.cb("customRulesStringName", (t) => {
|
||||
t.plan(2);
|
||||
const options = {
|
||||
"customRules": [
|
||||
|
|
@ -1071,6 +1089,7 @@ test("customRulesStringName", (t) => {
|
|||
};
|
||||
markdownlint(options, function callback(err) {
|
||||
t.falsy(err);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1123,3 +1142,385 @@ test.cb("customRulesLintJavaScript", (t) => {
|
|||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("customRulesAsyncThrowsInSyncContext", (t) => {
|
||||
t.plan(1);
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name1", "name2" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"asynchronous": true,
|
||||
"function": () => {}
|
||||
}
|
||||
],
|
||||
"strings": {
|
||||
"string": "Unused"
|
||||
}
|
||||
};
|
||||
t.throws(
|
||||
() => markdownlint.sync(options),
|
||||
{
|
||||
"message": "Custom rule name1/name2 at index 0 is asynchronous and " +
|
||||
"can not be used in a synchronous context."
|
||||
},
|
||||
"Did not get correct exception for async rule in sync context."
|
||||
);
|
||||
});
|
||||
|
||||
test("customRulesAsyncReadFiles", (t) => {
|
||||
t.plan(3);
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "name1" ],
|
||||
"description": "description1",
|
||||
"information": new URL("https://example.com/asyncRule1"),
|
||||
"tags": [ "tag" ],
|
||||
"asynchronous": true,
|
||||
"function":
|
||||
(params, onError) => fs.readFile(__filename, "utf8").then(
|
||||
(content) => {
|
||||
t.true(content.length > 0);
|
||||
onError({
|
||||
"lineNumber": 1,
|
||||
"detail": "detail1",
|
||||
"context": "context1",
|
||||
"range": [ 2, 3 ]
|
||||
});
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
"names": [ "name2" ],
|
||||
"description": "description2",
|
||||
"tags": [ "tag" ],
|
||||
"asynchronous": true,
|
||||
"function":
|
||||
async(params, onError) => {
|
||||
const content = await fs.readFile(__filename, "utf8");
|
||||
t.true(content.length > 0);
|
||||
onError({
|
||||
"lineNumber": 1,
|
||||
"detail": "detail2",
|
||||
"context": "context2"
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
"strings": {
|
||||
"string": "# Heading"
|
||||
}
|
||||
};
|
||||
const expected = {
|
||||
"string": [
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "MD047", "single-trailing-newline" ],
|
||||
"ruleDescription": "Files should end with a single newline character",
|
||||
"ruleInformation": `${homepage}/blob/v${version}/doc/Rules.md#md047`,
|
||||
"errorDetail": null,
|
||||
"errorContext": null,
|
||||
"errorRange": [ 9, 1 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "name1" ],
|
||||
"ruleDescription": "description1",
|
||||
"ruleInformation": "https://example.com/asyncRule1",
|
||||
"errorDetail": "detail1",
|
||||
"errorContext": "context1",
|
||||
"errorRange": [ 2, 3 ]
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "name2" ],
|
||||
"ruleDescription": "description2",
|
||||
"ruleInformation": null,
|
||||
"errorDetail": "detail2",
|
||||
"errorContext": "context2",
|
||||
"errorRange": null
|
||||
}
|
||||
]
|
||||
};
|
||||
return markdownlint.promises.markdownlint(options)
|
||||
.then((actual) => t.deepEqual(actual, expected, "Unexpected issues."));
|
||||
});
|
||||
|
||||
test("customRulesAsyncIgnoresSyncReturn", (t) => {
|
||||
t.plan(1);
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "sync" ],
|
||||
"description": "description",
|
||||
"information": new URL("https://example.com/asyncRule"),
|
||||
"tags": [ "tag" ],
|
||||
"asynchronous": false,
|
||||
"function": () => new Promise(() => {
|
||||
// Never resolves
|
||||
})
|
||||
},
|
||||
{
|
||||
"names": [ "async" ],
|
||||
"description": "description",
|
||||
"information": new URL("https://example.com/asyncRule"),
|
||||
"tags": [ "tag" ],
|
||||
"asynchronous": true,
|
||||
"function": (params, onError) => new Promise((resolve) => {
|
||||
onError({ "lineNumber": 1 });
|
||||
resolve();
|
||||
})
|
||||
}
|
||||
],
|
||||
"strings": {
|
||||
"string": "# Heading"
|
||||
}
|
||||
};
|
||||
const expected = {
|
||||
"string": [
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "async" ],
|
||||
"ruleDescription": "description",
|
||||
"ruleInformation": "https://example.com/asyncRule",
|
||||
"errorDetail": null,
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "MD047", "single-trailing-newline" ],
|
||||
"ruleDescription": "Files should end with a single newline character",
|
||||
"ruleInformation": `${homepage}/blob/v${version}/doc/Rules.md#md047`,
|
||||
"errorDetail": null,
|
||||
"errorContext": null,
|
||||
"errorRange": [ 9, 1 ]
|
||||
}
|
||||
]
|
||||
};
|
||||
return markdownlint.promises.markdownlint(options)
|
||||
.then((actual) => t.deepEqual(actual, expected, "Unexpected issues."));
|
||||
});
|
||||
|
||||
const errorMessage = "Custom error message.";
|
||||
const stringScenarios = [
|
||||
[
|
||||
"Files",
|
||||
[ "./test/custom-rules.md" ],
|
||||
null
|
||||
],
|
||||
[
|
||||
"Strings",
|
||||
null,
|
||||
{ "./test/custom-rules.md": "# Heading\n" }
|
||||
]
|
||||
];
|
||||
|
||||
[
|
||||
[
|
||||
"customRulesThrowString",
|
||||
() => {
|
||||
throw errorMessage;
|
||||
}
|
||||
],
|
||||
[
|
||||
"customRulesThrowError",
|
||||
() => {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
]
|
||||
].forEach((flavor) => {
|
||||
const [ name, func ] = flavor;
|
||||
const customRule = [
|
||||
{
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"function": func
|
||||
}
|
||||
];
|
||||
const expectedResult = {
|
||||
"./test/custom-rules.md": [
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "name" ],
|
||||
"ruleDescription": "description",
|
||||
"ruleInformation": null,
|
||||
"errorDetail": `This rule threw an exception: ${errorMessage}`,
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
}
|
||||
]
|
||||
};
|
||||
stringScenarios.forEach((inputs) => {
|
||||
const [ subname, files, strings ] = inputs;
|
||||
|
||||
test.cb(`${name}${subname}UnhandledAsync`, (t) => {
|
||||
t.plan(4);
|
||||
markdownlint({
|
||||
// @ts-ignore
|
||||
"customRules": customRule,
|
||||
// @ts-ignore
|
||||
files,
|
||||
// @ts-ignore
|
||||
strings
|
||||
}, function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for exception.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.is(err.message, errorMessage, "Incorrect message for exception.");
|
||||
t.true(!result, "Got result for exception.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb(`${name}${subname}HandledAsync`, (t) => {
|
||||
t.plan(2);
|
||||
markdownlint({
|
||||
// @ts-ignore
|
||||
"customRules": customRule,
|
||||
// @ts-ignore
|
||||
files,
|
||||
// @ts-ignore
|
||||
strings,
|
||||
"handleRuleFailures": true
|
||||
}, function callback(err, actualResult) {
|
||||
t.falsy(err);
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test(`${name}${subname}UnhandledSync`, (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
() => markdownlint.sync({
|
||||
// @ts-ignore
|
||||
"customRules": customRule,
|
||||
// @ts-ignore
|
||||
files,
|
||||
// @ts-ignore
|
||||
strings
|
||||
}),
|
||||
{
|
||||
"message": errorMessage
|
||||
},
|
||||
"Unexpected exception."
|
||||
);
|
||||
});
|
||||
|
||||
test(`${name}${subname}HandledSync`, (t) => {
|
||||
t.plan(1);
|
||||
const actualResult = markdownlint.sync({
|
||||
// @ts-ignore
|
||||
"customRules": customRule,
|
||||
// @ts-ignore
|
||||
files,
|
||||
// @ts-ignore
|
||||
strings,
|
||||
"handleRuleFailures": true
|
||||
});
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
[
|
||||
"customRulesAsyncExceptionString",
|
||||
() => {
|
||||
throw errorMessage;
|
||||
}
|
||||
],
|
||||
[
|
||||
"customRulesAsyncExceptionError",
|
||||
() => {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
],
|
||||
[
|
||||
"customRulesAsyncDeferredString",
|
||||
() => fs.readFile(__filename, "utf8").then(
|
||||
() => {
|
||||
throw errorMessage;
|
||||
}
|
||||
)
|
||||
],
|
||||
[
|
||||
"customRulesAsyncDeferredError",
|
||||
() => fs.readFile(__filename, "utf8").then(
|
||||
() => {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
)
|
||||
],
|
||||
[
|
||||
"customRulesAsyncRejectString",
|
||||
() => Promise.reject(errorMessage)
|
||||
],
|
||||
[
|
||||
"customRulesAsyncRejectError",
|
||||
() => Promise.reject(new Error(errorMessage))
|
||||
]
|
||||
].forEach((flavor) => {
|
||||
const [ name, func ] = flavor;
|
||||
const customRule = {
|
||||
"names": [ "name" ],
|
||||
"description": "description",
|
||||
"tags": [ "tag" ],
|
||||
"asynchronous": true,
|
||||
"function": func
|
||||
};
|
||||
stringScenarios.forEach((inputs) => {
|
||||
const [ subname, files, strings ] = inputs;
|
||||
|
||||
test.cb(`${name}${subname}Unhandled`, (t) => {
|
||||
t.plan(4);
|
||||
markdownlint({
|
||||
// @ts-ignore
|
||||
"customRules": [ customRule ],
|
||||
// @ts-ignore
|
||||
files,
|
||||
// @ts-ignore
|
||||
strings
|
||||
}, function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for rejection.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.is(err.message, errorMessage, "Incorrect message for rejection.");
|
||||
t.true(!result, "Got result for rejection.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb(`${name}${subname}Handled`, (t) => {
|
||||
t.plan(2);
|
||||
markdownlint({
|
||||
// @ts-ignore
|
||||
"customRules": [ customRule ],
|
||||
// @ts-ignore
|
||||
files,
|
||||
// @ts-ignore
|
||||
strings,
|
||||
"handleRuleFailures": true
|
||||
}, function callback(err, actualResult) {
|
||||
t.falsy(err);
|
||||
const expectedResult = {
|
||||
"./test/custom-rules.md": [
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"ruleNames": [ "name" ],
|
||||
"ruleDescription": "description",
|
||||
"ruleInformation": null,
|
||||
"errorDetail": `This rule threw an exception: ${errorMessage}`,
|
||||
"errorContext": null,
|
||||
"errorRange": null
|
||||
}
|
||||
]
|
||||
};
|
||||
t.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
16
test/markdownlint-test-extra-parse.js
Normal file
16
test/markdownlint-test-extra-parse.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const test = require("ava").default;
|
||||
const markdownlint = require("../lib/markdownlint");
|
||||
|
||||
// Parses all Markdown files in all package dependencies
|
||||
test("parseAllFiles", async(t) => {
|
||||
t.plan(1);
|
||||
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
||||
const { globby } = await import("globby");
|
||||
const files = await globby("**/*.{md,markdown}");
|
||||
await markdownlint.promises.markdownlint({ files });
|
||||
t.pass();
|
||||
});
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const globby = require("globby");
|
||||
const test = require("ava").default;
|
||||
const markdownlint = require("../lib/markdownlint");
|
||||
|
||||
|
|
@ -27,15 +26,3 @@ files.filter((file) => /\.md$/.test(file)).forEach((file) => {
|
|||
t.pass();
|
||||
});
|
||||
});
|
||||
|
||||
// Parses all Markdown files in all package dependencies
|
||||
test.cb("parseAllFiles", (t) => {
|
||||
t.plan(1);
|
||||
const options = {
|
||||
"files": globby.sync("**/*.{md,markdown}")
|
||||
};
|
||||
markdownlint(options, (err) => {
|
||||
t.falsy(err);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
|
@ -226,7 +226,7 @@ bar`
|
|||
});
|
||||
|
||||
test("isBlankLine", (t) => {
|
||||
t.plan(25);
|
||||
t.plan(29);
|
||||
const blankLines = [
|
||||
null,
|
||||
"",
|
||||
|
|
@ -244,7 +244,11 @@ test("isBlankLine", (t) => {
|
|||
"> ",
|
||||
"> > > \t",
|
||||
"> <!--text-->",
|
||||
">><!--text-->"
|
||||
">><!--text-->",
|
||||
"<!--",
|
||||
" <!-- text",
|
||||
"text --> ",
|
||||
"-->"
|
||||
];
|
||||
blankLines.forEach((line) => t.true(helpers.isBlankLine(line), line || ""));
|
||||
const nonBlankLines = [
|
||||
|
|
@ -253,9 +257,9 @@ test("isBlankLine", (t) => {
|
|||
".",
|
||||
"> .",
|
||||
"<!--text--> text",
|
||||
"<!--->",
|
||||
"<!--",
|
||||
"-->"
|
||||
"text <!--text-->",
|
||||
"text <!--",
|
||||
"--> text"
|
||||
];
|
||||
nonBlankLines.forEach((line) => t.true(!helpers.isBlankLine(line), line));
|
||||
});
|
||||
|
|
@ -383,7 +387,7 @@ test("forEachInlineCodeSpan", (t) => {
|
|||
});
|
||||
|
||||
test("getPreferredLineEnding", (t) => {
|
||||
t.plan(17);
|
||||
t.plan(19);
|
||||
const testCases = [
|
||||
[ "", os.EOL ],
|
||||
[ "\r", "\r" ],
|
||||
|
|
@ -408,6 +412,16 @@ test("getPreferredLineEnding", (t) => {
|
|||
const actual = helpers.getPreferredLineEnding(input);
|
||||
t.is(actual, expected, "Incorrect line ending returned.");
|
||||
});
|
||||
t.is(
|
||||
helpers.getPreferredLineEnding("", "linux"),
|
||||
"\n",
|
||||
"Incorrect line ending for linux"
|
||||
);
|
||||
t.is(
|
||||
helpers.getPreferredLineEnding("", "win32"),
|
||||
"\r\n",
|
||||
"Incorrect line ending for win32"
|
||||
);
|
||||
});
|
||||
|
||||
test("applyFix", (t) => {
|
||||
|
|
@ -934,3 +948,37 @@ test("applyFixes", (t) => {
|
|||
t.is(actual, expected, "Incorrect fix applied.");
|
||||
});
|
||||
});
|
||||
|
||||
test("deepFreeze", (t) => {
|
||||
t.plan(6);
|
||||
const obj = {
|
||||
"prop": true,
|
||||
"func": () => true,
|
||||
"sub": {
|
||||
"prop": [ 1 ],
|
||||
"sub": {
|
||||
"prop": "one"
|
||||
}
|
||||
}
|
||||
};
|
||||
t.is(helpers.deepFreeze(obj), obj, "Did not return object.");
|
||||
[
|
||||
() => {
|
||||
obj.prop = false;
|
||||
},
|
||||
() => {
|
||||
obj.func = () => false;
|
||||
},
|
||||
() => {
|
||||
obj.sub.prop = [];
|
||||
},
|
||||
() => {
|
||||
obj.sub.prop[0] = 0;
|
||||
},
|
||||
() => {
|
||||
obj.sub.sub.prop = "zero";
|
||||
}
|
||||
].forEach((scenario) => {
|
||||
t.throws(scenario, null, "Assigned to frozen object.");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,44 +6,29 @@ const { existsSync } = require("fs");
|
|||
// eslint-disable-next-line unicorn/import-style
|
||||
const { join } = require("path");
|
||||
const { promisify } = require("util");
|
||||
const globby = require("globby");
|
||||
const jsYaml = require("js-yaml");
|
||||
const stripJsonComments = require("strip-json-comments");
|
||||
const test = require("ava").default;
|
||||
const markdownlint = require("../lib/markdownlint");
|
||||
const markdownlintPromise = promisify(markdownlint);
|
||||
const readConfigPromise = promisify(markdownlint.readConfig);
|
||||
|
||||
/**
|
||||
* Parses JSONC text.
|
||||
*
|
||||
* @param {string} json JSON to parse.
|
||||
* @returns {Object} Object representation.
|
||||
*/
|
||||
function jsoncParse(json) {
|
||||
return JSON.parse(stripJsonComments(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses YAML text.
|
||||
*
|
||||
* @param {string} yaml YAML to parse.
|
||||
* @returns {Object} Object representation.
|
||||
*/
|
||||
function yamlParse(yaml) {
|
||||
return jsYaml.load(yaml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lints a test repository.
|
||||
*
|
||||
* @param {Object} t Test instance.
|
||||
* @param {string[]} globPatterns Array of files to in/exclude.
|
||||
* @param {string} configPath Path to config file.
|
||||
* @param {RegExp[]} [ignoreRes] Array of RegExp violations to ignore.
|
||||
* @returns {Promise} Test result.
|
||||
*/
|
||||
function lintTestRepo(t, globPatterns, configPath) {
|
||||
async function lintTestRepo(t, globPatterns, configPath, ignoreRes) {
|
||||
t.plan(1);
|
||||
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
||||
const { globby } = await import("globby");
|
||||
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
||||
const { "default": stripJsonComments } = await import("strip-json-comments");
|
||||
const jsoncParse = (json) => JSON.parse(stripJsonComments(json));
|
||||
const yamlParse = (yaml) => jsYaml.load(yaml);
|
||||
return Promise.all([
|
||||
globby(globPatterns),
|
||||
// @ts-ignore
|
||||
|
|
@ -57,7 +42,14 @@ function lintTestRepo(t, globPatterns, configPath) {
|
|||
// eslint-disable-next-line no-console
|
||||
console.log(`${t.title}: Linting ${files.length} files...`);
|
||||
return markdownlintPromise(options).then((results) => {
|
||||
const resultsString = results.toString();
|
||||
let resultsString = results.toString();
|
||||
for (const ignoreRe of (ignoreRes || [])) {
|
||||
const lengthBefore = resultsString.length;
|
||||
resultsString = resultsString.replace(ignoreRe, "");
|
||||
if (resultsString.length === lengthBefore) {
|
||||
t.fail(`Unnecessary ignore: ${ignoreRe}`);
|
||||
}
|
||||
}
|
||||
if (resultsString.length > 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(resultsString);
|
||||
|
|
@ -67,13 +59,26 @@ function lintTestRepo(t, globPatterns, configPath) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a list of globs.
|
||||
*
|
||||
* @param {string} rootDir Root directory for globs.
|
||||
* @param {...string} globs Globs to exclude.
|
||||
* @returns {string[]} Array of excluded globs.
|
||||
*/
|
||||
function excludeGlobs(rootDir, ...globs) {
|
||||
return globs.map((glob) => "!" + join(rootDir, glob));
|
||||
}
|
||||
|
||||
// Run markdownlint the same way the corresponding repositories do
|
||||
|
||||
test("https://github.com/eslint/eslint", (t) => {
|
||||
const rootDir = "./test-repos/eslint-eslint";
|
||||
const globPatterns = [ join(rootDir, "docs/**/*.md") ];
|
||||
const configPath = join(rootDir, ".markdownlint.yml");
|
||||
return lintTestRepo(t, globPatterns, configPath);
|
||||
const ignoreRes =
|
||||
[ /^[^:]+\/array-callback-return\.md: \d+: MD050\/.*$\r?\n?/gm ];
|
||||
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
|
||||
});
|
||||
|
||||
test("https://github.com/mkdocs/mkdocs", (t) => {
|
||||
|
|
@ -81,8 +86,13 @@ test("https://github.com/mkdocs/mkdocs", (t) => {
|
|||
const globPatterns = [
|
||||
join(rootDir, "README.md"),
|
||||
join(rootDir, "CONTRIBUTING.md"),
|
||||
join(rootDir, "docs/**/*.md"),
|
||||
"!" + join(rootDir, "docs/CNAME")
|
||||
join(rootDir, "docs"),
|
||||
...excludeGlobs(
|
||||
rootDir,
|
||||
"docs/CNAME",
|
||||
"docs/**/*.css",
|
||||
"docs/**/*.png"
|
||||
)
|
||||
];
|
||||
const configPath = join(rootDir, ".markdownlintrc");
|
||||
return lintTestRepo(t, globPatterns, configPath);
|
||||
|
|
@ -106,14 +116,16 @@ test("https://github.com/pi-hole/docs", (t) => {
|
|||
const rootDir = "./test-repos/pi-hole-docs";
|
||||
const globPatterns = [ join(rootDir, "**/*.md") ];
|
||||
const configPath = join(rootDir, ".markdownlint.json");
|
||||
return lintTestRepo(t, globPatterns, configPath);
|
||||
const ignoreRes =
|
||||
[ /^[^:]+\/(unbound|index|prerequisites)\.md: \d+: MD049\/.*$\r?\n?/gm ];
|
||||
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
|
||||
});
|
||||
|
||||
test("https://github.com/webhintio/hint", (t) => {
|
||||
const rootDir = "./test-repos/webhintio-hint";
|
||||
const globPatterns = [
|
||||
join(rootDir, "**/*.md"),
|
||||
"!" + join(rootDir, "**/CHANGELOG.md")
|
||||
...excludeGlobs(rootDir, "**/CHANGELOG.md")
|
||||
];
|
||||
const configPath = join(rootDir, ".markdownlintrc");
|
||||
return lintTestRepo(t, globPatterns, configPath);
|
||||
|
|
@ -132,12 +144,10 @@ const dotnetDocsDir = "./test-repos/dotnet-docs";
|
|||
if (existsSync(dotnetDocsDir)) {
|
||||
test("https://github.com/dotnet/docs", (t) => {
|
||||
const rootDir = dotnetDocsDir;
|
||||
const globPatterns = [
|
||||
join(rootDir, "**/*.md"),
|
||||
"!" + join(rootDir, "samples/**/*.md")
|
||||
];
|
||||
const globPatterns = [ join(rootDir, "**/*.md") ];
|
||||
const configPath = join(rootDir, ".markdownlint.json");
|
||||
return lintTestRepo(t, globPatterns, configPath);
|
||||
const ignoreRes = [ /^[^:]+: \d+: (MD049|MD050)\/.*$\r?\n?/gm ];
|
||||
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -147,6 +157,7 @@ if (existsSync(v8v8DevDir)) {
|
|||
const rootDir = v8v8DevDir;
|
||||
const globPatterns = [ join(rootDir, "src/**/*.md") ];
|
||||
const configPath = join(rootDir, ".markdownlint.json");
|
||||
return lintTestRepo(t, globPatterns, configPath);
|
||||
const ignoreRes = [ /^[^:]+: \d+: MD049\/.*$\r?\n?/gm ];
|
||||
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ const pluginInline = require("markdown-it-for-inline");
|
|||
const pluginSub = require("markdown-it-sub");
|
||||
const pluginSup = require("markdown-it-sup");
|
||||
const pluginTexMath = require("markdown-it-texmath");
|
||||
const stripJsonComments = require("strip-json-comments");
|
||||
const test = require("ava").default;
|
||||
const tv4 = require("tv4");
|
||||
const { homepage, version } = require("../package.json");
|
||||
const markdownlint = require("../lib/markdownlint");
|
||||
const constants = require("../lib/constants");
|
||||
const rules = require("../lib/rules");
|
||||
const customRules = require("./rules/rules.js");
|
||||
const configSchema = require("../schema/markdownlint-config-schema.json");
|
||||
|
|
@ -24,7 +24,7 @@ const pluginTexMathOptions = {
|
|||
"renderToString": () => ""
|
||||
}
|
||||
};
|
||||
const deprecatedRuleNames = new Set([ "MD002", "MD006" ]);
|
||||
const deprecatedRuleNames = new Set(constants.deprecatedRuleNames);
|
||||
const configSchemaStrict = {
|
||||
...configSchema,
|
||||
"additionalProperties": false
|
||||
|
|
@ -83,11 +83,12 @@ test.cb("projectFilesNoInlineConfig", (t) => {
|
|||
"doc/Prettier.md",
|
||||
"helpers/README.md"
|
||||
],
|
||||
"noInlineConfig": true,
|
||||
"config": {
|
||||
"line-length": { "line_length": 150 },
|
||||
"no-duplicate-heading": false
|
||||
}
|
||||
},
|
||||
"customRules": [ require("markdownlint-rule-github-internal-links") ],
|
||||
"noInlineConfig": true
|
||||
};
|
||||
markdownlint(options, function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
|
|
@ -492,8 +493,10 @@ test.cb("styleAll", (t) => {
|
|||
"MD042": [ 81 ],
|
||||
"MD045": [ 85 ],
|
||||
"MD046": [ 49, 73, 77 ],
|
||||
"MD047": [ 88 ],
|
||||
"MD048": [ 77 ]
|
||||
"MD047": [ 96 ],
|
||||
"MD048": [ 77 ],
|
||||
"MD049": [ 90 ],
|
||||
"MD050": [ 94 ]
|
||||
}
|
||||
};
|
||||
// @ts-ignore
|
||||
|
|
@ -535,8 +538,10 @@ test.cb("styleRelaxed", (t) => {
|
|||
"MD042": [ 81 ],
|
||||
"MD045": [ 85 ],
|
||||
"MD046": [ 49, 73, 77 ],
|
||||
"MD047": [ 88 ],
|
||||
"MD048": [ 77 ]
|
||||
"MD047": [ 96 ],
|
||||
"MD048": [ 77 ],
|
||||
"MD049": [ 90 ],
|
||||
"MD050": [ 94 ]
|
||||
}
|
||||
};
|
||||
// @ts-ignore
|
||||
|
|
@ -836,8 +841,9 @@ test.cb("customFileSystemAsync", (t) => {
|
|||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("readme", (t) => {
|
||||
t.plan(115);
|
||||
t.plan(119);
|
||||
const tagToRules = {};
|
||||
rules.forEach(function forRule(rule) {
|
||||
rule.tags.forEach(function forTag(tag) {
|
||||
|
|
@ -913,7 +919,7 @@ test.cb("readme", (t) => {
|
|||
});
|
||||
|
||||
test.cb("rules", (t) => {
|
||||
t.plan(336);
|
||||
t.plan(352);
|
||||
fs.readFile("doc/Rules.md", "utf8",
|
||||
(err, contents) => {
|
||||
t.falsy(err);
|
||||
|
|
@ -924,7 +930,6 @@ test.cb("rules", (t) => {
|
|||
let ruleHasAliases = true;
|
||||
let ruleUsesParams = null;
|
||||
const tagAliasParameterRe = /, |: | /;
|
||||
// eslint-disable-next-line func-style
|
||||
const testTagsAliasesParams = (r) => {
|
||||
// eslint-disable-next-line unicorn/prefer-default-parameters
|
||||
r = r || "[NO RULE]";
|
||||
|
|
@ -1064,9 +1069,10 @@ test("validateConfigSchemaAppliesToUnknownProperties", (t) => {
|
|||
}
|
||||
});
|
||||
|
||||
test("validateConfigExampleJson", (t) => {
|
||||
test("validateConfigExampleJson", async(t) => {
|
||||
t.plan(2);
|
||||
|
||||
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
||||
const { "default": stripJsonComments } = await import("strip-json-comments");
|
||||
// Validate JSONC
|
||||
const fileJson = ".markdownlint.jsonc";
|
||||
const dataJson = fs.readFileSync(
|
||||
|
|
@ -1078,7 +1084,6 @@ test("validateConfigExampleJson", (t) => {
|
|||
// @ts-ignore
|
||||
tv4.validate(jsonObject, configSchemaStrict),
|
||||
fileJson + "\n" + JSON.stringify(tv4.error, null, 2));
|
||||
|
||||
// Validate YAML
|
||||
const fileYaml = ".markdownlint.yaml";
|
||||
const dataYaml = fs.readFileSync(
|
||||
|
|
@ -1090,483 +1095,8 @@ test("validateConfigExampleJson", (t) => {
|
|||
"YAML example does not match JSON example.");
|
||||
});
|
||||
|
||||
test.cb("configSingle", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig("./test/config/config-child.json",
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configAbsolute", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(path.join(__dirname, "config", "config-child.json"),
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultiple", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig("./test/config/config-grandparent.json",
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultipleWithRequireResolve", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig("./test/config/config-packageparent.json",
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./node_modules/pseudo-package/config-frompackage.json"),
|
||||
...require("./config/config-packageparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configCustomFileSystem", (t) => {
|
||||
t.plan(5);
|
||||
const file = path.resolve("/dir/file.json");
|
||||
const extended = path.resolve("/dir/extended.json");
|
||||
const fileContent = {
|
||||
"extends": extended,
|
||||
"default": true,
|
||||
"MD001": false
|
||||
};
|
||||
const extendedContent = {
|
||||
"MD001": true,
|
||||
"MD002": true
|
||||
};
|
||||
const fsApi = {
|
||||
"access": (p, m, cb) => {
|
||||
t.is(p, extended);
|
||||
return (cb || m)();
|
||||
},
|
||||
"readFile": (p, o, cb) => {
|
||||
switch (p) {
|
||||
case file:
|
||||
t.is(p, file);
|
||||
return cb(null, JSON.stringify(fileContent));
|
||||
case extended:
|
||||
t.is(p, extended);
|
||||
return cb(null, JSON.stringify(extendedContent));
|
||||
default:
|
||||
return t.fail();
|
||||
}
|
||||
}
|
||||
};
|
||||
markdownlint.readConfig(
|
||||
file,
|
||||
null,
|
||||
fsApi,
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...extendedContent,
|
||||
...fileContent
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadFile", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig("./test/config/config-badfile.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad file.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
// @ts-ignore
|
||||
t.is(err.code, "ENOENT", "Error code for bad file not ENOENT.");
|
||||
t.true(!result, "Got result for bad file.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadChildFile", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig("./test/config/config-badchildfile.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child file.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
// @ts-ignore
|
||||
t.is(err.code, "ENOENT",
|
||||
"Error code for bad child file not ENOENT.");
|
||||
t.true(!result, "Got result for bad child file.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadChildPackage", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig("./test/config/config-badchildpackage.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child package.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
// @ts-ignore
|
||||
t.is(err.code, "ENOENT",
|
||||
"Error code for bad child package not ENOENT.");
|
||||
t.true(!result, "Got result for bad child package.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadJson", (t) => {
|
||||
t.plan(3);
|
||||
markdownlint.readConfig("./test/config/config-badjson.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad JSON.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.true(!result, "Got result for bad JSON.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadChildJson", (t) => {
|
||||
t.plan(3);
|
||||
markdownlint.readConfig("./test/config/config-badchildjson.json",
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child JSON.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.true(!result, "Got result for bad child JSON.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configSingleYaml", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-child.yaml",
|
||||
// @ts-ignore
|
||||
[ require("js-yaml").load ],
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultipleYaml", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-grandparent.yaml",
|
||||
// @ts-ignore
|
||||
[ require("js-yaml").load ],
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configMultipleHybrid", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-grandparent-hybrid.yaml",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ],
|
||||
function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.like(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadHybrid", (t) => {
|
||||
t.plan(4);
|
||||
markdownlint.readConfig(
|
||||
"./test/config/config-badcontent.txt",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ],
|
||||
function callback(err, result) {
|
||||
t.truthy(err, "Did not get an error for bad child JSON.");
|
||||
t.true(err instanceof Error, "Error not instance of Error.");
|
||||
t.truthy(err.message.match(
|
||||
// eslint-disable-next-line max-len
|
||||
/^Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+;/
|
||||
), "Error message unexpected.");
|
||||
t.true(!result, "Got result for bad child JSON.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("configSingleSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync("./test/config/config-child.json");
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configAbsoluteSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
path.join(__dirname, "config", "config-child.json"));
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configMultipleSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual =
|
||||
markdownlint.readConfigSync("./test/config/config-grandparent.json");
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configBadFileSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badFileCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badfile.json");
|
||||
},
|
||||
{
|
||||
"message": /ENOENT/
|
||||
},
|
||||
"Did not get correct exception for bad file."
|
||||
);
|
||||
});
|
||||
|
||||
test("configBadChildFileSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badChildFileCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badchildfile.json");
|
||||
},
|
||||
{
|
||||
"message": /ENOENT/
|
||||
},
|
||||
"Did not get correct exception for bad child file."
|
||||
);
|
||||
});
|
||||
|
||||
test("configBadJsonSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badJsonCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badjson.json");
|
||||
},
|
||||
{
|
||||
"message":
|
||||
/Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+/
|
||||
},
|
||||
"Did not get correct exception for bad JSON."
|
||||
);
|
||||
});
|
||||
|
||||
test("configBadChildJsonSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badChildJsonCall() {
|
||||
markdownlint.readConfigSync("./test/config/config-badchildjson.json");
|
||||
},
|
||||
{
|
||||
"message":
|
||||
/Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+/
|
||||
},
|
||||
"Did not get correct exception for bad child JSON."
|
||||
);
|
||||
});
|
||||
|
||||
test("configSingleYamlSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
// @ts-ignore
|
||||
"./test/config/config-child.yaml", [ require("js-yaml").load ]);
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configMultipleYamlSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
// @ts-ignore
|
||||
"./test/config/config-grandparent.yaml", [ require("js-yaml").load ]);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configMultipleHybridSync", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.readConfigSync(
|
||||
"./test/config/config-grandparent-hybrid.yaml",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ]);
|
||||
const expected = {
|
||||
...require("./config/config-child.json"),
|
||||
...require("./config/config-parent.json"),
|
||||
...require("./config/config-grandparent.json")
|
||||
};
|
||||
delete expected.extends;
|
||||
t.like(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configCustomFileSystemSync", (t) => {
|
||||
t.plan(4);
|
||||
const file = path.resolve("/dir/file.json");
|
||||
const extended = path.resolve("/dir/extended.json");
|
||||
const fileContent = {
|
||||
"extends": extended,
|
||||
"default": true,
|
||||
"MD001": false
|
||||
};
|
||||
const extendedContent = {
|
||||
"MD001": true,
|
||||
"MD002": true
|
||||
};
|
||||
const fsApi = {
|
||||
"accessSync": (p) => {
|
||||
t.is(p, extended);
|
||||
},
|
||||
"readFileSync": (p) => {
|
||||
switch (p) {
|
||||
case file:
|
||||
t.is(p, file);
|
||||
return JSON.stringify(fileContent);
|
||||
case extended:
|
||||
t.is(p, extended);
|
||||
return JSON.stringify(extendedContent);
|
||||
default:
|
||||
return t.fail();
|
||||
}
|
||||
}
|
||||
};
|
||||
const actual = markdownlint.readConfigSync(file, null, fsApi);
|
||||
const expected = {
|
||||
...extendedContent,
|
||||
...fileContent
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
});
|
||||
|
||||
test("configBadHybridSync", (t) => {
|
||||
t.plan(1);
|
||||
t.throws(
|
||||
function badHybridCall() {
|
||||
markdownlint.readConfigSync(
|
||||
"./test/config/config-badcontent.txt",
|
||||
// @ts-ignore
|
||||
[ JSON.parse, require("toml").parse, require("js-yaml").load ]);
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line max-len
|
||||
"message": /^Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+;/
|
||||
},
|
||||
"Did not get correct exception for bad content."
|
||||
);
|
||||
});
|
||||
|
||||
test.cb("configSinglePromise", (t) => {
|
||||
t.plan(1);
|
||||
markdownlint.promises.readConfig("./test/config/config-child.json")
|
||||
.then((actual) => {
|
||||
const expected = require("./config/config-child.json");
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configCustomFileSystemPromise", (t) => {
|
||||
t.plan(4);
|
||||
const file = path.resolve("/dir/file.json");
|
||||
const extended = path.resolve("/dir/extended.json");
|
||||
const fileContent = {
|
||||
"extends": extended,
|
||||
"default": true,
|
||||
"MD001": false
|
||||
};
|
||||
const extendedContent = {
|
||||
"MD001": true,
|
||||
"MD002": true
|
||||
};
|
||||
const fsApi = {
|
||||
"access": (p, m, cb) => {
|
||||
t.is(p, extended);
|
||||
return (cb || m)();
|
||||
},
|
||||
"readFile": (p, o, cb) => {
|
||||
switch (p) {
|
||||
case file:
|
||||
t.is(p, file);
|
||||
return cb(null, JSON.stringify(fileContent));
|
||||
case extended:
|
||||
t.is(p, extended);
|
||||
return cb(null, JSON.stringify(extendedContent));
|
||||
default:
|
||||
return t.fail();
|
||||
}
|
||||
}
|
||||
};
|
||||
markdownlint.promises.readConfig(file, null, fsApi)
|
||||
.then((actual) => {
|
||||
const expected = {
|
||||
...extendedContent,
|
||||
...fileContent
|
||||
};
|
||||
delete expected.extends;
|
||||
t.deepEqual(actual, expected, "Config object not correct.");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test.cb("configBadFilePromise", (t) => {
|
||||
t.plan(2);
|
||||
markdownlint.promises.readConfig("./test/config/config-badfile.json")
|
||||
.then(
|
||||
null,
|
||||
(error) => {
|
||||
t.truthy(error, "Did not get an error for bad JSON.");
|
||||
t.true(error instanceof Error, "Error not instance of Error.");
|
||||
t.end();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("allBuiltInRulesHaveValidUrl", (t) => {
|
||||
t.plan(132);
|
||||
t.plan(138);
|
||||
rules.forEach(function forRule(rule) {
|
||||
t.truthy(rule.information);
|
||||
t.true(Object.getPrototypeOf(rule.information) === URL.prototype);
|
||||
|
|
@ -1712,9 +1242,49 @@ test.cb("texmath test files with texmath plugin", (t) => {
|
|||
});
|
||||
});
|
||||
|
||||
test("token-map-spans", (t) => {
|
||||
t.plan(38);
|
||||
const options = {
|
||||
"customRules": [
|
||||
{
|
||||
"names": [ "token-map-spans" ],
|
||||
"description": "token-map-spans",
|
||||
"tags": [ "tms" ],
|
||||
"function": function tokenMapSpans(params) {
|
||||
const tokenLines = [];
|
||||
let lastLineNumber = -1;
|
||||
const inlines = params.tokens.filter((c) => c.type === "inline");
|
||||
for (const token of inlines) {
|
||||
t.truthy(token.map);
|
||||
for (let i = token.map[0]; i < token.map[1]; i++) {
|
||||
if (tokenLines.includes(i)) {
|
||||
t.true(
|
||||
lastLineNumber === token.lineNumber,
|
||||
`Line ${i + 1} is part of token maps from multiple lines.`
|
||||
);
|
||||
} else {
|
||||
tokenLines.push(i);
|
||||
}
|
||||
lastLineNumber = token.lineNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"files": [ "./test/token-map-spans.md" ]
|
||||
};
|
||||
markdownlint.sync(options);
|
||||
});
|
||||
|
||||
test("getVersion", (t) => {
|
||||
t.plan(1);
|
||||
const actual = markdownlint.getVersion();
|
||||
const expected = version;
|
||||
t.is(actual, expected, "Version string not correct.");
|
||||
});
|
||||
|
||||
test("constants", (t) => {
|
||||
t.plan(2);
|
||||
t.is(constants.homepage, homepage);
|
||||
t.is(constants.version, version);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
# Mixed Emphasis Markers
|
||||
|
||||
This paragraph *uses* both _kinds_ of emphasis marker.
|
||||
This paragraph *uses* both _kinds_ of emphasis marker. {MD049}
|
||||
|
||||
This paragraph _uses_ both *kinds* of emphasis marker.
|
||||
This paragraph _uses_ both *kinds* of emphasis marker. {MD049}
|
||||
|
||||
This paragraph *nests both _kinds_ of emphasis* marker.
|
||||
This paragraph *nests both _kinds_ of emphasis* marker. {MD049}
|
||||
|
||||
This paragraph *nests both __kinds__ of emphasis* marker.
|
||||
|
||||
This paragraph **nests both __kinds__ of emphasis** marker.
|
||||
This paragraph **nests both __kinds__ of emphasis** marker. {MD050}
|
||||
|
||||
This paragraph _nests both *kinds* of emphasis_ marker.
|
||||
This paragraph _nests both *kinds* of emphasis_ marker. {MD049}
|
||||
|
||||
This paragraph _nests both **kinds** of emphasis_ marker.
|
||||
This paragraph _nests both **kinds** of emphasis_ marker. {MD049} {MD050}
|
||||
|
||||
This paragraph __nests both **kinds** of emphasis__ marker.
|
||||
This paragraph __nests both **kinds** of emphasis__ marker. {MD050}
|
||||
|
||||
<!-- markdownlint-disable-file MD037 -->
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Quoted "Vue" and "vue-router"
|
|||
|
||||
Emphasized *Vue* and *vue-router*
|
||||
|
||||
Underscored _Vue_ and _vue-router_
|
||||
Underscored _Vue_ and _vue-router_ {MD049}
|
||||
|
||||
Call it npm
|
||||
But not Npm {MD044}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ Quoted "Markdownlint" {MD044}
|
|||
|
||||
Emphasized *Markdownlint* {MD044}
|
||||
|
||||
Emphasized _Markdownlint_ {MD044}
|
||||
|
||||
JavaScript is a language
|
||||
|
||||
JavaScript is not Java
|
||||
|
|
@ -52,7 +50,7 @@ HTML <u>javascript</u> {MD033} {MD044}
|
|||
node.js is runtime {MD044}
|
||||
|
||||
```js
|
||||
javascript is code {MD044} {MD046:54}
|
||||
javascript is code {MD044} {MD046:52}
|
||||
node.js is runtime {MD044}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|Pattern|Description|
|
||||
|-------------|-----------------|
|
||||
|`(?:\["'\](?<1>\[^"'\]*)["']|(?<1>\S+))`|...|
|
||||
|`(?:\["'\](?<1>\[^"'\]*)["']|(?<1>\S+))`|{MD011}|
|
||||
|
||||
|Pattern|Description|
|
||||
|-------------|-----------------|
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
const { filterTokens } = require("markdownlint-rule-helpers");
|
||||
const eslint = require("eslint");
|
||||
const cliEngine = new eslint.CLIEngine({});
|
||||
const eslintInstance = new eslint.ESLint();
|
||||
const linter = new eslint.Linter();
|
||||
const languageJavaScript = /js|javascript/i;
|
||||
|
||||
|
|
@ -28,23 +28,27 @@ module.exports = {
|
|||
"names": [ "lint-javascript" ],
|
||||
"description": "Rule that lints JavaScript code",
|
||||
"tags": [ "test", "lint", "javascript" ],
|
||||
"asynchronous": true,
|
||||
"function": (params, onError) => {
|
||||
filterTokens(params, "fence", (fence) => {
|
||||
if (languageJavaScript.test(fence.info)) {
|
||||
let config = cliEngine.getConfigForFile(params.name);
|
||||
config = cleanJsdocRulesFromEslintConfig(config);
|
||||
const results = linter.verify(fence.content, config);
|
||||
results.forEach((result) => {
|
||||
const lineNumber = fence.lineNumber + result.line;
|
||||
onError({
|
||||
"lineNumber": lineNumber,
|
||||
"detail": result.message,
|
||||
"context": params.lines[lineNumber - 1]
|
||||
return eslintInstance.calculateConfigForFile(params.name)
|
||||
.then((config) => {
|
||||
config = cleanJsdocRulesFromEslintConfig(config);
|
||||
const results = linter.verify(fence.content, config);
|
||||
results.forEach((result) => {
|
||||
const lineNumber = fence.lineNumber + result.line;
|
||||
onError({
|
||||
"lineNumber": lineNumber,
|
||||
"detail": result.message,
|
||||
"context": params.lines[lineNumber - 1]
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
// Unused:
|
||||
// Unsupported:
|
||||
// filterTokens("code_block"), language unknown
|
||||
// filterTokens("code_inline"), too brief
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ code
|
|||
|
||||
Mixed `code_span`
|
||||
scenarios
|
||||
are _also_ okay.
|
||||
are _also_ okay. {MD049}
|
||||
|
||||
Mixed `code*span`
|
||||
scenarios
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# Heading
|
||||
|
||||
<!-- markdownlint-disable-file emphasis-style strong-style -->
|
||||
|
||||
Line with *Normal emphasis*
|
||||
|
||||
Line with **Normal strong**
|
||||
|
|
@ -345,3 +347,13 @@ text [reference*link] star * star text
|
|||
```yaml /* autogenerated */
|
||||
# YAML...
|
||||
```
|
||||
|
||||
new_value from *old_value* and *older_value*.
|
||||
|
||||
:ballot_box_with_check: _Emoji syntax_
|
||||
|
||||
some_snake_case_function() is _called_
|
||||
|
||||
_~/.ssh/id_rsa_ and _emphasis_
|
||||
|
||||
Partial *em*phasis of a *wo*rd.
|
||||
|
|
|
|||
|
|
@ -50,3 +50,15 @@ Wrapped [ link with leading space
|
|||
](https://example.com) {MD039}
|
||||
|
||||
Non-wrapped [ link with leading space](https://example.com) {MD039}
|
||||
|
||||
[][ref]
|
||||
|
||||
[link][ref]
|
||||
|
||||
[link ][ref] {MD039}
|
||||
|
||||
[ link][ref] {MD039}
|
||||
|
||||
[ link ][ref] {MD039}
|
||||
|
||||
[ref]: https://example.com
|
||||
|
|
|
|||
6
test/strong_style_asterisk.json
Normal file
6
test/strong_style_asterisk.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"default": true,
|
||||
"MD050": {
|
||||
"style": "asterisk"
|
||||
}
|
||||
}
|
||||
5
test/strong_style_asterisk.md
Normal file
5
test/strong_style_asterisk.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Strong style asterisk
|
||||
|
||||
This is **fine**
|
||||
|
||||
This is __not__ {MD050}
|
||||
6
test/strong_style_underscore.json
Normal file
6
test/strong_style_underscore.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"default": true,
|
||||
"MD050": {
|
||||
"style": "underscore"
|
||||
}
|
||||
}
|
||||
5
test/strong_style_underscore.md
Normal file
5
test/strong_style_underscore.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Strong style underscore
|
||||
|
||||
This is __fine__
|
||||
|
||||
This is **not** {MD050}
|
||||
13
test/table-content-with-issues.md
Normal file
13
test/table-content-with-issues.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Table Content With Issues
|
||||
|
||||
| Content | Issue |
|
||||
|------------------------------|---------|
|
||||
| Text | N/A |
|
||||
| (link)[https://example.com] | {MD011} |
|
||||
| <hr> | {MD033} |
|
||||
| https://example.com | {MD034} |
|
||||
| * emphasis* | {MD037} |
|
||||
| __strong __ | {MD037} |
|
||||
| ` code` | {MD038} |
|
||||
| [link ](https://example.com) | {MD039} |
|
||||
| [link]() | {MD042} |
|
||||
42
test/token-map-spans.md
Normal file
42
test/token-map-spans.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Token Map Spans
|
||||
|
||||
Text *emphasis* text __strong__ text `code` text [link](https://example.com).
|
||||
|
||||
Paragraph with *emphasis
|
||||
spanning lines* and __strong
|
||||
spanning lines__ and `code
|
||||
spanning lines` and [link
|
||||
spanning lines](https://example.com).
|
||||
|
||||
> Blockquote
|
||||
> [link](https://example.com)
|
||||
> > Nested
|
||||
> > blockquote
|
||||
> > [link](https://example.com)
|
||||
|
||||
Heading
|
||||
-------
|
||||
|
||||
```lang
|
||||
Fenced
|
||||
code
|
||||
```
|
||||
|
||||
Indented
|
||||
code
|
||||
|
||||
1. List
|
||||
2. List
|
||||
- Sub-list
|
||||
- Sub-list
|
||||
3. List
|
||||
|
||||
| Table | Column 1 | Column 2 | Column 3 | Column 4 |
|
||||
|-------|------------|------------|----------|----------------------------|
|
||||
| Text | *emphasis* | __strong__ | `code` | [link](https://example.com) |
|
||||
| Text | *emphasis* | __strong__ | `code` | [link](https://example.com) |
|
||||
|
||||
<!-- markdownlint-configure-file {
|
||||
"code-block-style": false,
|
||||
"heading-style": false
|
||||
} -->
|
||||
Loading…
Add table
Add a link
Reference in a new issue