feat: use go tool
This commit is contained in:
+520
@@ -0,0 +1,520 @@
|
||||
# Changelog
|
||||
|
||||
## 0.40.0
|
||||
|
||||
- Improve MD011/MD013/MD051/MD060
|
||||
- Update dependencies
|
||||
|
||||
## 0.39.0
|
||||
|
||||
- Add MD060/table-column-style
|
||||
- Improve MD001/MD007/MD009/MD010/MD029/MD033/MD037/MD059
|
||||
- Add support for reporting violations as severity `warning`
|
||||
- Deprecate `resultVersion` and `toString` (breaking change)
|
||||
- Improve type definitions
|
||||
- Improve demo web page
|
||||
- Update dependencies
|
||||
|
||||
## 0.38.0
|
||||
|
||||
- Add MD059/descriptive-link-text
|
||||
- Improve MD025/MD027/MD036/MD038/MD041/MD043/MD045/MD051/MD052
|
||||
- `markdown-it` parser no longer a production dependency (breaking change)
|
||||
- Add `markdownItFactory` option, remove `markdownItPlugins` option
|
||||
- Remove support for end-of-life Node version 18
|
||||
- Improve performance
|
||||
- Update dependencies
|
||||
|
||||
## 0.37.4
|
||||
|
||||
- Stop using `module.createRequire`, export `resolveModule`
|
||||
|
||||
## 0.37.3
|
||||
|
||||
- Tweak `package.json` dependencies to work with `pnpm`
|
||||
|
||||
## 0.37.2
|
||||
|
||||
- Add subpath imports for overriding default bundler behavior
|
||||
- Improve MD032
|
||||
|
||||
## 0.37.1
|
||||
|
||||
- Add support for "browser" condition (as used by webpack)
|
||||
|
||||
## 0.37.0
|
||||
|
||||
- Convert module to ECMAScript (breaking change)
|
||||
- <https://nodejs.org/docs/latest/api/esm.html>
|
||||
- <https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c>
|
||||
- Convert module to named exports (breaking change)
|
||||
|
||||
## 0.36.1
|
||||
|
||||
- Fix behavior of MD054
|
||||
|
||||
## 0.36.0
|
||||
|
||||
- Improve MD051
|
||||
- Move `applyFix` and `applyFixes` from helpers to core
|
||||
- Make `micromark` parser available to custom rules
|
||||
- Introduce `./micromark` helpers exports
|
||||
- Update custom/rule documentation
|
||||
- Improve performance
|
||||
- Update dependencies
|
||||
|
||||
## 0.35.0
|
||||
|
||||
- Add MD058/blanks-around-tables
|
||||
- Use `micromark` in MD001/MD003/MD009/MD010/MD013/MD014/MD019/MD021/MD023/
|
||||
MD024/MD025/MD039/MD042/MD043
|
||||
- Improve MD018/MD020/MD031/MD034/MD044
|
||||
- `markdown-it` parser no longer invoked by default
|
||||
- Add strict version of JSON schema
|
||||
- Improve performance
|
||||
- Update dependencies
|
||||
|
||||
## 0.34.0
|
||||
|
||||
- Use `micromark` in MD027/MD028/MD036/MD040/MD041/MD046/MD048
|
||||
- Improve MD013/MD034/MD049/MD050/MD051
|
||||
- Update custom rule requirements and documentation
|
||||
- Improve various TypeScript declarations
|
||||
- Update dependencies
|
||||
|
||||
## 0.33.0
|
||||
|
||||
- Add MD055/table-pipe-style, MD056/table-column-count
|
||||
- Improve MD005/MD007/MD024/MD026/MD038
|
||||
- Incorporate `micromark-extension-directive`
|
||||
- Improve JSON schema, document validation
|
||||
- Reduce size of browser script
|
||||
- Update dependencies
|
||||
|
||||
## 0.32.1
|
||||
|
||||
- Fix behavior of MD054
|
||||
|
||||
## 0.32.0
|
||||
|
||||
- Remove deprecated MD002/MD006
|
||||
- Remove rule aliases for "header"
|
||||
- Add MD054/link-image-style
|
||||
- Use `micromark` in MD005/MD007/MD030
|
||||
- Improve MD022/MD026/MD034/MD037/MD038/MD045/MD051
|
||||
- Improve JSON schema and related examples
|
||||
- Provide type declaration for Configuration object
|
||||
- Remove support for end-of-life Node version 16
|
||||
- Update dependencies
|
||||
|
||||
## 0.31.1
|
||||
|
||||
- Improve MD032/MD034
|
||||
- Update dependencies
|
||||
|
||||
## 0.31.0
|
||||
|
||||
- Improve MD032/MD037/MD043/MD044/MD051/MD052
|
||||
- Improve performance
|
||||
- Update dependencies
|
||||
|
||||
## 0.30.0
|
||||
|
||||
- Use `micromark` in MD022/MD026/MD032/MD037/MD045/MD051
|
||||
- Incorporate `micromark-extension-math` for math syntax
|
||||
- Allow custom rules to override information URL
|
||||
- Update dependencies
|
||||
|
||||
## 0.29.0
|
||||
|
||||
- Update `micromark` parser dependencies for better performance
|
||||
- Use `micromark` in MD049/MD050
|
||||
- Improve MD034/MD037/MD044/MD049/MD050
|
||||
- Support multiple parsers in demo page
|
||||
- Remove support for end-of-life Node version 14
|
||||
- Update dependencies
|
||||
|
||||
## 0.28.2
|
||||
|
||||
- Update dependencies for CVE-2023-2251
|
||||
|
||||
## 0.28.1
|
||||
|
||||
- Update dependencies
|
||||
|
||||
## 0.28.0
|
||||
|
||||
- Introduce `micromark` parser for better positional data (internal only)
|
||||
- Use `micromark` in MD013/MD033/MD034/MD035/MD038/MD044/MD052/MD053
|
||||
- Simplify file-based test cases
|
||||
- Unify browser script for demo page
|
||||
- Update dependencies
|
||||
|
||||
## 0.27.0
|
||||
|
||||
- Improve MD011/MD013/MD022/MD031/MD032/MD033/MD034/MD040/MD043/MD051/MD053
|
||||
- Generate/separate documentation
|
||||
- Improve documentation
|
||||
- Update dependencies
|
||||
|
||||
## 0.26.2
|
||||
|
||||
- Improve MD037/MD051/MD053
|
||||
|
||||
## 0.26.1
|
||||
|
||||
- Improve MD051
|
||||
|
||||
## 0.26.0
|
||||
|
||||
- Add MD051/MD052/MD053 for validating link fragments & reference
|
||||
links/images & link/image reference definitions (MD053 auto-fixable)
|
||||
- Improve MD010/MD031/MD035/MD039/MD042/MD044/MD049/MD050
|
||||
- Add `markdownlint-disable-line` inline comment
|
||||
- Support `~` paths in `readConfig/Sync`
|
||||
- Add `configParsers` option
|
||||
- Remove support for end-of-life Node version 12
|
||||
- Default `resultVersion` to 3
|
||||
- Update browser script to use ES2015
|
||||
- Simplify JSON schema
|
||||
- Address remaining CodeQL issues
|
||||
- Improve performance
|
||||
- Update dependencies
|
||||
|
||||
## 0.25.1
|
||||
|
||||
- Update dependencies for CVE-2022-21670
|
||||
|
||||
## 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
|
||||
|
||||
## 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.23.1
|
||||
|
||||
- Work around lack of webpack support for dynamic calls to `require`(`.resolve`)
|
||||
|
||||
## 0.23.0
|
||||
|
||||
- Add comprehensive example `.markdownlint.jsonc`/`.markdownlint.yaml` files
|
||||
- Add fix information for MD004/ul-style
|
||||
- Improve MD018/MD019/MD020/MD021/MD037/MD041
|
||||
- Improve HTML comment handling
|
||||
- Update test runner and test suite
|
||||
- Update dependencies
|
||||
|
||||
## 0.22.0
|
||||
|
||||
- Allow `extends` in config to reference installed packages by name
|
||||
- Add `markdownlint-disable-next-line` inline comment
|
||||
- Support JSON front matter
|
||||
- Improve MD009/MD026/MD028/MD043
|
||||
- Update dependencies (including `markdown-it` to v12)
|
||||
|
||||
## 0.21.1
|
||||
|
||||
- Improve MD011/MD031
|
||||
- Export `getVersion` API
|
||||
|
||||
## 0.21.0
|
||||
|
||||
- Lint concurrently for better performance (async only)
|
||||
- Add Promise-based APIs
|
||||
- Update TypeScript declaration file
|
||||
- Hide `toString` on `LintResults`
|
||||
- Add ability to fix in browser demo
|
||||
- Allow custom rules in `.markdownlint.json` schema
|
||||
- Improve MD042/MD044
|
||||
- Improve documentation
|
||||
- Update dependencies
|
||||
|
||||
## 0.20.4
|
||||
|
||||
- Fix regression in MD037
|
||||
- Improve MD034/MD044
|
||||
- Improve documentation
|
||||
|
||||
## 0.20.3
|
||||
|
||||
- Fix regression in MD037
|
||||
- Improve MD044
|
||||
- Add automatic regression testing
|
||||
|
||||
## 0.20.2
|
||||
|
||||
- Fix regression in MD037
|
||||
- Improve MD038
|
||||
|
||||
## 0.20.1
|
||||
|
||||
- Fix regression in MD037
|
||||
|
||||
## 0.20.0
|
||||
|
||||
- Add `markdownlint-configure-file` inline comment
|
||||
- Reimplement MD037
|
||||
- Improve MD005/MD007/MD013/MD018/MD029/MD031/MD034/MD038/MD039
|
||||
- Improve HTML comment handling
|
||||
- Update dependencies
|
||||
|
||||
## 0.19.0
|
||||
|
||||
- Remove support for end-of-life Node version 8
|
||||
- Add fix information for MD005/list-indent
|
||||
- Improve MD007/MD013/MD014
|
||||
- Deprecate MD006/ul-start-left
|
||||
- Add rationale for every rule
|
||||
- Update test runner and code coverage
|
||||
- Add more JSDoc comments
|
||||
- Update dependencies
|
||||
|
||||
## 0.18.0
|
||||
|
||||
- Add MD048/code-fence-style
|
||||
- Add fix information for MD007/ul-indent
|
||||
- Add `markdownlint-disable-file`/`markdownlint-enable-file` inline comments
|
||||
- Add type declaration file (.d.ts) for TypeScript dependents
|
||||
- Update schema
|
||||
- Improve MD006/MD007/MD009/MD013/MD030
|
||||
- Update dependencies
|
||||
|
||||
## 0.17.2
|
||||
|
||||
- Improve MD020/MD033/MD044
|
||||
|
||||
## 0.17.1
|
||||
|
||||
- Fix handling of front matter by fix information
|
||||
|
||||
## 0.17.0
|
||||
|
||||
- Add `resultVersion` 3 to support fix information for default and custom rules
|
||||
- Add fix information for 24 rules
|
||||
- Update newline handling to match latest CommonMark specification
|
||||
- Improve MD014/MD037/MD039
|
||||
- Update dependencies
|
||||
|
||||
## 0.16.0
|
||||
|
||||
- Add custom rule sample for linting code
|
||||
- Improve MD026/MD031/MD033/MD038
|
||||
- Update dependencies
|
||||
|
||||
## 0.15.0
|
||||
|
||||
- Add `markdownlint-capture`/`markdownlint-restore` inline comments
|
||||
- Improve MD009/MD013/MD026/MD033/MD036
|
||||
- Update dependencies
|
||||
|
||||
## 0.14.2
|
||||
|
||||
- Improve MD047
|
||||
- Add `handleRuleFailures` option
|
||||
|
||||
## 0.14.1
|
||||
|
||||
- Improve MD033
|
||||
|
||||
## 0.14.0
|
||||
|
||||
- Remove support for end-of-life Node version 6
|
||||
- Introduce `markdownlint-rule-helpers`
|
||||
- Add MD046/MD047
|
||||
- Improve MD033/MD034/MD039
|
||||
- Improve custom rule validation and in-browser demo
|
||||
- Update dependencies
|
||||
|
||||
## 0.13.0
|
||||
|
||||
- Improve MD013/MD022/MD025/MD029/MD031/MD032/MD037/MD041
|
||||
- Deprecate MD002
|
||||
- Improve Pandoc YAML support
|
||||
- Update dependencies
|
||||
|
||||
## 0.12.0
|
||||
|
||||
- Add `information` link for custom rules
|
||||
- Add `markdownItPlugins` for extensibility
|
||||
- Improve MD023/MD032/MD038
|
||||
- Update dependencies
|
||||
|
||||
## 0.11.0
|
||||
|
||||
- Improve MD005/MD024/MD029/MD038
|
||||
- Improve custom rule example
|
||||
- Add `CONTRIBUTING.md`
|
||||
- Update dependencies
|
||||
|
||||
## 0.10.0
|
||||
|
||||
- Add support for non-JSON configuration files
|
||||
- Pass file/string name to custom rules
|
||||
- Update dependencies
|
||||
|
||||
## 0.9.0
|
||||
|
||||
- Remove support for end-of-life Node versions 0.10/0.12/4
|
||||
- Change "header" to "heading" per spec (non-breaking)
|
||||
- Improve MD003/MD009/MD041
|
||||
- Handle uncommon line-break characters
|
||||
- Refactor for ES6
|
||||
- Update dependencies
|
||||
|
||||
## 0.8.1
|
||||
|
||||
- Update item loop to be iterative
|
||||
- Improve MD014
|
||||
- Update dependencies
|
||||
|
||||
## 0.8.0
|
||||
|
||||
- Add support for using and authoring custom rules
|
||||
- Improve MD004/MD007/MD013
|
||||
- Add `engines` to `package.json`
|
||||
- Refactor
|
||||
- Update dependencies
|
||||
|
||||
## 0.7.0
|
||||
|
||||
- `resultVersion` defaults to 2 (breaking change)
|
||||
- Add MD045
|
||||
- Improve MD029
|
||||
- Remove `trimLeft`/`trimRight`
|
||||
- Split rules
|
||||
- Refactor
|
||||
- Update dependencies
|
||||
|
||||
## 0.6.4
|
||||
|
||||
- Improve MD029/MD042
|
||||
- Update dependencies
|
||||
|
||||
## 0.6.3
|
||||
|
||||
- Improve highlighting for MD020
|
||||
|
||||
## 0.6.2
|
||||
|
||||
- Improve MD013/MD027/MD034/MD037/MD038/MD041/MD044
|
||||
- Update dependencies
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- Update `markdown-it` versioning
|
||||
- Exclude demo/test from publishing
|
||||
|
||||
## 0.6.0
|
||||
|
||||
- `resultVersion` defaults to 1 (breaking change)
|
||||
- Ignore HTML comments
|
||||
- TOML front matter
|
||||
- Fixes for MD044
|
||||
- Update dependencies
|
||||
|
||||
## 0.5.0
|
||||
|
||||
- Add shareable configuration
|
||||
- Add `noInlineConfig` option
|
||||
- Add `README.md` links
|
||||
- Fix MD030
|
||||
- Improve MD009/MD041
|
||||
- Update dependencies
|
||||
|
||||
## 0.4.1
|
||||
|
||||
- Fixes for MD038/front matter
|
||||
- Improvements to MD044
|
||||
- Update dependencies
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- Add MD044
|
||||
- Enhance MD013/MD032/MD041/MD042/MD043
|
||||
- Fix for MD038
|
||||
- Update dependencies
|
||||
|
||||
## 0.3.1
|
||||
|
||||
- Fix regressions in MD032/MD038
|
||||
- Update dependencies
|
||||
|
||||
## 0.3.0
|
||||
|
||||
- More detailed error reporting with `resultVersion`
|
||||
- Enhance MD010/MD012/MD036
|
||||
- Fixes for MD027/MD029/MD030
|
||||
- Include JSON schema dependencies
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Add MD042/MD043
|
||||
- Enhance MD002/MD003/MD004/MD007/MD011/MD025/MD041
|
||||
- Update dependencies
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- Fix bug handling HTML in tables
|
||||
- Reference `markdownlint-cli`
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Add aliases
|
||||
- Exceptions for MD033
|
||||
- Exclusions for MD013
|
||||
- Update dependencies
|
||||
|
||||
## 0.0.8
|
||||
|
||||
- Support disabling/enabling rules inline
|
||||
- Improve code fence
|
||||
- Update dependencies
|
||||
|
||||
## 0.0.7
|
||||
|
||||
- Add MD041
|
||||
- Improve MD003
|
||||
- Ignore front matter
|
||||
- Update dependencies
|
||||
|
||||
## 0.0.6
|
||||
|
||||
- Improve performance
|
||||
- Simplify in-browser
|
||||
- Update dependencies
|
||||
|
||||
## 0.0.5
|
||||
|
||||
- Add `strings` option to enable file-less scenarios
|
||||
- Add in-browser demo
|
||||
|
||||
## 0.0.4
|
||||
|
||||
- Add tests MD033-MD040
|
||||
- Update dependencies
|
||||
|
||||
## 0.0.3
|
||||
|
||||
- Add synchronous API
|
||||
- Improve documentation and code
|
||||
|
||||
## 0.0.2
|
||||
|
||||
- Improve documentation, tests, and code
|
||||
|
||||
## 0.0.1
|
||||
|
||||
- Initial release
|
||||
- Includes tests MD001-MD032
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# Contributing
|
||||
|
||||
Interested in contributing? Great! Here are some suggestions to make it a good
|
||||
experience:
|
||||
|
||||
Start by [opening an issue](https://github.com/DavidAnson/markdownlint/issues),
|
||||
whether to identify a problem or outline a change. That issue should be used to
|
||||
discuss the situation and agree on a plan of action before writing code or
|
||||
sending a pull request. Maybe the problem isn't really a problem, or maybe there
|
||||
are more things to consider. If so, it's best to realize that before spending
|
||||
time and effort writing code that may not get used.
|
||||
|
||||
Match the coding style of the files you edit. Although everyone has their own
|
||||
preferences and opinions, a pull request is not the right forum to debate them.
|
||||
|
||||
Do not add new [`dependencies` to `package.json`][dependencies]. The Markdown
|
||||
parser [`micromark`][micromark] (and its extensions) is this project's only
|
||||
dependency.
|
||||
|
||||
Package versions for `dependencies` and `devDependencies` should be specified
|
||||
exactly (also known as "pinning"). The short explanation is that doing otherwise
|
||||
eventually leads to inconsistent behavior and broken functionality. (See [Why I
|
||||
pin dependency versions in Node.js packages][version-pinning] for a longer
|
||||
explanation.)
|
||||
|
||||
If developing a new rule, start by creating a [custom rule][custom-rules] in its
|
||||
own project. Once written, published, and tested in real world scenarios, open
|
||||
an issue to consider adding it to this project. For rule ideas, see [issues
|
||||
tagged with the `new rule` label][new-rule].
|
||||
|
||||
Add tests for all new/changed functionality. Test positive and negative
|
||||
scenarios. 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][npm-scripts]. Tests should all pass on all platforms. The test runner is
|
||||
[AVA][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. If `some-test.md` needs custom
|
||||
configuration, a `some-test.json` is used to provide a custom `options.config`
|
||||
for that scenario. Tests run by `markdownlint-test-scenarios.js` use [AVA's
|
||||
snapshot feature][ava-snapshots]. To update snapshots (for example, after
|
||||
modifying a test file), run `npm run update-snapshots` and include the updated
|
||||
files with the pull request.
|
||||
|
||||
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 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. (See [Git
|
||||
Tools - Rewriting History][rewriting-history] for details.)
|
||||
|
||||
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 (`.`). 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][closing-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][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, ChatGPT, LLMs (large language models), etc. that
|
||||
incorporate code from other projects is not allowed.
|
||||
|
||||
Thank you!
|
||||
|
||||
[ava]: https://github.com/avajs/ava
|
||||
[ava-snapshots]: https://github.com/avajs/ava/blob/main/docs/04-snapshot-testing.md
|
||||
[closing-keywords]: https://help.github.com/articles/closing-issues-using-keywords/
|
||||
[custom-rules]: doc/CustomRules.md
|
||||
[dependencies]: https://docs.npmjs.com/cli/v11/configuring-npm/package-json#dependencies
|
||||
[example-com]: https://en.wikipedia.org/wiki/Example.com
|
||||
[micromark]: https://www.npmjs.com/package/micromark
|
||||
[new-rule]: https://github.com/DavidAnson/markdownlint/labels/new%20rule
|
||||
[npm-scripts]: https://docs.npmjs.com/misc/scripts
|
||||
[rewriting-history]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History
|
||||
[version-pinning]: https://dlaa.me/blog/post/versionpinning
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) David Anson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+1124
File diff suppressed because it is too large
Load Diff
+194
@@ -0,0 +1,194 @@
|
||||
# Custom Rules
|
||||
|
||||
In addition to its built-in rules, `markdownlint` lets you enhance the linting
|
||||
experience by passing an array of custom rules using the [`options.customRules`
|
||||
property][options-custom-rules]. Custom rules can do everything the built-in
|
||||
rules can and are defined inline or imported from another package ([keyword
|
||||
`markdownlint-rule` on npm][markdownlint-rule]). When defined by a file or
|
||||
package, the export can be a single rule object (see below) or an array of them.
|
||||
Custom rules can be disabled, enabled, and customized using the same syntax as
|
||||
built-in rules.
|
||||
|
||||
## Implementing Simple Rules
|
||||
|
||||
For simple requirements like disallowing certain characters or patterns,
|
||||
the community-developed
|
||||
[markdownlint-rule-search-replace][markdownlint-rule-search-replace]
|
||||
plug-in can be used. This plug-in allows anyone to create a set of simple
|
||||
text-replacement rules without needing to write code.
|
||||
|
||||
[markdownlint-rule-search-replace]: https://www.npmjs.com/package/markdownlint-rule-search-replace
|
||||
|
||||
## Authoring
|
||||
|
||||
Rules are defined by a name (or multiple names), a description, an optional link
|
||||
to more information, one or more tags, and a function that implements the rule's
|
||||
behavior. That function is called once for each file/string input and is passed
|
||||
the parsed input and a function to log any violations.
|
||||
|
||||
Custom rules can (should) operate on a structured set of tokens based on the
|
||||
[`micromark`][micromark] `parser` (this is preferred). Alternatively, custom
|
||||
rules can operate on a structured set of tokens based on the
|
||||
[`markdown-it`][markdown-it] `parser` (legacy support). Finally, custom rules
|
||||
can operate directly on text with the `none` `parser`.
|
||||
|
||||
A simple rule implementation using the `micromark` parser to report a violation
|
||||
for any use of blockquotes might look like:
|
||||
|
||||
```javascript
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
module.exports = {
|
||||
"names": [ "any-blockquote-micromark" ],
|
||||
"description": "Rule that reports an error for any blockquote",
|
||||
"information": new URL("https://example.com/rules/any-blockquote"),
|
||||
"tags": [ "test" ],
|
||||
"parser": "micromark",
|
||||
"function": (params, onError) => {
|
||||
const blockquotes = params.parsers.micromark.tokens
|
||||
.filter((token) => token.type === "blockQuote");
|
||||
for (const blockquote of blockquotes) {
|
||||
const lines = blockquote.endLine - blockquote.startLine + 1;
|
||||
onError({
|
||||
"lineNumber": blockquote.startLine,
|
||||
"detail": "Blockquote spans " + lines + " line(s).",
|
||||
"context": params.lines[blockquote.startLine - 1]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That same rule implemented using the `markdown-it` parser might look like:
|
||||
|
||||
```javascript
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
module.exports = {
|
||||
"names": [ "any-blockquote-markdown-it" ],
|
||||
"description": "Rule that reports an error for any blockquote",
|
||||
"information": new URL("https://example.com/rules/any-blockquote"),
|
||||
"tags": [ "test" ],
|
||||
"parser": "markdownit",
|
||||
"function": (params, onError) => {
|
||||
const blockquotes = params.parsers.markdownit.tokens
|
||||
.filter((token) => token.type === "blockquote_open");
|
||||
for (const blockquote of blockquotes) {
|
||||
const [ startIndex, endIndex ] = blockquote.map;
|
||||
const lines = endIndex - startIndex;
|
||||
onError({
|
||||
"lineNumber": blockquote.lineNumber,
|
||||
"detail": "Blockquote spans " + lines + " line(s).",
|
||||
"context": blockquote.line
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A rule is implemented as an `Object`:
|
||||
|
||||
- `names` is a required `Array` of `String` values that identify the rule in
|
||||
output messages and config.
|
||||
- `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.
|
||||
- `parser` is a required `String` value `"markdownit" | "micromark" | "none"`
|
||||
that specifies the parser data used via `params.parsers` (see below).
|
||||
- `asynchronous` is an optional `Boolean` value that indicates whether the rule
|
||||
returns a `Promise` and runs asynchronously.
|
||||
- `function` is a required `Function` that implements the rule and is passed two
|
||||
parameters:
|
||||
- `params` is an `Object` with properties that describe the content being
|
||||
analyzed:
|
||||
- `name` is a `String` that identifies the input file/string.
|
||||
- `parsers` is an `Object` with properties corresponding to the value of
|
||||
`parser` in the rule definition (see above).
|
||||
- `markdownit` is an `Object` that provides access to output from the
|
||||
[`markdown-it`][markdown-it] parser.
|
||||
- `tokens` is an `Array` of [`markdown-it` `Token`s][markdown-it-token]
|
||||
with added `line` and `lineNumber` properties. (This property was
|
||||
previously on the `params` object.)
|
||||
- `micromark` is an `Object` that provides access to output from the
|
||||
[`micromark`][micromark] parser.
|
||||
- `tokens` is an `Array` of [`MicromarkToken`][micromark-token] objects.
|
||||
- Samples for both `tokens` are available via [test snapshots][tokens].
|
||||
- `lines` is an `Array` of `String` values corresponding to the lines of the
|
||||
input file/string.
|
||||
- `frontMatterLines` is an `Array` of `String` values corresponding to any
|
||||
front matter (not present in `lines`).
|
||||
- `config` is an `Object` corresponding to the rule's entry in
|
||||
`options.config` (if present).
|
||||
- `version` is a `String` that corresponds to the version of `markdownlint`
|
||||
- `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.
|
||||
- `detail` is an optional `String` with information about what caused the
|
||||
error.
|
||||
- `context` is an optional `String` with relevant text surrounding the error
|
||||
location.
|
||||
- `information` is an optional (absolute) `URL` of a link to override the
|
||||
same-named value provided by the rule definition. (Uncommon)
|
||||
- `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 at least one of `deleteCount` and
|
||||
`insertText` should be present; when applying a fix, the delete should be
|
||||
performed before the insert):
|
||||
- `lineNumber` is an optional `Number` specifying the 1-based line number
|
||||
of the edit.
|
||||
- `editColumn` is an optional `Number` specifying the 1-based column
|
||||
number of the edit.
|
||||
- `deleteCount` is an optional `Number` specifying the number of
|
||||
characters to delete (the value `-1` is used to delete the line).
|
||||
- `insertText` is an optional `String` specifying the text to insert. `\n`
|
||||
is the platform-independent way to add a line break; line breaks should
|
||||
be added at the beginning of a line instead of at the end.
|
||||
|
||||
The collection of helper functions shared by the built-in rules is available for
|
||||
use by custom rules in the [markdownlint-rule-helpers package][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., `import { lint } from "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]
|
||||
- [Complete example rule including npm configuration][extended-ascii]
|
||||
- [Custom rules from the github/docs repository][github-docs]
|
||||
- [Custom rules from the electron/lint-roller repository][electron]
|
||||
- [Custom rules from the webhintio/hint repository][hint]
|
||||
|
||||
## References
|
||||
|
||||
- [CommonMark documentation and specification][commonmark]
|
||||
- [`markdown-it` Markdown parser project page][markdown-it]
|
||||
|
||||
[commonmark]: https://commonmark.org/
|
||||
[electron]: https://github.com/electron/lint-roller/tree/main/markdownlint-rules
|
||||
[extended-ascii]: https://github.com/DavidAnson/markdownlint-rule-extended-ascii
|
||||
[github-docs]: https://github.com/github/docs/tree/main/src/content-linter/lib/linting-rules
|
||||
[hint]: https://github.com/webhintio/hint/blob/main/scripts/lint-markdown.js
|
||||
[lib]: ../lib
|
||||
[markdown-it]: https://github.com/markdown-it/markdown-it
|
||||
[markdown-it-token]: https://markdown-it.github.io/markdown-it/#Token
|
||||
[markdownlint-rule]: https://www.npmjs.com/search?q=keywords:markdownlint-rule
|
||||
[micromark]: https://github.com/micromark/micromark
|
||||
[micromark-token]: ../lib/markdownlint.d.mts
|
||||
[rule-helpers]: https://www.npmjs.com/package/markdownlint-rule-helpers
|
||||
[options-custom-rules]: ../README.md#optionscustomrules
|
||||
[test-rules]: ../test/rules
|
||||
[tokens]: ../test/snapshots/markdownlint-test-custom-rules.mjs.md
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Using `markdownlint` with Prettier
|
||||
|
||||
[`Prettier`](https://prettier.io) is a popular code formatter.
|
||||
For the most part, Prettier works seamlessly with `markdownlint`.
|
||||
|
||||
You can `extend` the [`prettier.json`](../style/prettier.json) style to disable
|
||||
all `markdownlint` rules that overlap with Prettier.
|
||||
|
||||
Other scenarios are documented below.
|
||||
|
||||
## List item indentation
|
||||
|
||||
The default settings of `markdownlint` and `Prettier` are compatible and don't
|
||||
result in any linting violations. If `Prettier` is used with `--tab-width` set
|
||||
to `4` (vs. `2`), the following `markdownlint` configuration can be used:
|
||||
|
||||
```json
|
||||
{
|
||||
"list-marker-space": {
|
||||
"ul_multi": 3,
|
||||
"ul_single": 3
|
||||
},
|
||||
"ul-indent": {
|
||||
"indent": 4
|
||||
}
|
||||
}
|
||||
```
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# Release Process
|
||||
|
||||
The `markdownlint` library has some related dependencies that are updated along
|
||||
with it. To prevent possible regressions from having a widespread impact, these
|
||||
releases are separated by a few days to provide an opportunity to find issues.
|
||||
|
||||
1. [`markdownlint`][markdownlint]
|
||||
2. [`markdownlint-cli2`][markdownlint-cli2]
|
||||
3. [`markdownlint-cli2-action`][markdownlint-cli2-action]
|
||||
4. [`vscode-markdownlint`][vscode-markdownlint]
|
||||
5. [`markdownlint-cli`][markdownlint-cli]
|
||||
|
||||
This sequence is not strict and may be adjusted based on the content of the
|
||||
release and the scope of feature work in each dependency.
|
||||
|
||||
[markdownlint]: https://github.com/DavidAnson/markdownlint
|
||||
[markdownlint-cli2]: https://github.com/DavidAnson/markdownlint-cli2
|
||||
[markdownlint-cli2-action]: https://github.com/marketplace/actions/markdownlint-cli2-action
|
||||
[vscode-markdownlint]: https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint
|
||||
[markdownlint-cli]: https://github.com/igorshubovych/markdownlint-cli
|
||||
+2814
File diff suppressed because it is too large
Load Diff
+51
@@ -0,0 +1,51 @@
|
||||
# `MD001` - Heading levels should only increment by one level at a time
|
||||
|
||||
Tags: `headings`
|
||||
|
||||
Aliases: `heading-increment`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `front_matter_title`: RegExp for matching title in front matter (`string`,
|
||||
default `^\s*title\s*[:=]`)
|
||||
|
||||
This rule is triggered when you skip heading levels in a Markdown document, for
|
||||
example:
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
|
||||
### Heading 3
|
||||
|
||||
We skipped out a 2nd level heading in this document
|
||||
```
|
||||
|
||||
When using multiple heading levels, nested headings should increase by only one
|
||||
level at a time:
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
## Another Heading 2
|
||||
|
||||
### Another Heading 3
|
||||
```
|
||||
|
||||
If [YAML](https://en.wikipedia.org/wiki/YAML) front matter is present and
|
||||
contains a `title` property (commonly used with blog posts), this rule treats
|
||||
that as a top level heading and will report a violation if the actual first
|
||||
heading is not a level 2 heading. To use a different property name in the
|
||||
front matter, specify the text of a regular expression via the
|
||||
`front_matter_title` parameter. To disable the use of front matter by this
|
||||
rule, specify `""` for `front_matter_title`. When front matter is not present,
|
||||
the first heading can be any level.
|
||||
|
||||
Rationale: Headings represent the structure of a document and can be confusing
|
||||
when skipped - especially for accessibility scenarios. More information:
|
||||
<https://www.w3.org/WAI/tutorials/page-structure/headings/>.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# `MD003` - Heading style
|
||||
|
||||
Tags: `headings`
|
||||
|
||||
Aliases: `heading-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: Heading style (`string`, default `consistent`, values `atx` /
|
||||
`atx_closed` / `consistent` / `setext` / `setext_with_atx` /
|
||||
`setext_with_atx_closed`)
|
||||
|
||||
This rule is triggered when different heading styles are used in the same
|
||||
document:
|
||||
|
||||
```markdown
|
||||
# ATX style H1
|
||||
|
||||
## Closed ATX style H2 ##
|
||||
|
||||
Setext style H1
|
||||
===============
|
||||
```
|
||||
|
||||
To fix the issue, use consistent heading styles throughout the document:
|
||||
|
||||
```markdown
|
||||
# ATX style H1
|
||||
|
||||
## ATX style H2
|
||||
```
|
||||
|
||||
The `setext_with_atx` and `setext_with_atx_closed` settings allow ATX-style
|
||||
headings of level 3 or more in documents with setext-style headings (which only
|
||||
support level 1 and 2 headings):
|
||||
|
||||
```markdown
|
||||
Setext style H1
|
||||
===============
|
||||
|
||||
Setext style H2
|
||||
---------------
|
||||
|
||||
### ATX style H3
|
||||
```
|
||||
|
||||
Note: The configured heading style can be a specific style to require (`atx`,
|
||||
`atx_closed`, `setext`, `setext_with_atx`, `setext_with_atx_closed`), or can
|
||||
require that all heading styles match the first heading style via `consistent`.
|
||||
|
||||
Note: The placement of a horizontal rule directly below a line of text can
|
||||
trigger this rule by turning that text into a level 2 setext-style heading:
|
||||
|
||||
```markdown
|
||||
A line of text followed by a horizontal rule becomes a heading
|
||||
---
|
||||
```
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# `MD004` - Unordered list style
|
||||
|
||||
Tags: `bullet`, `ul`
|
||||
|
||||
Aliases: `ul-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: List style (`string`, default `consistent`, values `asterisk` /
|
||||
`consistent` / `dash` / `plus` / `sublist`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when the symbols used in the document for unordered
|
||||
list items do not match the configured unordered list style:
|
||||
|
||||
```markdown
|
||||
* Item 1
|
||||
+ Item 2
|
||||
- Item 3
|
||||
```
|
||||
|
||||
To fix this issue, use the configured style for list items throughout the
|
||||
document:
|
||||
|
||||
```markdown
|
||||
* Item 1
|
||||
* Item 2
|
||||
* Item 3
|
||||
```
|
||||
|
||||
The configured list style can ensure all list styling is a specific symbol
|
||||
(`asterisk`, `plus`, `dash`), ensure each sublist has a consistent symbol that
|
||||
differs from its parent list (`sublist`), or ensure all list styles match the
|
||||
first list style (`consistent`).
|
||||
|
||||
For example, the following is valid for the `sublist` style because the
|
||||
outer-most indent uses asterisk, the middle indent uses plus, and the inner-most
|
||||
indent uses dash:
|
||||
|
||||
```markdown
|
||||
* Item 1
|
||||
+ Item 2
|
||||
- Item 3
|
||||
+ Item 4
|
||||
* Item 4
|
||||
+ Item 5
|
||||
```
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# `MD005` - Inconsistent indentation for list items at the same level
|
||||
|
||||
Tags: `bullet`, `indentation`, `ul`
|
||||
|
||||
Aliases: `list-indent`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when list items are parsed as being at the same level,
|
||||
but don't have the same indentation:
|
||||
|
||||
```markdown
|
||||
* Item 1
|
||||
* Nested Item 1
|
||||
* Nested Item 2
|
||||
* A misaligned item
|
||||
```
|
||||
|
||||
Usually, this rule will be triggered because of a typo. Correct the indentation
|
||||
for the list to fix it:
|
||||
|
||||
```markdown
|
||||
* Item 1
|
||||
* Nested Item 1
|
||||
* Nested Item 2
|
||||
* Nested Item 3
|
||||
```
|
||||
|
||||
Sequentially-ordered list markers are usually left-aligned such that all items
|
||||
have the same starting column:
|
||||
|
||||
```markdown
|
||||
...
|
||||
8. Item
|
||||
9. Item
|
||||
10. Item
|
||||
11. Item
|
||||
...
|
||||
```
|
||||
|
||||
This rule also supports right-alignment of list markers such that all items have
|
||||
the same ending column:
|
||||
|
||||
```markdown
|
||||
...
|
||||
8. Item
|
||||
9. Item
|
||||
10. Item
|
||||
11. Item
|
||||
...
|
||||
```
|
||||
|
||||
Rationale: Violations of this rule can lead to improperly rendered content.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# `MD007` - Unordered list indentation
|
||||
|
||||
Tags: `bullet`, `indentation`, `ul`
|
||||
|
||||
Aliases: `ul-indent`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `indent`: Spaces for indent (`integer`, default `2`)
|
||||
- `start_indent`: Spaces for first level indent (when start_indented is set)
|
||||
(`integer`, default `2`)
|
||||
- `start_indented`: Whether to indent the first level of the list (`boolean`,
|
||||
default `false`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when list items are not indented by the configured
|
||||
number of spaces (default: 2).
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
* List item
|
||||
* Nested list item indented by 3 spaces
|
||||
```
|
||||
|
||||
Corrected Example:
|
||||
|
||||
```markdown
|
||||
* List item
|
||||
* Nested list item indented by 2 spaces
|
||||
```
|
||||
|
||||
Note: This rule applies to a sublist only if its parent lists are all also
|
||||
unordered (otherwise, extra indentation of ordered lists interferes with the
|
||||
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 `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
|
||||
used after the list marker. Indenting by 4 spaces is consistent with code blocks
|
||||
and simpler for editors to implement. Additionally, this can be a compatibility
|
||||
issue for other Markdown parsers, which require 4-space indents. More
|
||||
information: [Markdown Style Guide][markdown-style-guide].
|
||||
|
||||
Note: See [Prettier.md](Prettier.md) for compatibility information.
|
||||
|
||||
[markdown-style-guide]: https://cirosantilli.com/markdown-style-guide#indentation-of-content-inside-lists
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# `MD009` - Trailing spaces
|
||||
|
||||
Tags: `whitespace`
|
||||
|
||||
Aliases: `no-trailing-spaces`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `br_spaces`: Spaces for line break (`integer`, default `2`)
|
||||
- `code_blocks`: Include code blocks (`boolean`, default `false`)
|
||||
- `list_item_empty_lines`: Allow spaces for empty lines in list items
|
||||
(`boolean`, default `false`)
|
||||
- `strict`: Include unnecessary breaks (`boolean`, default `false`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered on any lines that end with unexpected whitespace. To fix
|
||||
this, remove the trailing space from the end of the line.
|
||||
|
||||
The `br_spaces` parameter allows an exception to this rule for a specific number
|
||||
of trailing spaces, typically used to insert an explicit line break. The default
|
||||
value allows 2 spaces to indicate a hard break (\<br> element). (You must set
|
||||
`br_spaces` to a value >= 2 for this parameter to take effect. Setting
|
||||
`br_spaces` to 1 behaves the same as 0, disallowing any trailing spaces.)
|
||||
|
||||
By default, trailing space is allowed in indented and fenced code blocks because
|
||||
some programming languages require that. To report such instances, set the
|
||||
`code_blocks` parameter to `true`.
|
||||
|
||||
By default, this rule will not trigger when the allowed number of spaces is
|
||||
used, even when it doesn't create a hard break (for example, at the end of a
|
||||
paragraph). To report such instances, set the `strict` parameter to `true`.
|
||||
|
||||
```markdown
|
||||
Text text text
|
||||
text[2 spaces]
|
||||
```
|
||||
|
||||
Using spaces to indent blank lines inside a list item is usually not necessary,
|
||||
but some parsers require it. Set the `list_item_empty_lines` parameter to `true`
|
||||
to allow this (even when `strict` is `true`):
|
||||
|
||||
```markdown
|
||||
- list item text
|
||||
[2 spaces]
|
||||
list item text
|
||||
```
|
||||
|
||||
Rationale: Except when being used to create a line break, trailing whitespace
|
||||
has no purpose and does not affect the rendering of content.
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# `MD010` - Hard tabs
|
||||
|
||||
Tags: `hard_tab`, `whitespace`
|
||||
|
||||
Aliases: `no-hard-tabs`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `code_blocks`: Include code blocks (`boolean`, default `true`)
|
||||
- `ignore_code_languages`: Fenced code languages to ignore (`string[]`, default
|
||||
`[]`)
|
||||
- `spaces_per_tab`: Number of spaces for each hard tab (`integer`, default `1`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered by any lines that contain hard tab characters instead
|
||||
of using spaces for indentation. To fix this, replace any hard tab characters
|
||||
with spaces instead.
|
||||
|
||||
Example:
|
||||
|
||||
<!-- markdownlint-disable no-hard-tabs -->
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
|
||||
* hard tab character used to indent the list item
|
||||
```
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
Corrected example:
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
|
||||
* Spaces used to indent the list item instead
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
When code blocks are scanned (e.g., by default or if `code_blocks` is `true`),
|
||||
the `ignore_code_languages` parameter can be set to a list of languages that
|
||||
should be ignored (i.e., hard tabs will be allowed, though not required). This
|
||||
makes it easier for documents to include code for languages that require hard
|
||||
tabs.
|
||||
|
||||
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.
|
||||
|
||||
More information:
|
||||
|
||||
- <https://agiletribe.wordpress.com/2011/10/27/18-dont-use-tab-characters/>
|
||||
- <https://www.jwz.org/doc/tabs-vs-spaces.html>
|
||||
- <https://adamspiers.org/computing/why_no_tabs.html>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# `MD011` - Reversed link syntax
|
||||
|
||||
Tags: `links`
|
||||
|
||||
Aliases: `no-reversed-links`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when text that appears to be a link is encountered, but
|
||||
where the syntax appears to have been reversed (the `[]` and `()` are
|
||||
reversed):
|
||||
|
||||
```markdown
|
||||
(Incorrect link syntax)[https://www.example.com/]
|
||||
```
|
||||
|
||||
To fix this, swap the `[]` and `()` around:
|
||||
|
||||
```markdown
|
||||
[Correct link syntax](https://www.example.com/)
|
||||
```
|
||||
|
||||
Note: [Markdown Extra](https://en.wikipedia.org/wiki/Markdown_Extra)-style
|
||||
footnotes do not trigger this rule:
|
||||
|
||||
```markdown
|
||||
For (example)[^1]
|
||||
```
|
||||
|
||||
Rationale: Reversed links are not rendered as usable links.
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# `MD012` - Multiple consecutive blank lines
|
||||
|
||||
Tags: `blank_lines`, `whitespace`
|
||||
|
||||
Aliases: `no-multiple-blanks`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `maximum`: Consecutive blank lines (`integer`, default `1`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when there are multiple consecutive blank lines in the
|
||||
document:
|
||||
|
||||
```markdown
|
||||
Some text here
|
||||
|
||||
|
||||
Some more text here
|
||||
```
|
||||
|
||||
To fix this, delete the offending lines:
|
||||
|
||||
```markdown
|
||||
Some text here
|
||||
|
||||
Some more text here
|
||||
```
|
||||
|
||||
Note: this rule will not be triggered if there are multiple consecutive blank
|
||||
lines inside code blocks.
|
||||
|
||||
Note: The `maximum` parameter can be used to configure the maximum number of
|
||||
consecutive blank lines.
|
||||
|
||||
Rationale: Except in a code block, blank lines serve no purpose and do not
|
||||
affect the rendering of content.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# `MD013` - Line length
|
||||
|
||||
Tags: `line_length`
|
||||
|
||||
Aliases: `line-length`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `code_block_line_length`: Number of characters for code blocks (`integer`,
|
||||
default `80`)
|
||||
- `code_blocks`: Include code blocks (`boolean`, default `true`)
|
||||
- `heading_line_length`: Number of characters for headings (`integer`, default
|
||||
`80`)
|
||||
- `headings`: Include headings (`boolean`, default `true`)
|
||||
- `line_length`: Number of characters (`integer`, default `80`)
|
||||
- `stern`: Stern length checking (`boolean`, default `false`)
|
||||
- `strict`: Strict length checking (`boolean`, default `false`)
|
||||
- `tables`: Include tables (`boolean`, default `true`)
|
||||
|
||||
This rule is triggered when there are lines that are longer than the
|
||||
configured `line_length` (default: 80 characters). To fix this, split the line
|
||||
up into multiple lines. To set a different maximum length for headings, use
|
||||
`heading_line_length`. To set a different maximum length for code blocks, use
|
||||
`code_block_line_length`
|
||||
|
||||
This rule has an exception when there is no whitespace beyond the configured
|
||||
line length. This allows you to include items such as long URLs without being
|
||||
forced to break them in the middle. To disable this exception, set the `strict`
|
||||
parameter to `true` and an issue will be reported when any line is too long. To
|
||||
warn for lines that are too long and could be fixed but allow long lines
|
||||
without spaces, set the `stern` parameter to `true`.
|
||||
|
||||
For example (assuming normal behavior):
|
||||
|
||||
```markdown
|
||||
IF THIS LINE IS THE MAXIMUM LENGTH
|
||||
This line is okay because there are-no-spaces-beyond-that-length
|
||||
This line is a violation because there are spaces beyond that length
|
||||
This-line-is-okay-because-there-are-no-spaces-anywhere-within
|
||||
```
|
||||
|
||||
In `strict` mode, the last three lines above are all violations. In `stern`
|
||||
mode, the middle two lines above are both violations, but the last is okay.
|
||||
|
||||
You have the option to exclude this rule for code blocks, tables, or headings.
|
||||
To do so, set the `code_blocks`, `tables`, or `headings` parameter(s) to false.
|
||||
|
||||
Code blocks are included in this rule by default since it is often a
|
||||
requirement for document readability, and tentatively compatible with code
|
||||
rules. Still, some languages do not lend themselves to short lines.
|
||||
|
||||
Lines with link/image reference definitions and standalone lines (i.e., not part
|
||||
of a paragraph) with only a link/image (possibly using (strong) emphasis) are
|
||||
always exempted from this rule (even in `strict` mode) because there is often no
|
||||
way to split such lines without breaking the URL.
|
||||
|
||||
Rationale: Extremely long lines can be difficult to work with in some editors.
|
||||
More information: <https://cirosantilli.com/markdown-style-guide#line-wrapping>.
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# `MD014` - Dollar signs used before commands without showing output
|
||||
|
||||
Tags: `code`
|
||||
|
||||
Aliases: `commands-show-output`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when there are code blocks showing shell commands to be
|
||||
typed, and *all* of the shell commands are preceded by dollar signs ($):
|
||||
|
||||
<!-- markdownlint-disable commands-show-output -->
|
||||
|
||||
```markdown
|
||||
$ ls
|
||||
$ cat foo
|
||||
$ less bar
|
||||
```
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
The dollar signs are unnecessary in this situation, and should not be
|
||||
included:
|
||||
|
||||
```markdown
|
||||
ls
|
||||
cat foo
|
||||
less bar
|
||||
```
|
||||
|
||||
Showing output for commands preceded by dollar signs does not trigger this rule:
|
||||
|
||||
```markdown
|
||||
$ ls
|
||||
foo bar
|
||||
$ cat foo
|
||||
Hello world
|
||||
$ cat bar
|
||||
baz
|
||||
```
|
||||
|
||||
Because some commands do not produce output, it is not a violation if *some*
|
||||
commands do not have output:
|
||||
|
||||
```markdown
|
||||
$ mkdir test
|
||||
mkdir: created directory 'test'
|
||||
$ ls test
|
||||
```
|
||||
|
||||
Rationale: It is easier to copy/paste and less noisy if the dollar signs
|
||||
are omitted when they are not needed. See
|
||||
<https://cirosantilli.com/markdown-style-guide#dollar-signs-in-shell-code>
|
||||
for more information.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# `MD018` - No space after hash on atx style heading
|
||||
|
||||
Tags: `atx`, `headings`, `spaces`
|
||||
|
||||
Aliases: `no-missing-space-atx`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when spaces are missing after the hash characters
|
||||
in an atx style heading:
|
||||
|
||||
```markdown
|
||||
#Heading 1
|
||||
|
||||
##Heading 2
|
||||
```
|
||||
|
||||
To fix this, separate the heading text from the hash character by a single
|
||||
space:
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
```
|
||||
|
||||
Rationale: Violations of this rule can lead to improperly rendered content.
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# `MD019` - Multiple spaces after hash on atx style heading
|
||||
|
||||
Tags: `atx`, `headings`, `spaces`
|
||||
|
||||
Aliases: `no-multiple-space-atx`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when more than one space is used to separate the
|
||||
heading text from the hash characters in an atx style heading:
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
```
|
||||
|
||||
To fix this, separate the heading text from the hash character by a single
|
||||
space:
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
```
|
||||
|
||||
Rationale: Extra space has no purpose and does not affect the rendering of
|
||||
content.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# `MD020` - No space inside hashes on closed atx style heading
|
||||
|
||||
Tags: `atx_closed`, `headings`, `spaces`
|
||||
|
||||
Aliases: `no-missing-space-closed-atx`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when spaces are missing inside the hash characters
|
||||
in a closed atx style heading:
|
||||
|
||||
```markdown
|
||||
#Heading 1#
|
||||
|
||||
##Heading 2##
|
||||
```
|
||||
|
||||
To fix this, separate the heading text from the hash character by a single
|
||||
space:
|
||||
|
||||
```markdown
|
||||
# Heading 1 #
|
||||
|
||||
## Heading 2 ##
|
||||
```
|
||||
|
||||
Note: this rule will fire if either side of the heading is missing spaces.
|
||||
|
||||
Rationale: Violations of this rule can lead to improperly rendered content.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# `MD021` - Multiple spaces inside hashes on closed atx style heading
|
||||
|
||||
Tags: `atx_closed`, `headings`, `spaces`
|
||||
|
||||
Aliases: `no-multiple-space-closed-atx`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when more than one space is used to separate the
|
||||
heading text from the hash characters in a closed atx style heading:
|
||||
|
||||
```markdown
|
||||
# Heading 1 #
|
||||
|
||||
## Heading 2 ##
|
||||
```
|
||||
|
||||
To fix this, separate the heading text from the hash character by a single
|
||||
space:
|
||||
|
||||
```markdown
|
||||
# Heading 1 #
|
||||
|
||||
## Heading 2 ##
|
||||
```
|
||||
|
||||
Note: this rule will fire if either side of the heading contains multiple
|
||||
spaces.
|
||||
|
||||
Rationale: Extra space has no purpose and does not affect the rendering of
|
||||
content.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# `MD022` - Headings should be surrounded by blank lines
|
||||
|
||||
Tags: `blank_lines`, `headings`
|
||||
|
||||
Aliases: `blanks-around-headings`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `lines_above`: Blank lines above heading (`integer|integer[]`, default `1`)
|
||||
- `lines_below`: Blank lines below heading (`integer|integer[]`, default `1`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when headings (any style) are either not preceded or not
|
||||
followed by at least one blank line:
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
Some text
|
||||
|
||||
Some more text
|
||||
## Heading 2
|
||||
```
|
||||
|
||||
To fix this, ensure that all headings have a blank line both before and after
|
||||
(except where the heading is at the beginning or end of the document):
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
|
||||
Some text
|
||||
|
||||
Some more text
|
||||
|
||||
## Heading 2
|
||||
```
|
||||
|
||||
The `lines_above` and `lines_below` parameters can be used to specify a
|
||||
different number of blank lines (including `0`) above or below each heading.
|
||||
If the value `-1` is used for either parameter, any number of blank lines is
|
||||
allowed. To customize the number of lines above or below each heading level
|
||||
individually, specify a `number[]` where values correspond to heading levels
|
||||
1-6 (in order).
|
||||
|
||||
Notes: If `lines_above` or `lines_below` are configured to require more than one
|
||||
blank line, [MD012/no-multiple-blanks](md012.md) should also be customized. This
|
||||
rule checks for *at least* as many blank lines as specified; any extra blank
|
||||
lines are ignored.
|
||||
|
||||
Rationale: Aside from aesthetic reasons, some parsers, including `kramdown`,
|
||||
will not parse headings that don't have a blank line before, and will parse them
|
||||
as regular text.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# `MD023` - Headings must start at the beginning of the line
|
||||
|
||||
Tags: `headings`, `spaces`
|
||||
|
||||
Aliases: `heading-start-left`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when a heading is indented by one or more spaces:
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
|
||||
# Indented heading
|
||||
```
|
||||
|
||||
To fix this, ensure that all headings start at the beginning of the line:
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
|
||||
# Heading
|
||||
```
|
||||
|
||||
Note that scenarios like block quotes "indent" the start of the line, so the
|
||||
following is also correct:
|
||||
|
||||
```markdown
|
||||
> # Heading in Block Quote
|
||||
```
|
||||
|
||||
Rationale: Headings that don't start at the beginning of the line will not be
|
||||
parsed as headings, and will instead appear as regular text.
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# `MD024` - Multiple headings with the same content
|
||||
|
||||
Tags: `headings`
|
||||
|
||||
Aliases: `no-duplicate-heading`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `siblings_only`: Only check sibling headings (`boolean`, default `false`)
|
||||
|
||||
This rule is triggered if there are multiple headings in the document that have
|
||||
the same text:
|
||||
|
||||
```markdown
|
||||
# Some text
|
||||
|
||||
## Some text
|
||||
```
|
||||
|
||||
To fix this, ensure that the content of each heading is different:
|
||||
|
||||
```markdown
|
||||
# Some text
|
||||
|
||||
## Some more text
|
||||
```
|
||||
|
||||
If the parameter `siblings_only` is set to `true`, duplication is allowed for
|
||||
headings with different parents (as is common in changelogs):
|
||||
|
||||
```markdown
|
||||
# Change log
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Features
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### Features
|
||||
```
|
||||
|
||||
Rationale: Some Markdown parsers generate anchors for headings based on the
|
||||
heading name; headings with the same content can cause problems with that.
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# `MD025` - Multiple top-level headings in the same document
|
||||
|
||||
Tags: `headings`
|
||||
|
||||
Aliases: `single-h1`, `single-title`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `front_matter_title`: RegExp for matching title in front matter (`string`,
|
||||
default `^\s*title\s*[:=]`)
|
||||
- `level`: Heading level (`integer`, default `1`)
|
||||
|
||||
This rule is triggered when a top-level heading is in use (the first line of
|
||||
the file is an h1 heading), and more than one h1 heading is in use in the
|
||||
document:
|
||||
|
||||
```markdown
|
||||
# Top level heading
|
||||
|
||||
# Another top-level heading
|
||||
```
|
||||
|
||||
To fix, structure your document so there is a single h1 heading that is
|
||||
the title for the document. Subsequent headings must be
|
||||
lower-level headings (h2, h3, etc.):
|
||||
|
||||
```markdown
|
||||
# Title
|
||||
|
||||
## Heading
|
||||
|
||||
## Another heading
|
||||
```
|
||||
|
||||
Note: The `level` parameter can be used to change the top-level (ex: to h2) in
|
||||
cases where an h1 is added externally.
|
||||
|
||||
If [YAML](https://en.wikipedia.org/wiki/YAML) front matter is present and
|
||||
contains a `title` property (commonly used with blog posts), this rule treats
|
||||
that as a top level heading and will report a violation for any subsequent
|
||||
top-level headings. To use a different property name in the front matter,
|
||||
specify the text of a regular expression via the `front_matter_title` parameter.
|
||||
To disable the use of front matter by this rule, specify `""` for
|
||||
`front_matter_title`.
|
||||
|
||||
Rationale: A top-level heading is an h1 on the first line of the file, and
|
||||
serves as the title for the document. If this convention is in use, then there
|
||||
can not be more than one title for the document, and the entire document should
|
||||
be contained within this heading.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# `MD026` - Trailing punctuation in heading
|
||||
|
||||
Tags: `headings`
|
||||
|
||||
Aliases: `no-trailing-punctuation`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `punctuation`: Punctuation characters (`string`, default `.,;:!。,;:!`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered on any heading that has one of the specified normal or
|
||||
full-width punctuation characters as the last character in the line:
|
||||
|
||||
```markdown
|
||||
# This is a heading.
|
||||
```
|
||||
|
||||
To fix this, remove the trailing punctuation:
|
||||
|
||||
```markdown
|
||||
# This is a heading
|
||||
```
|
||||
|
||||
Note: The `punctuation` parameter can be used to specify what characters count
|
||||
as punctuation at the end of a heading. For example, you can change it to
|
||||
`".,;:"` to allow headings that end with an exclamation point. `?` is
|
||||
allowed by default because of how common it is in headings of FAQ-style
|
||||
documents. Setting the `punctuation` parameter to `""` allows all characters -
|
||||
and is equivalent to disabling the rule.
|
||||
|
||||
Note: The trailing semicolon of [HTML entity references][html-entity-references]
|
||||
like `©`, `©`, and `©` is ignored by this rule.
|
||||
|
||||
Rationale: Headings are not meant to be full sentences. More information:
|
||||
[Punctuation at the end of headers][end-punctuation].
|
||||
|
||||
[end-punctuation]: https://cirosantilli.com/markdown-style-guide#punctuation-at-the-end-of-headers
|
||||
[html-entity-references]: https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# `MD027` - Multiple spaces after blockquote symbol
|
||||
|
||||
Tags: `blockquote`, `indentation`, `whitespace`
|
||||
|
||||
Aliases: `no-multiple-space-blockquote`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `list_items`: Include list items (`boolean`, default `true`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when blockquotes have more than one space after the
|
||||
blockquote (`>`) symbol:
|
||||
|
||||
```markdown
|
||||
> This is a blockquote with bad indentation
|
||||
> there should only be one.
|
||||
```
|
||||
|
||||
To fix, remove any extraneous space:
|
||||
|
||||
```markdown
|
||||
> This is a blockquote with correct
|
||||
> indentation.
|
||||
```
|
||||
|
||||
Inferring intended list indentation within a blockquote can be challenging;
|
||||
setting the `list_items` parameter to `false` disables this rule for ordered
|
||||
and unordered list items.
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# `MD028` - Blank line inside blockquote
|
||||
|
||||
Tags: `blockquote`, `whitespace`
|
||||
|
||||
Aliases: `no-blanks-blockquote`
|
||||
|
||||
This rule is triggered when two blockquote blocks are separated by nothing
|
||||
except for a blank line:
|
||||
|
||||
```markdown
|
||||
> This is a blockquote
|
||||
> which is immediately followed by
|
||||
|
||||
> this blockquote. Unfortunately
|
||||
> In some parsers, these are treated as the same blockquote.
|
||||
```
|
||||
|
||||
To fix this, ensure that any blockquotes that are right next to each other
|
||||
have some text in between:
|
||||
|
||||
```markdown
|
||||
> This is a blockquote.
|
||||
|
||||
And Jimmy also said:
|
||||
|
||||
> This too is a blockquote.
|
||||
```
|
||||
|
||||
Alternatively, if they are supposed to be the same quote, then add the
|
||||
blockquote symbol at the beginning of the blank line:
|
||||
|
||||
```markdown
|
||||
> This is a blockquote.
|
||||
>
|
||||
> This is the same blockquote.
|
||||
```
|
||||
|
||||
Rationale: Some Markdown parsers will treat two blockquotes separated by one
|
||||
or more blank lines as the same blockquote, while others will treat them as
|
||||
separate blockquotes.
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# `MD029` - Ordered list item prefix
|
||||
|
||||
Tags: `ol`
|
||||
|
||||
Aliases: `ol-prefix`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: List style (`string`, default `one_or_ordered`, values `one` /
|
||||
`one_or_ordered` / `ordered` / `zero`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered for ordered lists that do not either start with '1.' or
|
||||
do not have a prefix that increases in numerical order (depending on the
|
||||
configured style). The less-common pattern of using '0.' as a first prefix or
|
||||
for all prefixes is also supported.
|
||||
|
||||
Example valid list if the style is configured as 'one':
|
||||
|
||||
```markdown
|
||||
1. Do this.
|
||||
1. Do that.
|
||||
1. Done.
|
||||
```
|
||||
|
||||
Examples of valid lists if the style is configured as 'ordered':
|
||||
|
||||
```markdown
|
||||
1. Do this.
|
||||
2. Do that.
|
||||
3. Done.
|
||||
```
|
||||
|
||||
```markdown
|
||||
0. Do this.
|
||||
1. Do that.
|
||||
2. Done.
|
||||
```
|
||||
|
||||
All three examples are valid when the style is configured as 'one_or_ordered'.
|
||||
|
||||
Example valid list if the style is configured as 'zero':
|
||||
|
||||
```markdown
|
||||
0. Do this.
|
||||
0. Do that.
|
||||
0. Done.
|
||||
```
|
||||
|
||||
Example invalid list for all styles:
|
||||
|
||||
```markdown
|
||||
1. Do this.
|
||||
3. Done.
|
||||
```
|
||||
|
||||
This rule supports 0-prefixing ordered list items for uniform indentation:
|
||||
|
||||
```markdown
|
||||
...
|
||||
08. Item
|
||||
09. Item
|
||||
10. Item
|
||||
11. Item
|
||||
...
|
||||
```
|
||||
|
||||
Note: This rule will report violations for cases like the following where an
|
||||
improperly-indented code block (or similar) appears between two list items and
|
||||
"breaks" the list in two:
|
||||
|
||||
<!-- markdownlint-disable code-fence-style -->
|
||||
|
||||
~~~markdown
|
||||
1. First list
|
||||
|
||||
```text
|
||||
Code block
|
||||
```
|
||||
|
||||
1. Second list
|
||||
~~~
|
||||
|
||||
The fix is to indent the code block so it becomes part of the preceding list
|
||||
item as intended:
|
||||
|
||||
~~~markdown
|
||||
1. First list
|
||||
|
||||
```text
|
||||
Code block
|
||||
```
|
||||
|
||||
2. Still first list
|
||||
~~~
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
# `MD030` - Spaces after list markers
|
||||
|
||||
Tags: `ol`, `ul`, `whitespace`
|
||||
|
||||
Aliases: `list-marker-space`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `ol_multi`: Spaces for multi-line ordered list items (`integer`, default `1`)
|
||||
- `ol_single`: Spaces for single-line ordered list items (`integer`, default
|
||||
`1`)
|
||||
- `ul_multi`: Spaces for multi-line unordered list items (`integer`, default
|
||||
`1`)
|
||||
- `ul_single`: Spaces for single-line unordered list items (`integer`, default
|
||||
`1`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule checks for the number of spaces between a list marker (e.g. '`-`',
|
||||
'`*`', '`+`' or '`1.`') and the text of the list item.
|
||||
|
||||
The number of spaces checked for depends on the document style in use, but the
|
||||
default is 1 space after any list marker:
|
||||
|
||||
```markdown
|
||||
* Foo
|
||||
* Bar
|
||||
* Baz
|
||||
|
||||
1. Foo
|
||||
1. Bar
|
||||
1. Baz
|
||||
|
||||
1. Foo
|
||||
* Bar
|
||||
1. Baz
|
||||
```
|
||||
|
||||
A document style may change the number of spaces after unordered list items
|
||||
and ordered list items independently, as well as based on whether the content
|
||||
of every item in the list consists of a single paragraph or multiple
|
||||
paragraphs (including sub-lists and code blocks).
|
||||
|
||||
For example, the style guide at
|
||||
<https://cirosantilli.com/markdown-style-guide#spaces-after-list-marker>
|
||||
specifies that 1 space after the list marker should be used if every item in
|
||||
the list fits within a single paragraph, but to use 2 or 3 spaces (for ordered
|
||||
and unordered lists respectively) if there are multiple paragraphs of content
|
||||
inside the list:
|
||||
|
||||
```markdown
|
||||
* Foo
|
||||
* Bar
|
||||
* Baz
|
||||
```
|
||||
|
||||
vs.
|
||||
|
||||
```markdown
|
||||
* Foo
|
||||
|
||||
Second paragraph
|
||||
|
||||
* Bar
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```markdown
|
||||
1. Foo
|
||||
|
||||
Second paragraph
|
||||
|
||||
1. Bar
|
||||
```
|
||||
|
||||
To fix this, ensure the correct number of spaces are used after the list marker
|
||||
for your selected document style.
|
||||
|
||||
Rationale: Violations of this rule can lead to improperly rendered content.
|
||||
|
||||
Note: See [Prettier.md](Prettier.md) for compatibility information.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# `MD031` - Fenced code blocks should be surrounded by blank lines
|
||||
|
||||
Tags: `blank_lines`, `code`
|
||||
|
||||
Aliases: `blanks-around-fences`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `list_items`: Include list items (`boolean`, default `true`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when fenced code blocks are either not preceded or not
|
||||
followed by a blank line:
|
||||
|
||||
````markdown
|
||||
Some text
|
||||
```
|
||||
Code block
|
||||
```
|
||||
|
||||
```
|
||||
Another code block
|
||||
```
|
||||
Some more text
|
||||
````
|
||||
|
||||
To fix this, ensure that all fenced code blocks have a blank line both before
|
||||
and after (except where the block is at the beginning or end of the document):
|
||||
|
||||
````markdown
|
||||
Some text
|
||||
|
||||
```
|
||||
Code block
|
||||
```
|
||||
|
||||
```
|
||||
Another code block
|
||||
```
|
||||
|
||||
Some more text
|
||||
````
|
||||
|
||||
Set the `list_items` parameter to `false` to disable this rule for list items.
|
||||
Disabling this behavior for lists can be useful if it is necessary to create a
|
||||
[tight](https://spec.commonmark.org/0.29/#tight) list containing a code fence.
|
||||
|
||||
Rationale: Aside from aesthetic reasons, some parsers, including kramdown, will
|
||||
not parse fenced code blocks that don't have blank lines before and after them.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# `MD032` - Lists should be surrounded by blank lines
|
||||
|
||||
Tags: `blank_lines`, `bullet`, `ol`, `ul`
|
||||
|
||||
Aliases: `blanks-around-lists`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when lists (of any kind) are either not preceded or not
|
||||
followed by a blank line:
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
* List item
|
||||
* List item
|
||||
|
||||
1. List item
|
||||
2. List item
|
||||
***
|
||||
```
|
||||
|
||||
In the first case above, text immediately precedes the unordered list. In the
|
||||
second case above, a thematic break immediately follows the ordered list. To fix
|
||||
violations of this rule, ensure that all lists have a blank line both before and
|
||||
after (except when the list is at the very beginning or end of the document):
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
|
||||
* List item
|
||||
* List item
|
||||
|
||||
1. List item
|
||||
2. List item
|
||||
|
||||
***
|
||||
```
|
||||
|
||||
Note that the following case is **not** a violation of this rule:
|
||||
|
||||
```markdown
|
||||
1. List item
|
||||
More item 1
|
||||
2. List item
|
||||
More item 2
|
||||
```
|
||||
|
||||
Although it is not indented, the text "More item 2" is referred to as a
|
||||
[lazy continuation line][lazy-continuation] and considered part of the second
|
||||
list item.
|
||||
|
||||
Rationale: In addition to aesthetic reasons, some parsers, including kramdown,
|
||||
will not parse lists that don't have blank lines before and after them.
|
||||
|
||||
[lazy-continuation]: https://spec.commonmark.org/0.30/#lazy-continuation-line
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# `MD033` - Inline HTML
|
||||
|
||||
Tags: `html`
|
||||
|
||||
Aliases: `no-inline-html`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `allowed_elements`: Allowed elements (`string[]`, default `[]`)
|
||||
- `table_allowed_elements`: Allowed elements in tables (`string[]`, default
|
||||
`[]`)
|
||||
|
||||
This rule is triggered whenever raw HTML is used in a Markdown document:
|
||||
|
||||
```markdown
|
||||
<h1>Inline HTML heading</h1>
|
||||
```
|
||||
|
||||
To fix this, use 'pure' Markdown instead of including raw HTML:
|
||||
|
||||
```markdown
|
||||
# Markdown heading
|
||||
```
|
||||
|
||||
To allow specific HTML elements anywhere in Markdown content, set the
|
||||
`allowed_elements` parameter to a list of HTML element names. To allow a
|
||||
specific set of HTML elements within Markdown tables, set the
|
||||
`table_allowed_elements` parameter to a list of HTML element names. This can be
|
||||
used to permit the use of `<br>`-style line breaks only within Markdown tables.
|
||||
|
||||
Rationale: Raw HTML is allowed in Markdown, but this rule is included for
|
||||
those who want their documents to only include "pure" Markdown, or for those
|
||||
who are rendering Markdown documents into something other than HTML.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# `MD034` - Bare URL used
|
||||
|
||||
Tags: `links`, `url`
|
||||
|
||||
Aliases: `no-bare-urls`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered whenever a URL or email address appears without
|
||||
surrounding angle brackets:
|
||||
|
||||
```markdown
|
||||
For more info, visit https://www.example.com/ or email user@example.com.
|
||||
```
|
||||
|
||||
To fix this, add angle brackets around the URL or email address:
|
||||
|
||||
```markdown
|
||||
For more info, visit <https://www.example.com/> or email <user@example.com>.
|
||||
```
|
||||
|
||||
If a URL or email address contains non-ASCII characters, it may be not be
|
||||
handled as intended even when angle brackets are present. In such cases,
|
||||
[percent-encoding](https://en.m.wikipedia.org/wiki/Percent-encoding) can be used
|
||||
to comply with the required syntax for URL and email.
|
||||
|
||||
Note: To include a bare URL or email without it being converted into a link,
|
||||
wrap it in a code span:
|
||||
|
||||
```markdown
|
||||
Not a clickable link: `https://www.example.com`
|
||||
```
|
||||
|
||||
Note: The following scenario does not trigger this rule because it could be a
|
||||
shortcut link:
|
||||
|
||||
```markdown
|
||||
[https://www.example.com]
|
||||
```
|
||||
|
||||
Note: The following syntax triggers this rule because the nested link could be
|
||||
a shortcut link (which takes precedence):
|
||||
|
||||
```markdown
|
||||
[text [shortcut] text](https://example.com)
|
||||
```
|
||||
|
||||
To avoid this, escape both inner brackets:
|
||||
|
||||
```markdown
|
||||
[link \[text\] link](https://example.com)
|
||||
```
|
||||
|
||||
Rationale: Without angle brackets, a bare URL or email isn't converted into a
|
||||
link by some Markdown parsers.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# `MD035` - Horizontal rule style
|
||||
|
||||
Tags: `hr`
|
||||
|
||||
Aliases: `hr-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: Horizontal rule style (`string`, default `consistent`)
|
||||
|
||||
This rule is triggered when inconsistent styles of horizontal rules are used
|
||||
in the document:
|
||||
|
||||
```markdown
|
||||
---
|
||||
|
||||
- - -
|
||||
|
||||
***
|
||||
|
||||
* * *
|
||||
|
||||
****
|
||||
```
|
||||
|
||||
To fix this, use the same horizontal rule everywhere:
|
||||
|
||||
```markdown
|
||||
---
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
The configured style can ensure all horizontal rules use a specific string or it
|
||||
can ensure all horizontal rules match the first horizontal rule (`consistent`).
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# `MD036` - Emphasis used instead of a heading
|
||||
|
||||
Tags: `emphasis`, `headings`
|
||||
|
||||
Aliases: `no-emphasis-as-heading`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `punctuation`: Punctuation characters (`string`, default `.,;:!?。,;:!?`)
|
||||
|
||||
This check looks for instances where emphasized (i.e. bold or italic) text is
|
||||
used to separate sections, where a heading should be used instead:
|
||||
|
||||
```markdown
|
||||
**My document**
|
||||
|
||||
Lorem ipsum dolor sit amet...
|
||||
|
||||
_Another section_
|
||||
|
||||
Consectetur adipiscing elit, sed do eiusmod.
|
||||
```
|
||||
|
||||
To fix this, use Markdown headings instead of emphasized text to denote
|
||||
sections:
|
||||
|
||||
```markdown
|
||||
# My document
|
||||
|
||||
Lorem ipsum dolor sit amet...
|
||||
|
||||
## Another section
|
||||
|
||||
Consectetur adipiscing elit, sed do eiusmod.
|
||||
```
|
||||
|
||||
Note: This rule looks for single-line paragraphs that consist entirely
|
||||
of emphasized text. It won't fire on emphasis used within regular text,
|
||||
multi-line emphasized paragraphs, or paragraphs ending in punctuation
|
||||
(normal or full-width). Similarly to rule MD026, you can configure what
|
||||
characters are recognized as punctuation.
|
||||
|
||||
Rationale: Using emphasis instead of a heading prevents tools from inferring
|
||||
the structure of a document. More information:
|
||||
<https://cirosantilli.com/markdown-style-guide#emphasis-vs-headers>.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# `MD037` - Spaces inside emphasis markers
|
||||
|
||||
Tags: `emphasis`, `whitespace`
|
||||
|
||||
Aliases: `no-space-in-emphasis`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when emphasis markers (bold, italic) are used, but they
|
||||
have spaces between the markers and the text:
|
||||
|
||||
```markdown
|
||||
Here is some ** bold ** text.
|
||||
|
||||
Here is some * italic * text.
|
||||
|
||||
Here is some more __ bold __ text.
|
||||
|
||||
Here is some more _ italic _ text.
|
||||
```
|
||||
|
||||
To fix this, remove the spaces around the emphasis markers:
|
||||
|
||||
```markdown
|
||||
Here is some **bold** text.
|
||||
|
||||
Here is some *italic* text.
|
||||
|
||||
Here is some more __bold__ text.
|
||||
|
||||
Here is some more _italic_ text.
|
||||
```
|
||||
|
||||
Rationale: Emphasis is only parsed as such when the asterisks/underscores
|
||||
aren't surrounded by spaces. This rule attempts to detect where
|
||||
they were surrounded by spaces, but it appears that emphasized text was
|
||||
intended by the author.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# `MD038` - Spaces inside code span elements
|
||||
|
||||
Tags: `code`, `whitespace`
|
||||
|
||||
Aliases: `no-space-in-code`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered for code spans containing content with unnecessary space
|
||||
next to the beginning or ending backticks:
|
||||
|
||||
```markdown
|
||||
`some text `
|
||||
|
||||
` some text`
|
||||
|
||||
` some text `
|
||||
```
|
||||
|
||||
To fix this, remove the extra space characters from the beginning and ending:
|
||||
|
||||
```markdown
|
||||
`some text`
|
||||
```
|
||||
|
||||
Note: A single leading *and* trailing space is allowed by the specification and
|
||||
trimmed by the parser to support code spans that begin or end with a backtick:
|
||||
|
||||
```markdown
|
||||
`` `backticks` ``
|
||||
|
||||
`` backtick` ``
|
||||
```
|
||||
|
||||
Note: When single-space padding is present in the input, it will be preserved
|
||||
(even if unnecessary):
|
||||
|
||||
```markdown
|
||||
` code `
|
||||
```
|
||||
|
||||
Note: Code spans containing only spaces are allowed by the specification and are
|
||||
also preserved:
|
||||
|
||||
```markdown
|
||||
` `
|
||||
|
||||
` `
|
||||
```
|
||||
|
||||
Rationale: Violations of this rule are usually unintentional and can lead to
|
||||
improperly-rendered content.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# `MD039` - Spaces inside link text
|
||||
|
||||
Tags: `links`, `whitespace`
|
||||
|
||||
Aliases: `no-space-in-links`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered on links that have spaces surrounding the link text:
|
||||
|
||||
```markdown
|
||||
[ a link ](https://www.example.com/)
|
||||
```
|
||||
|
||||
To fix this, remove the spaces surrounding the link text:
|
||||
|
||||
```markdown
|
||||
[a link](https://www.example.com/)
|
||||
```
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# `MD040` - Fenced code blocks should have a language specified
|
||||
|
||||
Tags: `code`, `language`
|
||||
|
||||
Aliases: `fenced-code-language`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `allowed_languages`: List of languages (`string[]`, default `[]`)
|
||||
- `language_only`: Require language only (`boolean`, default `false`)
|
||||
|
||||
This rule is triggered when fenced code blocks are used, but a language isn't
|
||||
specified:
|
||||
|
||||
````markdown
|
||||
```
|
||||
#!/bin/bash
|
||||
echo Hello world
|
||||
```
|
||||
````
|
||||
|
||||
To fix this, add a language specifier to the code block:
|
||||
|
||||
````markdown
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo Hello world
|
||||
```
|
||||
````
|
||||
|
||||
To display a code block without syntax highlighting, use:
|
||||
|
||||
````markdown
|
||||
```text
|
||||
Plain text in a code block
|
||||
```
|
||||
````
|
||||
|
||||
You can configure the `allowed_languages` parameter to specify a list of
|
||||
languages code blocks could use. Languages are case sensitive. The default value
|
||||
is `[]` which means any language specifier is valid.
|
||||
|
||||
You can prevent extra data from being present in the info string of fenced code
|
||||
blocks. To do so, set the `language_only` parameter to `true`.
|
||||
|
||||
<!-- markdownlint-disable-next-line no-space-in-code -->
|
||||
Info strings with leading/trailing whitespace (ex: `js `) or other content (ex:
|
||||
`ruby startline=3`) will trigger this rule.
|
||||
|
||||
Rationale: Specifying a language improves content rendering by using the
|
||||
correct syntax highlighting for code. More information:
|
||||
<https://cirosantilli.com/markdown-style-guide#option-code-fenced>.
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# `MD041` - First line in a file should be a top-level heading
|
||||
|
||||
Tags: `headings`
|
||||
|
||||
Aliases: `first-line-h1`, `first-line-heading`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `allow_preamble`: Allow content before first heading (`boolean`, default
|
||||
`false`)
|
||||
- `front_matter_title`: RegExp for matching title in front matter (`string`,
|
||||
default `^\s*title\s*[:=]`)
|
||||
- `level`: Heading level (`integer`, default `1`)
|
||||
|
||||
This rule is intended to ensure documents have a title and is triggered when
|
||||
the first line in a document is not a top-level ([HTML][HTML] `h1`) heading:
|
||||
|
||||
```markdown
|
||||
This is a document without a heading
|
||||
```
|
||||
|
||||
To fix this, add a top-level heading to the beginning of the document:
|
||||
|
||||
```markdown
|
||||
# Document Heading
|
||||
|
||||
This is a document with a top-level heading
|
||||
```
|
||||
|
||||
Because it is common for projects on GitHub to use an image for the heading of
|
||||
`README.md` and that pattern is not well-supported by Markdown, HTML headings
|
||||
are also permitted by this rule. For example:
|
||||
|
||||
```markdown
|
||||
<h1 align="center"><img src="https://placekitten.com/300/150"/></h1>
|
||||
|
||||
This is a document with a top-level HTML heading
|
||||
```
|
||||
|
||||
In some cases, a document's title heading may be preceded by text like a table
|
||||
of contents. This is not ideal for accessibility, but can be allowed by setting
|
||||
the `allow_preamble` parameter to `true`.
|
||||
|
||||
```markdown
|
||||
This is a document with preamble text
|
||||
|
||||
# Document Heading
|
||||
```
|
||||
|
||||
If [YAML][YAML] front matter is present and contains a `title` property
|
||||
(commonly used with blog posts), this rule will not report a violation. To use a
|
||||
different property name in the front matter, specify the text of a [regular
|
||||
expression][RegExp] via the `front_matter_title` parameter. To disable the use
|
||||
of front matter by this rule, specify `""` for `front_matter_title`.
|
||||
|
||||
The `level` parameter can be used to change the top-level heading (ex: to `h2`)
|
||||
in cases where an `h1` is added externally.
|
||||
|
||||
Rationale: The top-level heading often acts as the title of a document. More
|
||||
information: <https://cirosantilli.com/markdown-style-guide#top-level-header>.
|
||||
|
||||
[HTML]: https://en.wikipedia.org/wiki/HTML
|
||||
[RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions
|
||||
[YAML]: https://en.wikipedia.org/wiki/YAML
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# `MD042` - No empty links
|
||||
|
||||
Tags: `links`
|
||||
|
||||
Aliases: `no-empty-links`
|
||||
|
||||
This rule is triggered when an empty link is encountered:
|
||||
|
||||
```markdown
|
||||
[an empty link]()
|
||||
```
|
||||
|
||||
To fix the violation, provide a destination for the link:
|
||||
|
||||
```markdown
|
||||
[a valid link](https://example.com/)
|
||||
```
|
||||
|
||||
Empty fragments will trigger this rule:
|
||||
|
||||
```markdown
|
||||
[an empty fragment](#)
|
||||
```
|
||||
|
||||
But non-empty fragments will not:
|
||||
|
||||
```markdown
|
||||
[a valid fragment](#fragment)
|
||||
```
|
||||
|
||||
Rationale: Empty links do not lead anywhere and therefore don't function as
|
||||
links.
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
# `MD043` - Required heading structure
|
||||
|
||||
Tags: `headings`
|
||||
|
||||
Aliases: `required-headings`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `headings`: List of headings (`string[]`, default `[]`)
|
||||
- `match_case`: Match case of headings (`boolean`, default `false`)
|
||||
|
||||
This rule is triggered when the headings in a file do not match the array of
|
||||
headings passed to the rule. It can be used to enforce a standard heading
|
||||
structure for a set of files.
|
||||
|
||||
To require exactly the following structure:
|
||||
|
||||
```markdown
|
||||
# Heading
|
||||
## Item
|
||||
### Detail
|
||||
```
|
||||
|
||||
Set the `headings` parameter to:
|
||||
|
||||
```json
|
||||
[
|
||||
"# Heading",
|
||||
"## Item",
|
||||
"### Detail"
|
||||
]
|
||||
```
|
||||
|
||||
To allow optional headings as with the following structure:
|
||||
|
||||
```markdown
|
||||
# Heading
|
||||
## Item
|
||||
### Detail (optional)
|
||||
## Foot
|
||||
### Notes (optional)
|
||||
```
|
||||
|
||||
Use the special value `"*"` meaning "zero or more unspecified headings" or the
|
||||
special value `"+"` meaning "one or more unspecified headings" and set the
|
||||
`headings` parameter to:
|
||||
|
||||
```json
|
||||
[
|
||||
"# Heading",
|
||||
"## Item",
|
||||
"*",
|
||||
"## Foot",
|
||||
"*"
|
||||
]
|
||||
```
|
||||
|
||||
To allow a single required heading to vary as with a project name:
|
||||
|
||||
```markdown
|
||||
# Project Name
|
||||
## Description
|
||||
## Examples
|
||||
```
|
||||
|
||||
Use the special value `"?"` meaning "exactly one unspecified heading":
|
||||
|
||||
```json
|
||||
[
|
||||
"?",
|
||||
"## Description",
|
||||
"## Examples"
|
||||
]
|
||||
```
|
||||
|
||||
When an error is detected, this rule outputs the line number of the first
|
||||
problematic heading (otherwise, it outputs the last line number of the file).
|
||||
|
||||
Note that while the `headings` parameter uses the "## Text" ATX heading style
|
||||
for simplicity, a file may use any supported heading style.
|
||||
|
||||
By default, the case of headings in the document is not required to match that
|
||||
of `headings`. To require that case match exactly, set the `match_case`
|
||||
parameter to `true`.
|
||||
|
||||
Rationale: Projects may wish to enforce a consistent document structure across
|
||||
a set of similar content.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# `MD044` - Proper names should have the correct capitalization
|
||||
|
||||
Tags: `spelling`
|
||||
|
||||
Aliases: `proper-names`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `code_blocks`: Include code blocks (`boolean`, default `true`)
|
||||
- `html_elements`: Include HTML elements (`boolean`, default `true`)
|
||||
- `names`: List of proper names (`string[]`, default `[]`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when any of the strings in the `names` array do not have
|
||||
the specified capitalization. It can be used to enforce a standard letter case
|
||||
for the names of projects and products.
|
||||
|
||||
For example, the language "JavaScript" is usually written with both the 'J' and
|
||||
'S' capitalized - though sometimes the 's' or 'j' appear in lower-case. To
|
||||
enforce the proper capitalization, specify the desired letter case in the
|
||||
`names` array:
|
||||
|
||||
```json
|
||||
[
|
||||
"JavaScript"
|
||||
]
|
||||
```
|
||||
|
||||
Sometimes a proper name is capitalized differently in certain contexts. In such
|
||||
cases, add both forms to the `names` array:
|
||||
|
||||
```json
|
||||
[
|
||||
"GitHub",
|
||||
"github.com"
|
||||
]
|
||||
```
|
||||
|
||||
Set the `code_blocks` parameter to `false` to disable this rule for code blocks
|
||||
and spans. Set the `html_elements` parameter to `false` to disable this rule
|
||||
for HTML elements and attributes (such as when using a proper name as part of
|
||||
a path for `a`/`href` or `img`/`src`).
|
||||
|
||||
Rationale: Incorrect capitalization of proper names is usually a mistake.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# `MD045` - Images should have alternate text (alt text)
|
||||
|
||||
Tags: `accessibility`, `images`
|
||||
|
||||
Aliases: `no-alt-text`
|
||||
|
||||
This rule reports a violation when an image is missing alternate text (alt text)
|
||||
information.
|
||||
|
||||
Alternate text is commonly specified inline as:
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
Or with reference syntax as:
|
||||
|
||||
```markdown
|
||||
![Alternate text][ref]
|
||||
|
||||
...
|
||||
|
||||
[ref]: image.jpg "Optional title"
|
||||
```
|
||||
|
||||
Or with HTML as:
|
||||
|
||||
```html
|
||||
<img src="image.jpg" alt="Alternate text" />
|
||||
```
|
||||
|
||||
Note: If the [HTML `aria-hidden` attribute][aria-hidden] is used to hide the
|
||||
image from assistive technology, this rule does not report a violation:
|
||||
|
||||
```html
|
||||
<img src="image.jpg" aria-hidden="true" />
|
||||
```
|
||||
|
||||
Guidance for writing alternate text is available from the [W3C][w3c],
|
||||
[Wikipedia][wikipedia], and [other locations][phase2technology].
|
||||
|
||||
Rationale: Alternate text is important for accessibility and describes the
|
||||
content of an image for people who may not be able to see it.
|
||||
|
||||
[aria-hidden]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden
|
||||
[phase2technology]: https://www.phase2technology.com/blog/no-more-excuses
|
||||
[w3c]: https://www.w3.org/WAI/alt/
|
||||
[wikipedia]: https://en.wikipedia.org/wiki/Alt_attribute
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# `MD046` - Code block style
|
||||
|
||||
Tags: `code`
|
||||
|
||||
Aliases: `code-block-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: Block style (`string`, default `consistent`, values `consistent` /
|
||||
`fenced` / `indented`)
|
||||
|
||||
This rule is triggered when unwanted or different code block styles are used in
|
||||
the same document.
|
||||
|
||||
In the default configuration this rule reports a violation for the following
|
||||
document:
|
||||
|
||||
<!-- markdownlint-disable code-block-style -->
|
||||
|
||||
Some text.
|
||||
|
||||
# Indented code
|
||||
|
||||
More text.
|
||||
|
||||
```ruby
|
||||
# Fenced code
|
||||
```
|
||||
|
||||
More text.
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
To fix violations of this rule, use a consistent style (either indenting or code
|
||||
fences).
|
||||
|
||||
The configured code block style can be specific (`fenced`, `indented`) or can
|
||||
require all code blocks match the first code block (`consistent`).
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# `MD047` - Files should end with a single newline character
|
||||
|
||||
Tags: `blank_lines`
|
||||
|
||||
Aliases: `single-trailing-newline`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when there is not a single newline character at the end
|
||||
of a file.
|
||||
|
||||
An example that triggers the rule:
|
||||
|
||||
```markdown
|
||||
# Heading
|
||||
|
||||
This file ends without a newline.[EOF]
|
||||
```
|
||||
|
||||
To fix the violation, add a newline character to the end of the file:
|
||||
|
||||
```markdown
|
||||
# Heading
|
||||
|
||||
This file ends with a newline.
|
||||
[EOF]
|
||||
```
|
||||
|
||||
Rationale: Some programs have trouble with files that do not end with a newline.
|
||||
|
||||
More information: [What's the point in adding a new line to the end of a
|
||||
file?][stack-exchange]
|
||||
|
||||
[stack-exchange]: https://unix.stackexchange.com/questions/18743/whats-the-point-in-adding-a-new-line-to-the-end-of-a-file
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# `MD048` - Code fence style
|
||||
|
||||
Tags: `code`
|
||||
|
||||
Aliases: `code-fence-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: Code fence style (`string`, default `consistent`, values `backtick`
|
||||
/ `consistent` / `tilde`)
|
||||
|
||||
This rule is triggered when the symbols used in the document for fenced code
|
||||
blocks do not match the configured code fence style:
|
||||
|
||||
````markdown
|
||||
```ruby
|
||||
# Fenced code
|
||||
```
|
||||
|
||||
~~~ruby
|
||||
# Fenced code
|
||||
~~~
|
||||
````
|
||||
|
||||
To fix this issue, use the configured code fence style throughout the
|
||||
document:
|
||||
|
||||
````markdown
|
||||
```ruby
|
||||
# Fenced code
|
||||
```
|
||||
|
||||
```ruby
|
||||
# Fenced code
|
||||
```
|
||||
````
|
||||
|
||||
The configured code fence style can be a specific symbol to use (`backtick`,
|
||||
`tilde`) or it can require all code fences match the first code fence
|
||||
(`consistent`).
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# `MD049` - Emphasis style
|
||||
|
||||
Tags: `emphasis`
|
||||
|
||||
Aliases: `emphasis-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: Emphasis style (`string`, default `consistent`, values `asterisk` /
|
||||
`consistent` / `underscore`)
|
||||
|
||||
Fixable: Some 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 all emphasis matches the first emphasis
|
||||
(`consistent`).
|
||||
|
||||
Note: Emphasis within a word is restricted to `asterisk` in order to avoid
|
||||
unwanted emphasis for words containing internal underscores like_this_one.
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# `MD050` - Strong style
|
||||
|
||||
Tags: `emphasis`
|
||||
|
||||
Aliases: `strong-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: Strong style (`string`, default `consistent`, values `asterisk` /
|
||||
`consistent` / `underscore`)
|
||||
|
||||
Fixable: Some 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 all strong matches the first strong (`consistent`).
|
||||
|
||||
Note: Emphasis within a word is restricted to `asterisk` in order to avoid
|
||||
unwanted emphasis for words containing internal underscores like__this__one.
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
# `MD051` - Link fragments should be valid
|
||||
|
||||
Tags: `links`
|
||||
|
||||
Aliases: `link-fragments`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `ignore_case`: Ignore case of fragments (`boolean`, default `false`)
|
||||
- `ignored_pattern`: Pattern for ignoring additional fragments (`string`,
|
||||
default ``)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when a link fragment does not match any of the fragments
|
||||
that are automatically generated for headings in a document:
|
||||
|
||||
```markdown
|
||||
# Heading Name
|
||||
|
||||
[Link](#fragment)
|
||||
```
|
||||
|
||||
To fix this issue, change the link fragment to reference an existing heading's
|
||||
generated name (see below):
|
||||
|
||||
```markdown
|
||||
# Heading Name
|
||||
|
||||
[Link](#heading-name)
|
||||
```
|
||||
|
||||
For consistency, this rule requires fragments to exactly match the [GitHub
|
||||
heading algorithm][github-heading-algorithm] which converts letters to
|
||||
lowercase. Therefore, the following example is reported as a violation:
|
||||
|
||||
```markdown
|
||||
# Heading Name
|
||||
|
||||
[Link](#Heading-Name)
|
||||
```
|
||||
|
||||
To ignore case when comparing fragments with heading names, the `ignore_case`
|
||||
parameter can be set to `true`. In this configuration, the previous example is
|
||||
not reported as a violation.
|
||||
|
||||
Alternatively, some platforms allow the syntax `{#named-anchor}` to be used
|
||||
within a heading to provide a specific name (consisting of only lower-case
|
||||
letters, numbers, `-`, and `_`):
|
||||
|
||||
```markdown
|
||||
# Heading Name {#custom-name}
|
||||
|
||||
[Link](#custom-name)
|
||||
```
|
||||
|
||||
Alternatively, any HTML tag with an `id` attribute or an `a` tag with a `name`
|
||||
attribute can be used to define a fragment:
|
||||
|
||||
```markdown
|
||||
<a id="bookmark"></a>
|
||||
|
||||
[Link](#bookmark)
|
||||
```
|
||||
|
||||
An `a` tag can be useful in scenarios where a heading is not appropriate or for
|
||||
control over the text of the fragment identifier.
|
||||
|
||||
[HTML links to `#top` scroll to the top of a document][html-top-fragment]. This
|
||||
rule allows that syntax (using lower-case for consistency):
|
||||
|
||||
```markdown
|
||||
[Link](#top)
|
||||
```
|
||||
|
||||
This rule also recognizes the custom fragment syntax used by GitHub to highlight
|
||||
[specific content in a document][github-linking-to-content].
|
||||
|
||||
For example, this link to line 20:
|
||||
|
||||
```markdown
|
||||
[Link](#L20)
|
||||
```
|
||||
|
||||
And this link to content starting within line 19 running into line 21:
|
||||
|
||||
```markdown
|
||||
[Link](#L19C5-L21C11)
|
||||
```
|
||||
|
||||
Some Markdown generators dynamically create and insert headings when building
|
||||
documents, for example by combining a fixed prefix like `figure-` and an
|
||||
incrementing numeric counter. To ignore such generated fragments, set the
|
||||
`ignored_pattern` [regular expression][RegEx] parameter to a pattern that
|
||||
matches (e.g., `^figure-`).
|
||||
|
||||
Rationale: [GitHub section links][github-section-links] are created
|
||||
automatically for every heading when Markdown content is displayed on GitHub.
|
||||
This makes it easy to link directly to different sections within a document.
|
||||
However, section links change if headings are renamed or removed. This rule
|
||||
helps identify broken section links within a document.
|
||||
|
||||
Note: Section links are **not** part of the CommonMark specification; this rule
|
||||
enforces the [GitHub heading algorithm][github-heading-algorithm]:
|
||||
|
||||
1. Convert text to lowercase
|
||||
2. Remove punctuation characters
|
||||
3. Convert spaces to dashes
|
||||
4. Append an incrementing integer (as needed for uniqueness)
|
||||
5. [URI-encode][encodeURIComponent] the result
|
||||
|
||||
[encodeURIComponent]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
||||
[github-section-links]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
|
||||
[github-heading-algorithm]: https://github.com/gjtorikian/html-pipeline/blob/f13a1534cb650ba17af400d1acd3a22c28004c09/lib/html/pipeline/toc_filter.rb
|
||||
[github-linking-to-content]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-a-permanent-link-to-a-code-snippet#linking-to-markdown
|
||||
[html-top-fragment]: https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment
|
||||
[RegEx]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# `MD052` - Reference links and images should use a label that is defined
|
||||
|
||||
Tags: `images`, `links`
|
||||
|
||||
Aliases: `reference-links-images`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `ignored_labels`: Ignored link labels (`string[]`, default `["x"]`)
|
||||
- `shortcut_syntax`: Include shortcut syntax (`boolean`, default `false`)
|
||||
|
||||
Links and images in Markdown can provide the link destination or image source
|
||||
at the time of use or can define it elsewhere and use a label for reference.
|
||||
The reference format is convenient for keeping paragraph text clutter-free
|
||||
and makes it easy to reuse the same URL in multiple places.
|
||||
|
||||
There are three kinds of reference links and images:
|
||||
|
||||
```markdown
|
||||
Full: [text][label]
|
||||
Collapsed: [label][]
|
||||
Shortcut: [label]
|
||||
|
||||
Full: ![text][image]
|
||||
Collapsed: ![image][]
|
||||
Shortcut: ![image]
|
||||
|
||||
[label]: https://example.com/label
|
||||
[image]: https://example.com/image
|
||||
```
|
||||
|
||||
A link or image renders correctly when the corresponding label is defined, but
|
||||
displays as text with brackets when the label is not present. By default, this
|
||||
rule warns of undefined labels for "full" and "collapsed" reference syntax but
|
||||
not for "shortcut" syntax because it is ambiguous.
|
||||
|
||||
The text `[example]` could be a shortcut link or the text "example" in brackets,
|
||||
so "shortcut" syntax is ignored by default. To include "shortcut" syntax, set
|
||||
the `include_shortcut` parameter to `true`. Note that doing so produces warnings
|
||||
for *all* text in the document that *could* be a shortcut. If bracketed text is
|
||||
intentional, brackets can be escaped with the `\` character: `\[example\]`.
|
||||
|
||||
If there are link labels that are deliberately unreferenced, they can be ignored
|
||||
by setting the `ignored_labels` parameter to the list of strings to ignore. The
|
||||
default value of this parameter ignores the checkbox syntax used by
|
||||
[GitHub Flavored Markdown task list items][gfm-tasklist]:
|
||||
|
||||
```markdown
|
||||
- [x] Checked task list item
|
||||
```
|
||||
|
||||
[gfm-tasklist]: https://github.github.com/gfm/#task-list-items-extension-
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# `MD053` - Link and image reference definitions should be needed
|
||||
|
||||
Tags: `images`, `links`
|
||||
|
||||
Aliases: `link-image-reference-definitions`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `ignored_definitions`: Ignored definitions (`string[]`, default `["//"]`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
Links and images in Markdown can provide the link destination or image source
|
||||
at the time of use or can use a label to reference a definition elsewhere in
|
||||
the document. The latter reference format is convenient for keeping paragraph
|
||||
text clutter-free and makes it easy to reuse the same URL in multiple places.
|
||||
|
||||
Because link and image reference definitions are located separately from
|
||||
where they are used, there are two scenarios where a definition can be
|
||||
unnecessary:
|
||||
|
||||
1. If a label is not referenced by any link or image in a document, that
|
||||
definition is unused and can be deleted.
|
||||
2. If a label is defined multiple times in a document, the first definition is
|
||||
used and the others can be deleted.
|
||||
|
||||
This rule considers a reference definition to be used if any link or image
|
||||
reference has the corresponding label. The "full", "collapsed", and "shortcut"
|
||||
formats are all supported.
|
||||
|
||||
If there are reference definitions that are deliberately unreferenced, they can
|
||||
be ignored by setting the `ignored_definitions` parameter to the list of strings
|
||||
to ignore. The default value of this parameter ignores the following convention
|
||||
for adding non-HTML comments to Markdown:
|
||||
|
||||
```markdown
|
||||
[//]: # (This behaves like a comment)
|
||||
```
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# `MD054` - Link and image style
|
||||
|
||||
Tags: `images`, `links`
|
||||
|
||||
Aliases: `link-image-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `autolink`: Allow autolinks (`boolean`, default `true`)
|
||||
- `collapsed`: Allow collapsed reference links and images (`boolean`, default
|
||||
`true`)
|
||||
- `full`: Allow full reference links and images (`boolean`, default `true`)
|
||||
- `inline`: Allow inline links and images (`boolean`, default `true`)
|
||||
- `shortcut`: Allow shortcut reference links and images (`boolean`, default
|
||||
`true`)
|
||||
- `url_inline`: Allow URLs as inline links (`boolean`, default `true`)
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
Links and images in Markdown can provide the link destination or image source at
|
||||
the time of use or can use a label to reference a definition elsewhere in the
|
||||
document. The three reference formats are convenient for keeping paragraph text
|
||||
clutter-free and make it easy to reuse the same URL in multiple places.
|
||||
|
||||
By default, this rule allows all link/image styles.
|
||||
|
||||
Setting the `autolink` parameter to `false` disables autolinks:
|
||||
|
||||
```markdown
|
||||
<https://example.com>
|
||||
```
|
||||
|
||||
Setting the `inline` parameter to `false` disables inline links and images:
|
||||
|
||||
```markdown
|
||||
[link](https://example.com)
|
||||
|
||||

|
||||
```
|
||||
|
||||
Setting the `full` parameter to `false` disables full reference links and
|
||||
images:
|
||||
|
||||
```markdown
|
||||
[link][url]
|
||||
|
||||
![image][url]
|
||||
|
||||
[url]: https://example.com
|
||||
```
|
||||
|
||||
Setting the `collapsed` parameter to `false` disables collapsed reference links
|
||||
and images:
|
||||
|
||||
```markdown
|
||||
[url][]
|
||||
|
||||
![url][]
|
||||
|
||||
[url]: https://example.com
|
||||
```
|
||||
|
||||
Setting the `shortcut` parameter to `false` disables shortcut reference links
|
||||
and images:
|
||||
|
||||
```markdown
|
||||
[url]
|
||||
|
||||
![url]
|
||||
|
||||
[url]: https://example.com
|
||||
```
|
||||
|
||||
To fix violations of this rule, change the link or image to use an allowed
|
||||
style. This rule can automatically fix violations when a link or image can be
|
||||
converted to the `inline` style (preferred) or a link can be converted to the
|
||||
`autolink` style (which does not support images and must be an absolute URL).
|
||||
This rule does *not* fix scenarios that require converting a link or image to
|
||||
the `full`, `collapsed`, or `shortcut` reference styles because that involves
|
||||
naming the reference and determining where to insert it in the document.
|
||||
|
||||
Setting the `url_inline` parameter to `false` prevents the use of inline links
|
||||
with the same absolute URL text/destination and no title because such links can
|
||||
be converted to autolinks:
|
||||
|
||||
```markdown
|
||||
[https://example.com](https://example.com)
|
||||
```
|
||||
|
||||
To fix `url_inline` violations, use the simpler autolink syntax instead:
|
||||
|
||||
```markdown
|
||||
<https://example.com>
|
||||
```
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
Autolinks are concise, but appear as URLs which can be long and confusing.
|
||||
Inline links and images can include descriptive text, but take up more space in
|
||||
Markdown form. Reference links and images can be easier to read and manipulate
|
||||
in Markdown form, but require a separate link reference definition.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# `MD055` - Table pipe style
|
||||
|
||||
Tags: `table`
|
||||
|
||||
Aliases: `table-pipe-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `style`: Table pipe style (`string`, default `consistent`, values
|
||||
`consistent` / `leading_and_trailing` / `leading_only` /
|
||||
`no_leading_or_trailing` / `trailing_only`)
|
||||
|
||||
This rule is triggered when a [GitHub Flavored Markdown table][gfm-table-055]
|
||||
is inconsistent about its use of leading and trailing pipe characters (`|`).
|
||||
|
||||
By default (`consistent` style), the header row of the first table in a document
|
||||
is used to determine the style that is enforced for every table in the document.
|
||||
A specific style can be used instead (`leading_and_trailing`, `leading_only`,
|
||||
`no_leading_or_trailing`, `trailing_only`).
|
||||
|
||||
This table's header row has leading and trailing pipes, but its delimiter row is
|
||||
missing the trailing pipe and its first row of cells is missing the leading
|
||||
pipe:
|
||||
|
||||
```markdown
|
||||
| Header | Header |
|
||||
| ------ | ------
|
||||
Cell | Cell |
|
||||
```
|
||||
|
||||
To fix these issues, make sure there is a pipe character at the beginning and
|
||||
end of every row:
|
||||
|
||||
```markdown
|
||||
| Header | Header |
|
||||
| ------ | ------ |
|
||||
| Cell | Cell |
|
||||
```
|
||||
|
||||
Note that text immediately following a table (i.e., not separated by an empty
|
||||
line) is treated as part of the table (per the specification) and may also
|
||||
trigger this rule:
|
||||
|
||||
```markdown
|
||||
| Header | Header |
|
||||
| ------ | ------ |
|
||||
| Cell | Cell |
|
||||
This text is part of the table
|
||||
```
|
||||
|
||||
Rationale: Some parsers have difficulty with tables that are missing their
|
||||
leading or trailing pipe characters. The use of leading/trailing pipes can also
|
||||
help provide visual clarity.
|
||||
|
||||
[gfm-table-055]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# `MD056` - Table column count
|
||||
|
||||
Tags: `table`
|
||||
|
||||
Aliases: `table-column-count`
|
||||
|
||||
This rule is triggered when a [GitHub Flavored Markdown table][gfm-table-056]
|
||||
does not have the same number of cells in every row.
|
||||
|
||||
This table's second data row has too few cells and its third data row has too
|
||||
many cells:
|
||||
|
||||
```markdown
|
||||
| Header | Header |
|
||||
| ------ | ------ |
|
||||
| Cell | Cell |
|
||||
| Cell |
|
||||
| Cell | Cell | Cell |
|
||||
```
|
||||
|
||||
To fix these issues, ensure every row has the same number of cells:
|
||||
|
||||
```markdown
|
||||
| Header | Header |
|
||||
| ------ | ------ |
|
||||
| Cell | Cell |
|
||||
| Cell | Cell |
|
||||
| Cell | Cell |
|
||||
```
|
||||
|
||||
Note that a table's header row and its delimiter row must have the same number
|
||||
of cells or it will not be recognized as a table (per specification).
|
||||
|
||||
Rationale: Extra cells in a row are usually not shown, so their data is lost.
|
||||
Missing cells in a row create holes in the table and suggest an omission.
|
||||
|
||||
[gfm-table-056]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# `MD058` - Tables should be surrounded by blank lines
|
||||
|
||||
Tags: `table`
|
||||
|
||||
Aliases: `blanks-around-tables`
|
||||
|
||||
Fixable: Some violations can be fixed by tooling
|
||||
|
||||
This rule is triggered when tables are either not preceded or not followed by a
|
||||
blank line:
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
| Header | Header |
|
||||
| ------ | ------ |
|
||||
| Cell | Cell |
|
||||
> Blockquote
|
||||
```
|
||||
|
||||
To fix violations of this rule, ensure that all tables have a blank line both
|
||||
before and after (except when the table is at the very beginning or end of the
|
||||
document):
|
||||
|
||||
```markdown
|
||||
Some text
|
||||
|
||||
| Header | Header |
|
||||
| ------ | ------ |
|
||||
| Cell | Cell |
|
||||
|
||||
> Blockquote
|
||||
```
|
||||
|
||||
Note that text immediately following a table (i.e., not separated by an empty
|
||||
line) is treated as part of the table (per the specification) and will not
|
||||
trigger this rule:
|
||||
|
||||
```markdown
|
||||
| Header | Header |
|
||||
| ------ | ------ |
|
||||
| Cell | Cell |
|
||||
This text is part of the table and the next line is blank
|
||||
|
||||
Some text
|
||||
```
|
||||
|
||||
Rationale: In addition to aesthetic reasons, some parsers will incorrectly parse
|
||||
tables that don't have blank lines before and after them.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# `MD059` - Link text should be descriptive
|
||||
|
||||
Tags: `accessibility`, `links`
|
||||
|
||||
Aliases: `descriptive-link-text`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `prohibited_texts`: Prohibited link texts (`string[]`, default `["click
|
||||
here","here","link","more"]`)
|
||||
|
||||
This rule is triggered when a link has generic text like `[click here](...)` or
|
||||
`[link](...)`.
|
||||
|
||||
Link text should be descriptive and communicate the purpose of the link (e.g.,
|
||||
`[Download the budget document](...)` or `[CommonMark Specification](...)`).
|
||||
This is especially important for screen readers which sometimes present links
|
||||
without context.
|
||||
|
||||
By default, this rule prohibits a small number of common English words/phrases.
|
||||
To customize that list of words/phrases, set the `prohibited_texts` parameter to
|
||||
an `Array` of `string`s.
|
||||
|
||||
Note: For languages other than English, use the `prohibited_texts` parameter to
|
||||
customize the list for that language. It is *not* a goal for this rule to have
|
||||
translations for every language.
|
||||
|
||||
Note: This rule checks Markdown links; HTML links are ignored.
|
||||
|
||||
More information:
|
||||
|
||||
- <https://webaim.org/techniques/hypertext/>
|
||||
- <https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-link-only.html>
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
# `MD060` - Table column style
|
||||
|
||||
Tags: `table`
|
||||
|
||||
Aliases: `table-column-style`
|
||||
|
||||
Parameters:
|
||||
|
||||
- `aligned_delimiter`: Aligned delimiter columns (`boolean`, default `false`)
|
||||
- `style`: Table column style (`string`, default `any`, values `aligned` /
|
||||
`any` / `compact` / `tight`)
|
||||
|
||||
This rule is triggered when the column separator pipe characters (`|`) of a
|
||||
[GitHub Flavored Markdown table][gfm-table-060] are used inconsistently.
|
||||
|
||||
This rule recognizes three table column styles based on popular use.
|
||||
|
||||
Style `aligned` ensures pipe characters are vertically aligned:
|
||||
|
||||
```markdown
|
||||
| Character | Meaning |
|
||||
| --------- | ------- |
|
||||
| Y | Yes |
|
||||
| N | No |
|
||||
```
|
||||
|
||||
The `aligned` style ignores cell content, so the following is also valid:
|
||||
|
||||
```markdown
|
||||
| Character | Meaning |
|
||||
|-----------|---------|
|
||||
| Y | Yes |
|
||||
| N | No |
|
||||
```
|
||||
|
||||
Style `compact` avoids extra padding with a single space around cell content:
|
||||
|
||||
```markdown
|
||||
| Character | Meaning |
|
||||
| --- | --- |
|
||||
| Y | Yes |
|
||||
| N | No |
|
||||
```
|
||||
|
||||
Style `tight` uses no padding at all for cell content:
|
||||
|
||||
```markdown
|
||||
|Character|Meaning|
|
||||
|---|---|
|
||||
|Y|Yes|
|
||||
|N|No|
|
||||
```
|
||||
|
||||
When this rule's `style` parameter is set to `aligned`, `compact`, or `tight`,
|
||||
every table must match the corresponding pattern and any violations will be
|
||||
reported. By default, or when the `any` style is used, each table is analyzed to
|
||||
see if it satisfies any supported style. If so, no violations are reported. If
|
||||
not, violations are be reported for whichever style would produce the *fewest*
|
||||
issues (i.e., whichever style is the closest match).
|
||||
|
||||
Setting the `aligned_delimiter` parameter to `true` requires pipe characters in
|
||||
the delimiter row to align with those in the header row. This can be used with
|
||||
`compact` and `tight` tables to make the header text more obvious. (It's already
|
||||
required for tables with style `aligned`.)
|
||||
|
||||
Style `compact` with `aligned_delimiter`:
|
||||
|
||||
```markdown
|
||||
| Character | Meaning |
|
||||
| --------- | ------- |
|
||||
| Y | Yes |
|
||||
| N | No |
|
||||
```
|
||||
|
||||
Style `tight` with `aligned_delimiter`:
|
||||
|
||||
```markdown
|
||||
|Character|Meaning|
|
||||
|---------|-------|
|
||||
|Y|Yes|
|
||||
|N|No|
|
||||
```
|
||||
|
||||
**Note**: This rule does not require leading/trailing pipe characters, so this
|
||||
is also a valid table for style `compact`:
|
||||
|
||||
```markdown
|
||||
Character | Meaning
|
||||
--- | ---
|
||||
Y | Yes
|
||||
N | No
|
||||
```
|
||||
|
||||
**Note**: Pipe alignment for the `aligned` style is based on visual appearance
|
||||
and not character count. Because editors typically render [emoji][emoji] and
|
||||
[CJK characters][cjk-characters] at *twice* the width of
|
||||
[Latin characters][latin-script], this rule takes that into account for tables
|
||||
using the `aligned` style. The following table is correctly formatted and will
|
||||
appear aligned in most editors and monospaced fonts:
|
||||
|
||||
<!-- markdownlint-capture -->
|
||||
<!-- markdownlint-disable extended-ascii -->
|
||||
|
||||
```markdown
|
||||
| Response | Emoji |
|
||||
| -------- | ----- |
|
||||
| Yes | ✅ |
|
||||
| No | ❎ |
|
||||
```
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
Rationale: Consistent formatting makes it easier to understand a document.
|
||||
|
||||
[cjk-characters]: https://en.wikipedia.org/wiki/CJK_characters
|
||||
[emoji]: https://en.wikipedia.org/wiki/Emoji
|
||||
[gfm-table-060]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables
|
||||
[latin-script]: https://en.wikipedia.org/wiki/Latin_script
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) David Anson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# markdownlint-rule-helpers
|
||||
|
||||
> A collection of `markdownlint` helper functions for custom rules
|
||||
|
||||
## Overview
|
||||
|
||||
The [Markdown][markdown] linter [`markdownlint`][markdownlint] offers a variety
|
||||
of built-in validation [rules][rules] and supports the creation of [custom
|
||||
rules][custom-rules]. The internal rules share various helper functions; this
|
||||
package exposes those for reuse by custom rules.
|
||||
|
||||
## API
|
||||
|
||||
*Undocumented* - This package exports the internal functions as-is. The APIs
|
||||
were not originally meant to be public, are not officially supported, and may
|
||||
change from release to release. There are brief descriptive comments above each
|
||||
function, but no [JSDoc][jsdoc] annotations. That said, some of what's here will
|
||||
be useful to custom rule authors and may avoid duplicating code.
|
||||
|
||||
## Tests
|
||||
|
||||
*None* - The entire body of code is tested to 100% coverage by the core
|
||||
`markdownlint` project, so there are no additional tests here.
|
||||
|
||||
[custom-rules]: https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/CustomRules.md
|
||||
[jsdoc]: https://en.m.wikipedia.org/wiki/JSDoc
|
||||
[markdown]: https://en.wikipedia.org/wiki/Markdown
|
||||
[markdownlint]: https://github.com/DavidAnson/markdownlint
|
||||
[rules]: https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/Rules.md
|
||||
+696
@@ -0,0 +1,696 @@
|
||||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const micromark = require("./micromark-helpers.cjs");
|
||||
|
||||
const { newLineRe, nextLinesRe } = require("./shared.cjs");
|
||||
|
||||
module.exports.newLineRe = newLineRe;
|
||||
module.exports.nextLinesRe = nextLinesRe;
|
||||
|
||||
/** @typedef {import("../lib/exports.mjs").RuleOnError} RuleOnError */
|
||||
/** @typedef {import("../lib/exports.mjs").RuleOnErrorFixInfo} RuleOnErrorFixInfo */
|
||||
/** @typedef {import("../lib/exports.mjs").MicromarkToken} MicromarkToken */
|
||||
// eslint-disable-next-line jsdoc/valid-types
|
||||
/** @typedef {import("micromark-extension-gfm-footnote", { with: { "resolution-mode": "import" } })} */
|
||||
// eslint-disable-next-line jsdoc/valid-types
|
||||
/** @typedef {import("../lib/micromark-types.d.mts", { with: { "resolution-mode": "import" } })} */
|
||||
|
||||
// Regular expression for matching common front matter (YAML and TOML)
|
||||
// @ts-ignore
|
||||
module.exports.frontMatterRe =
|
||||
/((^---[^\S\r\n\u2028\u2029]*$[\s\S]+?^---\s*)|(^\+\+\+[^\S\r\n\u2028\u2029]*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{[^\S\r\n\u2028\u2029]*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m;
|
||||
|
||||
// Regular expression for matching the start of inline disable/enable comments
|
||||
const inlineCommentStartRe =
|
||||
/(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-line|disable-next-line|configure-file))(?:\s|-->)/gi;
|
||||
module.exports.inlineCommentStartRe = inlineCommentStartRe;
|
||||
|
||||
// Regular expression for identifying an HTML entity at the end of a line
|
||||
module.exports.endOfLineHtmlEntityRe =
|
||||
/&(?:#\d+|#[xX][\da-fA-F]+|[a-zA-Z]{2,31}|blk\d{2}|emsp1[34]|frac\d{2}|sup\d|there4);$/;
|
||||
|
||||
// Regular expression for identifying a GitHub emoji code at the end of a line
|
||||
module.exports.endOfLineGemojiCodeRe =
|
||||
/:(?:[abmovx]|[-+]1|100|1234|(?:1st|2nd|3rd)_place_medal|8ball|clock\d{1,4}|e-mail|non-potable_water|o2|t-rex|u5272|u5408|u55b6|u6307|u6708|u6709|u6e80|u7121|u7533|u7981|u7a7a|[a-z]{2,15}2?|[a-z]{1,14}(?:_[a-z\d]{1,16})+):$/;
|
||||
|
||||
// All punctuation characters (normal and full-width)
|
||||
const allPunctuation = ".,;:!?。,;:!?";
|
||||
module.exports.allPunctuation = allPunctuation;
|
||||
|
||||
// All punctuation characters without question mark (normal and full-width)
|
||||
module.exports.allPunctuationNoQuestion = allPunctuation.replace(/[??]/gu, "");
|
||||
|
||||
/**
|
||||
* Returns true iff the input is a Number.
|
||||
*
|
||||
* @param {Object} obj Object of unknown type.
|
||||
* @returns {boolean} True iff obj is a Number.
|
||||
*/
|
||||
function isNumber(obj) {
|
||||
return typeof obj === "number";
|
||||
}
|
||||
module.exports.isNumber = isNumber;
|
||||
|
||||
/**
|
||||
* Returns true iff the input is a String.
|
||||
*
|
||||
* @param {Object} obj Object of unknown type.
|
||||
* @returns {boolean} True iff obj is a String.
|
||||
*/
|
||||
function isString(obj) {
|
||||
return typeof obj === "string";
|
||||
}
|
||||
module.exports.isString = isString;
|
||||
|
||||
/**
|
||||
* Returns true iff the input String is empty.
|
||||
*
|
||||
* @param {string} str String of unknown length.
|
||||
* @returns {boolean} True iff the input String is empty.
|
||||
*/
|
||||
function isEmptyString(str) {
|
||||
return str.length === 0;
|
||||
}
|
||||
module.exports.isEmptyString = isEmptyString;
|
||||
|
||||
/**
|
||||
* Returns true iff the input is an Object.
|
||||
*
|
||||
* @param {Object} obj Object of unknown type.
|
||||
* @returns {boolean} True iff obj is an Object.
|
||||
*/
|
||||
function isObject(obj) {
|
||||
return !!obj && (typeof obj === "object") && !Array.isArray(obj);
|
||||
}
|
||||
module.exports.isObject = isObject;
|
||||
|
||||
/**
|
||||
* Returns true iff the input is a URL.
|
||||
*
|
||||
* @param {Object} obj Object of unknown type.
|
||||
* @returns {boolean} True iff obj is a URL.
|
||||
*/
|
||||
function isUrl(obj) {
|
||||
return !!obj && (Object.getPrototypeOf(obj) === URL.prototype);
|
||||
}
|
||||
module.exports.isUrl = isUrl;
|
||||
|
||||
/**
|
||||
* Clones the input if it is an Array.
|
||||
*
|
||||
* @param {Object} arr Object of unknown type.
|
||||
* @returns {Object} Clone of obj iff obj is an Array.
|
||||
*/
|
||||
function cloneIfArray(arr) {
|
||||
return Array.isArray(arr) ? [ ...arr ] : arr;
|
||||
}
|
||||
module.exports.cloneIfArray = cloneIfArray;
|
||||
|
||||
/**
|
||||
* Clones the input if it is a URL.
|
||||
*
|
||||
* @param {Object | undefined} url Object of unknown type.
|
||||
* @returns {Object} Clone of obj iff obj is a URL.
|
||||
*/
|
||||
function cloneIfUrl(url) {
|
||||
// @ts-ignore
|
||||
return isUrl(url) ? new URL(url) : url;
|
||||
}
|
||||
module.exports.cloneIfUrl = cloneIfUrl;
|
||||
|
||||
/**
|
||||
* Gets a Regular Expression for matching the specified HTML attribute.
|
||||
*
|
||||
* @param {string} name HTML attribute name.
|
||||
* @returns {RegExp} Regular Expression for matching.
|
||||
*/
|
||||
module.exports.getHtmlAttributeRe = function getHtmlAttributeRe(name) {
|
||||
return new RegExp(`\\s${name}\\s*=\\s*['"]?([^'"\\s>]*)`, "iu");
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true iff the input line is blank (contains nothing, whitespace, or
|
||||
* comments (unclosed start/end comments allowed)).
|
||||
*
|
||||
* @param {string} line Input line.
|
||||
* @returns {boolean} True iff line is blank.
|
||||
*/
|
||||
function isBlankLine(line) {
|
||||
const startComment = "<!--";
|
||||
const endComment = "-->";
|
||||
const removeComments = (/** @type {string} */ s) => {
|
||||
while (true) {
|
||||
const start = s.indexOf(startComment);
|
||||
const end = s.indexOf(endComment);
|
||||
if ((end !== -1) && ((start === -1) || (end < start))) {
|
||||
// Unmatched end comment is first
|
||||
s = s.slice(end + endComment.length);
|
||||
} else if ((start !== -1) && (end !== -1)) {
|
||||
// Start comment is before end comment
|
||||
s = s.slice(0, start) + s.slice(end + endComment.length);
|
||||
} else if ((start !== -1) && (end === -1)) {
|
||||
// Unmatched start comment is last
|
||||
s = s.slice(0, start);
|
||||
} else {
|
||||
// No more comments to remove
|
||||
return s;
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
!line ||
|
||||
!line.trim() ||
|
||||
!removeComments(line).replace(/>/g, "").trim()
|
||||
);
|
||||
}
|
||||
module.exports.isBlankLine = isBlankLine;
|
||||
|
||||
// Replaces the content of properly-formatted CommonMark comments with "."
|
||||
// This preserves the line/column information for the rest of the document
|
||||
// https://spec.commonmark.org/0.29/#html-blocks
|
||||
// https://spec.commonmark.org/0.29/#html-comment
|
||||
const htmlCommentBegin = "<!--";
|
||||
const htmlCommentEnd = "-->";
|
||||
const safeCommentCharacter = ".";
|
||||
const startsWithPipeRe = /^ *\|/;
|
||||
const notCrLfRe = /[^\r\n]/g;
|
||||
const notSpaceCrLfRe = /[^ \r\n]/g;
|
||||
const trailingSpaceRe = / +[\r\n]/g;
|
||||
const replaceTrailingSpace = (/** @type {string} */ s) => s.replace(notCrLfRe, safeCommentCharacter);
|
||||
module.exports.clearHtmlCommentText = function clearHtmlCommentText(/** @type {string} */ text) {
|
||||
let i = 0;
|
||||
while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) {
|
||||
const j = text.indexOf(htmlCommentEnd, i + 2);
|
||||
if (j === -1) {
|
||||
// Un-terminated comments are treated as text
|
||||
break;
|
||||
}
|
||||
// If the comment has content...
|
||||
if (j > i + htmlCommentBegin.length) {
|
||||
const content = text.slice(i + htmlCommentBegin.length, j);
|
||||
const lastLf = text.lastIndexOf("\n", i) + 1;
|
||||
const preText = text.slice(lastLf, i);
|
||||
const isBlock = preText.trim().length === 0;
|
||||
const couldBeTable = startsWithPipeRe.test(preText);
|
||||
const spansTableCells = couldBeTable && content.includes("\n");
|
||||
const isValid =
|
||||
isBlock ||
|
||||
!(
|
||||
spansTableCells ||
|
||||
content.startsWith(">") ||
|
||||
content.startsWith("->") ||
|
||||
content.endsWith("-") ||
|
||||
content.includes("--")
|
||||
);
|
||||
// If a valid block/inline comment...
|
||||
if (isValid) {
|
||||
const clearedContent = content
|
||||
.replace(notSpaceCrLfRe, safeCommentCharacter)
|
||||
.replace(trailingSpaceRe, replaceTrailingSpace);
|
||||
text =
|
||||
text.slice(0, i + htmlCommentBegin.length) +
|
||||
clearedContent +
|
||||
text.slice(j);
|
||||
}
|
||||
}
|
||||
i = j + htmlCommentEnd.length;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
// Escapes a string for use in a RegExp
|
||||
module.exports.escapeForRegExp = function escapeForRegExp(/** @type {string} */ str) {
|
||||
return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds ellipsis to the left/right/middle of the specified text.
|
||||
*
|
||||
* @param {string} text Text to ellipsify.
|
||||
* @param {boolean} [start] True iff the start of the text is important.
|
||||
* @param {boolean} [end] True iff the end of the text is important.
|
||||
* @returns {string} Ellipsified text.
|
||||
*/
|
||||
function ellipsify(text, start, end) {
|
||||
if (text.length <= 30) {
|
||||
// Nothing to do
|
||||
} else if (start && end) {
|
||||
text = text.slice(0, 15) + "..." + text.slice(-15);
|
||||
} else if (end) {
|
||||
text = "..." + text.slice(-30);
|
||||
} else {
|
||||
text = text.slice(0, 30) + "...";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
module.exports.ellipsify = ellipsify;
|
||||
|
||||
/**
|
||||
* Adds a generic error object via the onError callback.
|
||||
*
|
||||
* @param {RuleOnError} onError RuleOnError instance.
|
||||
* @param {number} lineNumber Line number.
|
||||
* @param {string} [detail] Error details.
|
||||
* @param {string} [context] Error context.
|
||||
* @param {number[]} [range] Column and length of error.
|
||||
* @param {RuleOnErrorFixInfo} [fixInfo] RuleOnErrorFixInfo instance.
|
||||
* @returns {void}
|
||||
*/
|
||||
function addError(onError, lineNumber, detail, context, range, fixInfo) {
|
||||
onError({
|
||||
lineNumber,
|
||||
detail,
|
||||
context,
|
||||
range,
|
||||
fixInfo
|
||||
});
|
||||
}
|
||||
module.exports.addError = addError;
|
||||
|
||||
/**
|
||||
* Adds an error object with details conditionally via the onError callback.
|
||||
*
|
||||
* @param {RuleOnError} onError RuleOnError instance.
|
||||
* @param {number} lineNumber Line number.
|
||||
* @param {Object} expected Expected value.
|
||||
* @param {Object} actual Actual value.
|
||||
* @param {string} [detail] Error details.
|
||||
* @param {string} [context] Error context.
|
||||
* @param {number[]} [range] Column and length of error.
|
||||
* @param {RuleOnErrorFixInfo} [fixInfo] RuleOnErrorFixInfo instance.
|
||||
* @returns {void}
|
||||
*/
|
||||
function addErrorDetailIf(
|
||||
onError, lineNumber, expected, actual, detail, context, range, fixInfo) {
|
||||
if (expected !== actual) {
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
"Expected: " + expected + "; Actual: " + actual +
|
||||
(detail ? "; " + detail : ""),
|
||||
context,
|
||||
range,
|
||||
fixInfo);
|
||||
}
|
||||
}
|
||||
module.exports.addErrorDetailIf = addErrorDetailIf;
|
||||
|
||||
/**
|
||||
* Adds an error object with context via the onError callback.
|
||||
*
|
||||
* @param {RuleOnError} onError RuleOnError instance.
|
||||
* @param {number} lineNumber Line number.
|
||||
* @param {string} context Error context.
|
||||
* @param {boolean} [start] True iff the start of the text is important.
|
||||
* @param {boolean} [end] True iff the end of the text is important.
|
||||
* @param {number[]} [range] Column and length of error.
|
||||
* @param {RuleOnErrorFixInfo} [fixInfo] RuleOnErrorFixInfo instance.
|
||||
* @returns {void}
|
||||
*/
|
||||
function addErrorContext(onError, lineNumber, context, start, end, range, fixInfo) {
|
||||
// Normalize new line characters so Linux and Windows trim consistently
|
||||
context = ellipsify(context.replace(newLineRe, "\n"), start, end);
|
||||
addError(onError, lineNumber, undefined, context, range, fixInfo);
|
||||
}
|
||||
module.exports.addErrorContext = addErrorContext;
|
||||
|
||||
/**
|
||||
* Defines a range within a file (start line/column to end line/column, subset of MicromarkToken).
|
||||
*
|
||||
* @typedef {Object} FileRange
|
||||
* @property {number} startLine Start line (1-based).
|
||||
* @property {number} startColumn Start column (1-based).
|
||||
* @property {number} endLine End line (1-based).
|
||||
* @property {number} endColumn End column (1-based).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns whether line/column A is less than or equal to line/column B.
|
||||
*
|
||||
* @param {number} lineA Line A.
|
||||
* @param {number} columnA Column A.
|
||||
* @param {number} lineB Line B.
|
||||
* @param {number} columnB Column B.
|
||||
* @returns {boolean} True iff A is less than or equal to B.
|
||||
*/
|
||||
const positionLessThanOrEqual = (lineA, columnA, lineB, columnB) => (
|
||||
(lineA < lineB) ||
|
||||
((lineA === lineB) && (columnA <= columnB))
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns whether two ranges (or MicromarkTokens) overlap anywhere.
|
||||
*
|
||||
* @param {FileRange|MicromarkToken} rangeA Range A.
|
||||
* @param {FileRange|MicromarkToken} rangeB Range B.
|
||||
* @returns {boolean} True iff the two ranges overlap.
|
||||
*/
|
||||
module.exports.hasOverlap = function hasOverlap(rangeA, rangeB) {
|
||||
const lte = positionLessThanOrEqual(rangeA.startLine, rangeA.startColumn, rangeB.startLine, rangeB.startColumn);
|
||||
const first = lte ? rangeA : rangeB;
|
||||
const second = lte ? rangeB : rangeA;
|
||||
return positionLessThanOrEqual(second.startLine, second.startColumn, first.endLine, first.endColumn);
|
||||
};
|
||||
|
||||
// Determines if the front matter includes a title
|
||||
module.exports.frontMatterHasTitle =
|
||||
function frontMatterHasTitle(/** @type {readonly string[]} */ frontMatterLines, /** @type {string} */ frontMatterTitlePattern) {
|
||||
const ignoreFrontMatter =
|
||||
(frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern;
|
||||
const frontMatterTitleRe =
|
||||
new RegExp(
|
||||
String(frontMatterTitlePattern || "^\\s*\"?title\"?\\s*[:=]"),
|
||||
"i"
|
||||
);
|
||||
return !ignoreFrontMatter &&
|
||||
frontMatterLines.some((line) => frontMatterTitleRe.test(line));
|
||||
};
|
||||
|
||||
/**
|
||||
* Result object for getReferenceLinkImageData.
|
||||
*
|
||||
* @typedef {Object} GetReferenceLinkImageDataResult
|
||||
* @property {Map<string, number[][]>} references References.
|
||||
* @property {Map<string, number[][]>} shortcuts Shortcuts.
|
||||
* @property {Map<string, [number, string]>} definitions Definitions.
|
||||
* @property {[string, number][]} duplicateDefinitions Duplicate definitions.
|
||||
* @property {number[]} definitionLineIndices Definition line indices.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns an object with information about reference links and images.
|
||||
*
|
||||
* @param {MicromarkToken[]} tokens Micromark tokens.
|
||||
* @returns {GetReferenceLinkImageDataResult} Reference link/image data.
|
||||
*/
|
||||
function getReferenceLinkImageData(tokens) {
|
||||
const normalizeReference = (/** @type {string} */ s) => s.toLowerCase().trim().replace(/\s+/g, " ");
|
||||
const getText = (/** @type {MicromarkToken} */ t) => t?.children.filter((c) => c.type !== "blockQuotePrefix").map((c) => c.text).join("");
|
||||
/** @type {Map<string, number[][]>} */
|
||||
const references = new Map();
|
||||
/** @type {Map<string, number[][]>} */
|
||||
const shortcuts = new Map();
|
||||
const addReferenceToDictionary = (/** @type {MicromarkToken} */ token, /** @type {string} */ label, /** @type {boolean} */ isShortcut) => {
|
||||
const referenceDatum = [
|
||||
token.startLine - 1,
|
||||
token.startColumn - 1,
|
||||
token.text.length
|
||||
];
|
||||
const reference = normalizeReference(label);
|
||||
const dictionary = isShortcut ? shortcuts : references;
|
||||
const referenceData = dictionary.get(reference) || [];
|
||||
referenceData.push(referenceDatum);
|
||||
dictionary.set(reference, referenceData);
|
||||
};
|
||||
/** @type {Map<string, [number, string]>} */
|
||||
const definitions = new Map();
|
||||
/** @type {number[]} */
|
||||
const definitionLineIndices = [];
|
||||
/** @type {[string, number][]} */
|
||||
const duplicateDefinitions = [];
|
||||
const filteredTokens =
|
||||
micromark.filterByTypes(
|
||||
tokens,
|
||||
[
|
||||
// definitionLineIndices
|
||||
"definition", "gfmFootnoteDefinition",
|
||||
// definitions and definitionLineIndices
|
||||
"definitionLabelString", "gfmFootnoteDefinitionLabelString",
|
||||
// references and shortcuts
|
||||
"gfmFootnoteCall", "image", "link",
|
||||
// undefined link labels
|
||||
"undefinedReferenceCollapsed", "undefinedReferenceFull", "undefinedReferenceShortcut"
|
||||
]
|
||||
);
|
||||
for (const token of filteredTokens) {
|
||||
let labelPrefix = "";
|
||||
// eslint-disable-next-line default-case
|
||||
switch (token.type) {
|
||||
case "definition":
|
||||
case "gfmFootnoteDefinition":
|
||||
// definitionLineIndices
|
||||
for (let i = token.startLine; i <= token.endLine; i++) {
|
||||
definitionLineIndices.push(i - 1);
|
||||
}
|
||||
break;
|
||||
case "gfmFootnoteDefinitionLabelString":
|
||||
labelPrefix = "^";
|
||||
case "definitionLabelString": // eslint-disable-line no-fallthrough
|
||||
{
|
||||
// definitions and definitionLineIndices
|
||||
const reference = normalizeReference(`${labelPrefix}${token.text}`);
|
||||
if (definitions.has(reference)) {
|
||||
duplicateDefinitions.push([ reference, token.startLine - 1 ]);
|
||||
} else {
|
||||
const parent =
|
||||
micromark.getParentOfType(token, [ "definition" ]);
|
||||
const destinationString = parent &&
|
||||
micromark.getDescendantsByType(parent, [ "definitionDestination", "definitionDestinationRaw", "definitionDestinationString" ])[0]?.text;
|
||||
definitions.set(
|
||||
reference,
|
||||
[ token.startLine - 1, destinationString || "" ]
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "gfmFootnoteCall":
|
||||
case "image":
|
||||
case "link":
|
||||
{
|
||||
// Identify if shortcut or full/collapsed
|
||||
let isShortcut = (token.children.length === 1);
|
||||
const isFullOrCollapsed = (token.children.length === 2) && !token.children.some((t) => t.type === "resource");
|
||||
const [ labelText ] = micromark.getDescendantsByType(token, [ "label", "labelText" ]);
|
||||
const [ referenceString ] = micromark.getDescendantsByType(token, [ "reference", "referenceString" ]);
|
||||
let label = getText(labelText);
|
||||
// Identify if footnote
|
||||
if (!isShortcut && !isFullOrCollapsed) {
|
||||
const [ footnoteCallMarker, footnoteCallString ] = token.children.filter(
|
||||
(t) => [ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ].includes(t.type)
|
||||
);
|
||||
if (footnoteCallMarker && footnoteCallString) {
|
||||
label = `${footnoteCallMarker.text}${footnoteCallString.text}`;
|
||||
isShortcut = true;
|
||||
}
|
||||
}
|
||||
// Track link (handle shortcuts separately due to ambiguity in "text [text] text")
|
||||
if (isShortcut || isFullOrCollapsed) {
|
||||
addReferenceToDictionary(token, getText(referenceString) || label, isShortcut);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "undefinedReferenceCollapsed":
|
||||
case "undefinedReferenceFull":
|
||||
case "undefinedReferenceShortcut":
|
||||
{
|
||||
const undefinedReference = micromark.getDescendantsByType(token, [ "undefinedReference" ])[0];
|
||||
const label = undefinedReference.children.map((t) => t.text).join("");
|
||||
const isShortcut = (token.type === "undefinedReferenceShortcut");
|
||||
addReferenceToDictionary(token, label, isShortcut);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
references,
|
||||
shortcuts,
|
||||
definitions,
|
||||
duplicateDefinitions,
|
||||
definitionLineIndices
|
||||
};
|
||||
}
|
||||
module.exports.getReferenceLinkImageData = getReferenceLinkImageData;
|
||||
|
||||
/**
|
||||
* Gets the most common line ending, falling back to the platform default.
|
||||
*
|
||||
* @param {string} input Markdown content to analyze.
|
||||
* @param {{EOL: string}} [os] Node.js "os" module.
|
||||
* @returns {string} Preferred line ending.
|
||||
*/
|
||||
function getPreferredLineEnding(input, os) {
|
||||
let cr = 0;
|
||||
let lf = 0;
|
||||
let crlf = 0;
|
||||
const endings = input.match(newLineRe) || [];
|
||||
for (const ending of endings) {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (ending) {
|
||||
case "\r":
|
||||
cr++;
|
||||
break;
|
||||
case "\n":
|
||||
lf++;
|
||||
break;
|
||||
case "\r\n":
|
||||
crlf++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let preferredLineEnding = null;
|
||||
if (!cr && !lf && !crlf) {
|
||||
preferredLineEnding = (os && os.EOL) || "\n";
|
||||
} else if ((lf >= crlf) && (lf >= cr)) {
|
||||
preferredLineEnding = "\n";
|
||||
} else if (crlf >= cr) {
|
||||
preferredLineEnding = "\r\n";
|
||||
} else {
|
||||
preferredLineEnding = "\r";
|
||||
}
|
||||
return preferredLineEnding;
|
||||
}
|
||||
module.exports.getPreferredLineEnding = getPreferredLineEnding;
|
||||
|
||||
/**
|
||||
* Expands a path with a tilde to an absolute path.
|
||||
*
|
||||
* @param {string} file Path that may begin with a tilde.
|
||||
* @param {{homedir: () => string}} os Node.js "os" module.
|
||||
* @returns {string} Absolute path (or original path).
|
||||
*/
|
||||
function expandTildePath(file, os) {
|
||||
const homedir = os && os.homedir && os.homedir();
|
||||
return homedir ? file.replace(/^~($|\/|\\)/, `${homedir}$1`) : file;
|
||||
}
|
||||
module.exports.expandTildePath = expandTildePath;
|
||||
|
||||
/** @typedef {import("../lib/markdownlint.mjs").LintError[]} LintErrors */
|
||||
/** @typedef {import("../lib/markdownlint.mjs").LintResults} LintResults */
|
||||
|
||||
/**
|
||||
* Converts lint errors from resultVersion 3 to 2.
|
||||
*
|
||||
* @param {LintErrors} errors Lint errors (v3).
|
||||
* @returns {LintErrors} Lint errors (v2).
|
||||
*/
|
||||
function convertLintErrorsVersion3To2(errors) {
|
||||
const noPrevious = {
|
||||
"ruleNames": [],
|
||||
"lineNumber": -1
|
||||
};
|
||||
return errors.filter((error, index, array) => {
|
||||
// @ts-ignore
|
||||
delete error.fixInfo;
|
||||
// @ts-ignore
|
||||
delete error.severity;
|
||||
const previous = array[index - 1] || noPrevious;
|
||||
return (
|
||||
(error.ruleNames[0] !== previous.ruleNames[0]) ||
|
||||
(error.lineNumber !== previous.lineNumber)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts lint errors from resultVersion 2 to 1.
|
||||
*
|
||||
* @param {LintErrors} errors Lint errors (v2).
|
||||
* @returns {LintErrors} Lint errors (v1).
|
||||
*/
|
||||
function convertLintErrorsVersion2To1(errors) {
|
||||
for (const error of errors) {
|
||||
// @ts-ignore
|
||||
error.ruleName = error.ruleNames[0];
|
||||
// @ts-ignore
|
||||
error.ruleAlias = error.ruleNames[1] || error.ruleName;
|
||||
// @ts-ignore
|
||||
delete error.ruleNames;
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts lint errors from resultVersion 2 to 0.
|
||||
*
|
||||
* @param {LintErrors} errors Lint errors (v2).
|
||||
* @returns {LintErrors} Lint errors (v0).
|
||||
*/
|
||||
function convertLintErrorsVersion2To0(errors) {
|
||||
/** @type {Object.<string, number[]>} */
|
||||
const dictionary = {};
|
||||
for (const error of errors) {
|
||||
const ruleName = error.ruleNames[0];
|
||||
const ruleLines = dictionary[ruleName] || [];
|
||||
ruleLines.push(error.lineNumber);
|
||||
dictionary[ruleName] = ruleLines;
|
||||
}
|
||||
// @ts-ignore
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies and transforms lint results from resultVersion 3 to ?.
|
||||
*
|
||||
* @param {LintResults} results Lint results (v3).
|
||||
* @param {(errors: LintErrors) => LintErrors} transform Lint errors (v?).
|
||||
* @returns {LintResults} Lint results (v?).
|
||||
*/
|
||||
function copyAndTransformResults(results, transform) {
|
||||
/** @type {Object.<string, LintErrors>} */
|
||||
const newResults = {};
|
||||
Object.defineProperty(newResults, "toString", { "value": results.toString });
|
||||
for (const key of Object.keys(results)) {
|
||||
const arr = results[key].map((r) => ({ ...r }));
|
||||
newResults[key] = transform(arr);
|
||||
}
|
||||
// @ts-ignore
|
||||
return newResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts lint results from resultVersion 3 to 0.
|
||||
*
|
||||
* @param {LintResults} results Lint results (v3).
|
||||
* @returns {LintResults} Lint results (v0).
|
||||
*/
|
||||
module.exports.convertToResultVersion0 = function convertToResultVersion0(results) {
|
||||
return copyAndTransformResults(results, (r) => convertLintErrorsVersion2To0(convertLintErrorsVersion3To2(r)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts lint results from resultVersion 3 to 1.
|
||||
*
|
||||
* @param {LintResults} results Lint results (v3).
|
||||
* @returns {LintResults} Lint results (v1).
|
||||
*/
|
||||
module.exports.convertToResultVersion1 = function convertToResultVersion1(results) {
|
||||
return copyAndTransformResults(results, (r) => convertLintErrorsVersion2To1(convertLintErrorsVersion3To2(r)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts lint results from resultVersion 3 to 2.
|
||||
*
|
||||
* @param {LintResults} results Lint results (v3).
|
||||
* @returns {LintResults} Lint results (v2).
|
||||
*/
|
||||
module.exports.convertToResultVersion2 = function convertToResultVersion2(results) {
|
||||
return copyAndTransformResults(results, convertLintErrorsVersion3To2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats lint results to an array of strings.
|
||||
*
|
||||
* @param {LintResults|undefined} lintResults Lint results.
|
||||
* @returns {string[]} Lint error strings.
|
||||
*/
|
||||
module.exports.formatLintResults = function formatLintResults(lintResults) {
|
||||
const results = [];
|
||||
const entries = Object.entries(lintResults || {});
|
||||
entries.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
for (const [ source, lintErrors ] of entries) {
|
||||
for (const lintError of lintErrors) {
|
||||
const { lineNumber, ruleNames, ruleDescription, errorDetail, errorContext, errorRange, severity } = lintError;
|
||||
const rule = ruleNames.join("/");
|
||||
const line = `:${lineNumber}`;
|
||||
const rangeStart = (errorRange && errorRange[0]) || 0;
|
||||
const column = rangeStart ? `:${rangeStart}` : "";
|
||||
const description = ruleDescription;
|
||||
const detail = (errorDetail ? ` [${errorDetail}]` : "");
|
||||
const context = (errorContext ? ` [Context: "${errorContext}"]` : "");
|
||||
results.push(`${source}${line}${column} ${severity} ${rule} ${description}${detail}${context}`);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
};
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const { flatTokensSymbol, htmlFlowSymbol, newLineRe } = require("./shared.cjs");
|
||||
|
||||
// eslint-disable-next-line jsdoc/valid-types
|
||||
/** @typedef {import("micromark-util-types", { with: { "resolution-mode": "import" } }).TokenType} TokenType */
|
||||
/** @typedef {import("../lib/exports.mjs").MicromarkToken} Token */
|
||||
// eslint-disable-next-line jsdoc/valid-types
|
||||
/** @typedef {import("../lib/micromark-types.d.mts", { with: { "resolution-mode": "import" } })} */
|
||||
|
||||
/**
|
||||
* Determines if a Micromark token is within an htmlFlow type.
|
||||
*
|
||||
* @param {Token} token Micromark token.
|
||||
* @returns {boolean} True iff the token is within an htmlFlow type.
|
||||
*/
|
||||
function inHtmlFlow(token) {
|
||||
// @ts-ignore
|
||||
return Boolean(token[htmlFlowSymbol]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a token is an htmlFlow type containing an HTML comment.
|
||||
*
|
||||
* @param {Token} token Micromark token.
|
||||
* @returns {boolean} True iff token is htmlFlow containing a comment.
|
||||
*/
|
||||
function isHtmlFlowComment(token) {
|
||||
const { text, type } = token;
|
||||
if (
|
||||
(type === "htmlFlow") &&
|
||||
text.startsWith("<!--") &&
|
||||
text.endsWith("-->")
|
||||
) {
|
||||
const comment = text.slice(4, -3);
|
||||
return (
|
||||
!comment.startsWith(">") &&
|
||||
!comment.startsWith("->") &&
|
||||
!comment.endsWith("-")
|
||||
// The following condition from the CommonMark specification is commented
|
||||
// to avoid parsing HTML comments that include "--" because that is NOT a
|
||||
// condition of the HTML specification.
|
||||
// https://spec.commonmark.org/0.30/#raw-html
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#comments
|
||||
// && !comment.includes("--")
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a range of numbers to a set.
|
||||
*
|
||||
* @param {Set<number>} set Set of numbers.
|
||||
* @param {number} start Starting number.
|
||||
* @param {number} end Ending number.
|
||||
* @returns {void}
|
||||
*/
|
||||
function addRangeToSet(set, start, end) {
|
||||
for (let i = start; i <= end; i++) {
|
||||
set.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback AllowedPredicate
|
||||
* @param {Token} token Micromark token.
|
||||
* @returns {boolean} True iff allowed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback TransformPredicate
|
||||
* @param {Token} token Micromark token.
|
||||
* @returns {Token[]} Child tokens.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Filter a list of Micromark tokens by predicate.
|
||||
*
|
||||
* @param {Token[]} tokens Micromark tokens.
|
||||
* @param {AllowedPredicate} allowed Allowed token predicate.
|
||||
* @param {TransformPredicate} [transformChildren] Transform predicate.
|
||||
* @returns {Token[]} Filtered tokens.
|
||||
*/
|
||||
function filterByPredicate(tokens, allowed, transformChildren) {
|
||||
const result = [];
|
||||
const queue = [
|
||||
{
|
||||
"array": tokens,
|
||||
"index": 0
|
||||
}
|
||||
];
|
||||
while (queue.length > 0) {
|
||||
const current = queue[queue.length - 1];
|
||||
const { array, index } = current;
|
||||
if (index < array.length) {
|
||||
const token = array[current.index++];
|
||||
if (allowed(token)) {
|
||||
result.push(token);
|
||||
}
|
||||
const { children } = token;
|
||||
if (children.length > 0) {
|
||||
const transformed =
|
||||
transformChildren ? transformChildren(token) : children;
|
||||
queue.push(
|
||||
{
|
||||
"array": transformed,
|
||||
"index": 0
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
queue.pop();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of Micromark tokens by type.
|
||||
*
|
||||
* @param {Token[]} tokens Micromark tokens.
|
||||
* @param {TokenType[]} types Types to allow.
|
||||
* @param {boolean} [htmlFlow] Whether to include htmlFlow content.
|
||||
* @returns {Token[]} Filtered tokens.
|
||||
*/
|
||||
function filterByTypes(tokens, types, htmlFlow) {
|
||||
const predicate = (/** @type {Token} */ token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token));
|
||||
/** @type {Token[]} */
|
||||
const flatTokens =
|
||||
// @ts-ignore
|
||||
tokens[flatTokensSymbol];
|
||||
if (flatTokens) {
|
||||
return flatTokens.filter(predicate);
|
||||
}
|
||||
return filterByPredicate(tokens, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blockquote prefix text (if any) for the specified line number.
|
||||
*
|
||||
* @param {Token[]} tokens Micromark tokens.
|
||||
* @param {number} lineNumber Line number to examine.
|
||||
* @param {number} [count] Number of times to repeat.
|
||||
* @returns {string} Blockquote prefix text.
|
||||
*/
|
||||
function getBlockQuotePrefixText(tokens, lineNumber, count = 1) {
|
||||
return filterByTypes(tokens, [ "blockQuotePrefix", "linePrefix" ])
|
||||
.filter((prefix) => prefix.startLine === lineNumber)
|
||||
.map((prefix) => prefix.text)
|
||||
.join("")
|
||||
.trimEnd()
|
||||
// eslint-disable-next-line unicorn/prefer-spread
|
||||
.concat("\n")
|
||||
.repeat(count);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a list of nested Micromark token descendants by type path.
|
||||
*
|
||||
* @param {Token|Token[]} parent Micromark token parent or parents.
|
||||
* @param {(TokenType|TokenType[])[]} typePath Micromark token type path.
|
||||
* @returns {Token[]} Micromark token descendants.
|
||||
*/
|
||||
function getDescendantsByType(parent, typePath) {
|
||||
let tokens = Array.isArray(parent) ? parent : [ parent ];
|
||||
for (const type of typePath) {
|
||||
const predicate = (/** @type {Token} */ token) => Array.isArray(type) ? type.includes(token.type) : (type === token.type);
|
||||
tokens = tokens.flatMap((t) => t.children.filter(predicate));
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the heading level of a Micromark heading tokan.
|
||||
*
|
||||
* @param {Token} heading Micromark heading token.
|
||||
* @returns {number} Heading level.
|
||||
*/
|
||||
function getHeadingLevel(heading) {
|
||||
let level = 1;
|
||||
const headingSequence = heading.children.find(
|
||||
(child) => [ "atxHeadingSequence", "setextHeadingLine" ].includes(child.type)
|
||||
);
|
||||
// @ts-ignore
|
||||
const { text } = headingSequence;
|
||||
if (text[0] === "#") {
|
||||
level = Math.min(text.length, 6);
|
||||
} else if (text[0] === "-") {
|
||||
level = 2;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the heading style of a Micromark heading tokan.
|
||||
*
|
||||
* @param {Token} heading Micromark heading token.
|
||||
* @returns {"atx" | "atx_closed" | "setext"} Heading style.
|
||||
*/
|
||||
function getHeadingStyle(heading) {
|
||||
if (heading.type === "setextHeading") {
|
||||
return "setext";
|
||||
}
|
||||
const atxHeadingSequenceLength = heading.children.filter(
|
||||
(child) => child.type === "atxHeadingSequence"
|
||||
).length;
|
||||
if (atxHeadingSequenceLength === 1) {
|
||||
return "atx";
|
||||
}
|
||||
return "atx_closed";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the heading text of a Micromark heading token.
|
||||
*
|
||||
* @param {Token} heading Micromark heading token.
|
||||
* @returns {string} Heading text.
|
||||
*/
|
||||
function getHeadingText(heading) {
|
||||
return getDescendantsByType(heading, [ [ "atxHeadingText", "setextHeadingText" ] ])
|
||||
.flatMap((descendant) => descendant.children.filter((child) => child.type !== "htmlText"))
|
||||
.map((data) => data.text)
|
||||
.join("")
|
||||
.replace(newLineRe, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML tag information.
|
||||
*
|
||||
* @typedef {Object} HtmlTagInfo
|
||||
* @property {boolean} close True iff close tag.
|
||||
* @property {string} name Tag name.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets information about the tag in an HTML token.
|
||||
*
|
||||
* @param {Token} token Micromark token.
|
||||
* @returns {HtmlTagInfo | null} HTML tag information.
|
||||
*/
|
||||
function getHtmlTagInfo(token) {
|
||||
const htmlTagNameRe = /^<([^!>][^/\s>]*)/;
|
||||
if (token.type === "htmlText") {
|
||||
const match = htmlTagNameRe.exec(token.text);
|
||||
if (match) {
|
||||
const name = match[1];
|
||||
const close = name.startsWith("/");
|
||||
return {
|
||||
close,
|
||||
"name": close ? name.slice(1) : name
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest parent of the specified type for a Micromark token.
|
||||
*
|
||||
* @param {Token} token Micromark token.
|
||||
* @param {TokenType[]} types Types to allow.
|
||||
* @returns {Token | null} Parent token.
|
||||
*/
|
||||
function getParentOfType(token, types) {
|
||||
/** @type {Token | null} */
|
||||
let current = token;
|
||||
while ((current = current.parent) && !types.includes(current.type)) {
|
||||
// Empty
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
const docfxTabSyntaxRe = /^#tab\//;
|
||||
|
||||
/**
|
||||
* Returns whether the specified Micromark token looks like a Docfx tab.
|
||||
*
|
||||
* @param {Token | null} heading Micromark token.
|
||||
* @returns {boolean} True iff the token looks like a Docfx tab.
|
||||
*/
|
||||
function isDocfxTab(heading) {
|
||||
// See https://dotnet.github.io/docfx/docs/markdown.html?tabs=linux%2Cdotnet#tabs
|
||||
if (heading?.type === "atxHeading") {
|
||||
const headingTexts = getDescendantsByType(heading, [ "atxHeadingText" ]);
|
||||
if ((headingTexts.length === 1) && (headingTexts[0].children.length === 1) && (headingTexts[0].children[0].type === "link")) {
|
||||
const resourceDestinationStrings = filterByTypes(headingTexts[0].children[0].children, [ "resourceDestinationString" ]);
|
||||
return (resourceDestinationStrings.length === 1) && docfxTabSyntaxRe.test(resourceDestinationStrings[0].text);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set containing token types that do not contain content.
|
||||
*
|
||||
* @type {Set<TokenType>}
|
||||
*/
|
||||
const nonContentTokens = new Set([
|
||||
"blockQuoteMarker",
|
||||
"blockQuotePrefix",
|
||||
"blockQuotePrefixWhitespace",
|
||||
"gfmFootnoteDefinitionIndent",
|
||||
"lineEnding",
|
||||
"lineEndingBlank",
|
||||
"linePrefix",
|
||||
"listItemIndent",
|
||||
"undefinedReference",
|
||||
"undefinedReferenceCollapsed",
|
||||
"undefinedReferenceFull",
|
||||
"undefinedReferenceShortcut"
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
addRangeToSet,
|
||||
filterByPredicate,
|
||||
filterByTypes,
|
||||
getBlockQuotePrefixText,
|
||||
getDescendantsByType,
|
||||
getHeadingLevel,
|
||||
getHeadingStyle,
|
||||
getHeadingText,
|
||||
getHtmlTagInfo,
|
||||
getParentOfType,
|
||||
inHtmlFlow,
|
||||
isDocfxTab,
|
||||
isHtmlFlowComment,
|
||||
nonContentTokens
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "markdownlint-rule-helpers",
|
||||
"version": "0.30.0",
|
||||
"description": "A collection of markdownlint helper functions for custom rules",
|
||||
"main": "./helpers.cjs",
|
||||
"exports": {
|
||||
".": "./helpers.cjs",
|
||||
"./micromark": "./micromark-helpers.cjs"
|
||||
},
|
||||
"author": "David Anson (https://dlaa.me/)",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/DavidAnson/markdownlint",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/DavidAnson/markdownlint.git"
|
||||
},
|
||||
"bugs": "https://github.com/DavidAnson/markdownlint/issues",
|
||||
"funding": "https://github.com/sponsors/DavidAnson",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"keywords": [
|
||||
"markdownlint",
|
||||
"markdownlint-rule"
|
||||
]
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
// Symbol for identifing the flat tokens array from micromark parse
|
||||
module.exports.flatTokensSymbol = Symbol("flat-tokens");
|
||||
|
||||
// Symbol for identifying the htmlFlow token from micromark parse
|
||||
module.exports.htmlFlowSymbol = Symbol("html-flow");
|
||||
|
||||
// Regular expression for matching common newline characters
|
||||
// See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
|
||||
module.exports.newLineRe = /\r\n?|\n/g;
|
||||
|
||||
// Regular expression for matching next lines
|
||||
module.exports.nextLinesRe = /[\r\n][\s\S]*$/;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// @ts-check
|
||||
|
||||
import { getReferenceLinkImageData as helpersGetReferenceLinkImageData } from "../helpers/helpers.cjs";
|
||||
import { filterByTypes } from "../helpers/micromark-helpers.cjs";
|
||||
|
||||
/** @typedef {import("markdownlint").RuleParams} RuleParams */
|
||||
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
|
||||
/** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */
|
||||
/** @typedef {import("../helpers/helpers.cjs").GetReferenceLinkImageDataResult} GetReferenceLinkImageDataResult */
|
||||
|
||||
/** @type {Map<string, object>} */
|
||||
const map = new Map();
|
||||
/** @type {RuleParams | undefined} */
|
||||
let params = undefined;
|
||||
|
||||
/**
|
||||
* Initializes (resets) the cache.
|
||||
*
|
||||
* @param {RuleParams} [p] Rule parameters object.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function initialize(p) {
|
||||
map.clear();
|
||||
params = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cached Micromark token array (for testing).
|
||||
*
|
||||
* @returns {MicromarkToken[]} Micromark tokens.
|
||||
*/
|
||||
export function micromarkTokens() {
|
||||
return params?.parsers.micromark.tokens || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached object value - computes it and caches it.
|
||||
*
|
||||
* @param {string} name Cache object name.
|
||||
* @param {() => Object} getValue Getter for object value.
|
||||
* @returns {Object} Object value.
|
||||
*/
|
||||
function getCached(name, getValue) {
|
||||
if (map.has(name)) {
|
||||
// @ts-ignore
|
||||
return map.get(name);
|
||||
}
|
||||
const value = getValue();
|
||||
map.set(name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a list of Micromark tokens by type and caches the result.
|
||||
*
|
||||
* @param {MicromarkTokenType[]} types Types to allow.
|
||||
* @param {boolean} [htmlFlow] Whether to include htmlFlow content.
|
||||
* @returns {MicromarkToken[]} Filtered tokens.
|
||||
*/
|
||||
export function filterByTypesCached(types, htmlFlow) {
|
||||
// @ts-ignore
|
||||
return getCached(
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
JSON.stringify(arguments),
|
||||
() => filterByTypes(micromarkTokens(), types, htmlFlow)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference link and image data object.
|
||||
*
|
||||
* @returns {GetReferenceLinkImageDataResult} Reference link and image data object.
|
||||
*/
|
||||
export function getReferenceLinkImageData() {
|
||||
// @ts-ignore
|
||||
return getCached(
|
||||
getReferenceLinkImageData.name,
|
||||
() => helpersGetReferenceLinkImageData(micromarkTokens())
|
||||
);
|
||||
}
|
||||
+2409
File diff suppressed because it is too large
Load Diff
+8
@@ -0,0 +1,8 @@
|
||||
import type { ConfigurationStrict } from "./configuration-strict.d.ts";
|
||||
|
||||
export interface Configuration extends ConfigurationStrict {
|
||||
/**
|
||||
* Index signature for arbitrary custom rules.
|
||||
*/
|
||||
[k: string]: unknown;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {string[]} */
|
||||
export const deprecatedRuleNames = [];
|
||||
export const fixableRuleNames = [
|
||||
"MD004", "MD005", "MD007", "MD009", "MD010", "MD011",
|
||||
"MD012", "MD014", "MD018", "MD019", "MD020", "MD021",
|
||||
"MD022", "MD023", "MD026", "MD027", "MD029", "MD030",
|
||||
"MD031", "MD032", "MD034", "MD037", "MD038", "MD039",
|
||||
"MD044", "MD047", "MD049", "MD050", "MD051", "MD053",
|
||||
"MD054", "MD058"
|
||||
];
|
||||
export const homepage = "https://github.com/DavidAnson/markdownlint";
|
||||
export const version = "0.40.0";
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
/* eslint-disable jsdoc/reject-any-type */
|
||||
|
||||
/**
|
||||
* Calls require for markdownit.cjs. Used to synchronously defer loading because module.createRequire is buggy under webpack (https://github.com/webpack/webpack/issues/16724).
|
||||
*
|
||||
* @returns {any} Exported module content.
|
||||
*/
|
||||
function requireMarkdownItCjs() {
|
||||
return require("./markdownit.cjs");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requireMarkdownItCjs
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { lintAsync as lint, readConfigAsync as readConfig } from "./markdownlint.mjs";
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
export { lintAsync as lint, readConfigAsync as readConfig } from "./markdownlint.mjs";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { extendConfigPromise as extendConfig, lintPromise as lint, readConfigPromise as readConfig } from "./markdownlint.mjs";
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
export { extendConfigPromise as extendConfig, lintPromise as lint, readConfigPromise as readConfig } from "./markdownlint.mjs";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { lintSync as lint, readConfigSync as readConfig } from "./markdownlint.mjs";
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
export { lintSync as lint, readConfigSync as readConfig } from "./markdownlint.mjs";
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
export { resolveModule } from "./resolve-module.cjs";
|
||||
export type Configuration = import("./markdownlint.mjs").Configuration;
|
||||
export type ConfigurationParser = import("./markdownlint.mjs").ConfigurationParser;
|
||||
export type ConfigurationStrict = import("./markdownlint.mjs").ConfigurationStrict;
|
||||
export type FixInfo = import("./markdownlint.mjs").FixInfo;
|
||||
export type FixInfoNormalized = import("./markdownlint.mjs").FixInfoNormalized;
|
||||
export type LintCallback = import("./markdownlint.mjs").LintCallback;
|
||||
export type LintContentCallback = import("./markdownlint.mjs").LintContentCallback;
|
||||
export type LintError = import("./markdownlint.mjs").LintError;
|
||||
export type LintResults = import("./markdownlint.mjs").LintResults;
|
||||
export type MarkdownItFactory = import("./markdownlint.mjs").MarkdownItFactory;
|
||||
export type MarkdownItToken = import("./markdownlint.mjs").MarkdownItToken;
|
||||
export type MarkdownParsers = import("./markdownlint.mjs").MarkdownParsers;
|
||||
export type MicromarkToken = import("./markdownlint.mjs").MicromarkToken;
|
||||
export type MicromarkTokenType = import("./markdownlint.mjs").MicromarkTokenType;
|
||||
export type Options = import("./markdownlint.mjs").Options;
|
||||
export type ParserMarkdownIt = import("./markdownlint.mjs").ParserMarkdownIt;
|
||||
export type ParserMicromark = import("./markdownlint.mjs").ParserMicromark;
|
||||
export type Plugin = import("./markdownlint.mjs").Plugin;
|
||||
export type ReadConfigCallback = import("./markdownlint.mjs").ReadConfigCallback;
|
||||
export type ResolveConfigExtendsCallback = import("./markdownlint.mjs").ResolveConfigExtendsCallback;
|
||||
export type Rule = import("./markdownlint.mjs").Rule;
|
||||
export type RuleConfiguration = import("./markdownlint.mjs").RuleConfiguration;
|
||||
export type RuleFunction = import("./markdownlint.mjs").RuleFunction;
|
||||
export type RuleOnError = import("./markdownlint.mjs").RuleOnError;
|
||||
export type RuleOnErrorFixInfo = import("./markdownlint.mjs").RuleOnErrorFixInfo;
|
||||
export type RuleOnErrorInfo = import("./markdownlint.mjs").RuleOnErrorInfo;
|
||||
export type RuleParams = import("./markdownlint.mjs").RuleParams;
|
||||
export { applyFix, applyFixes, getVersion } from "./markdownlint.mjs";
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// @ts-check
|
||||
|
||||
export { applyFix, applyFixes, getVersion } from "./markdownlint.mjs";
|
||||
export { resolveModule } from "./resolve-module.cjs";
|
||||
|
||||
/** @typedef {import("./markdownlint.mjs").Configuration} Configuration */
|
||||
/** @typedef {import("./markdownlint.mjs").ConfigurationParser} ConfigurationParser */
|
||||
/** @typedef {import("./markdownlint.mjs").ConfigurationStrict} ConfigurationStrict */
|
||||
/** @typedef {import("./markdownlint.mjs").FixInfo} FixInfo */
|
||||
/** @typedef {import("./markdownlint.mjs").FixInfoNormalized} FixInfoNormalized */
|
||||
/** @typedef {import("./markdownlint.mjs").LintCallback} LintCallback */
|
||||
/** @typedef {import("./markdownlint.mjs").LintContentCallback} LintContentCallback */
|
||||
/** @typedef {import("./markdownlint.mjs").LintError} LintError */
|
||||
/** @typedef {import("./markdownlint.mjs").LintResults} LintResults */
|
||||
/** @typedef {import("./markdownlint.mjs").MarkdownItFactory} MarkdownItFactory */
|
||||
/** @typedef {import("./markdownlint.mjs").MarkdownItToken} MarkdownItToken */
|
||||
/** @typedef {import("./markdownlint.mjs").MarkdownParsers} MarkdownParsers */
|
||||
/** @typedef {import("./markdownlint.mjs").MicromarkToken} MicromarkToken */
|
||||
/** @typedef {import("./markdownlint.mjs").MicromarkTokenType} MicromarkTokenType */
|
||||
/** @typedef {import("./markdownlint.mjs").Options} Options */
|
||||
/** @typedef {import("./markdownlint.mjs").ParserMarkdownIt} ParserMarkdownIt */
|
||||
/** @typedef {import("./markdownlint.mjs").ParserMicromark} ParserMicromark */
|
||||
/** @typedef {import("./markdownlint.mjs").Plugin} Plugin */
|
||||
/** @typedef {import("./markdownlint.mjs").ReadConfigCallback} ReadConfigCallback */
|
||||
/** @typedef {import("./markdownlint.mjs").ResolveConfigExtendsCallback} ResolveConfigExtendsCallback */
|
||||
/** @typedef {import("./markdownlint.mjs").Rule} Rule */
|
||||
/** @typedef {import("./markdownlint.mjs").RuleConfiguration} RuleConfiguration */
|
||||
/** @typedef {import("./markdownlint.mjs").RuleFunction} RuleFunction */
|
||||
/** @typedef {import("./markdownlint.mjs").RuleOnError} RuleOnError */
|
||||
/** @typedef {import("./markdownlint.mjs").RuleOnErrorFixInfo} RuleOnErrorFixInfo */
|
||||
/** @typedef {import("./markdownlint.mjs").RuleOnErrorInfo} RuleOnErrorInfo */
|
||||
/** @typedef {import("./markdownlint.mjs").RuleParams} RuleParams */
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const { newLineRe } = require("../helpers");
|
||||
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/52529
|
||||
/** @typedef {import("markdownlint").MarkdownIt} MarkdownIt */
|
||||
/** @typedef {import("markdownlint").MarkdownItToken} MarkdownItToken */
|
||||
/** @typedef {import("markdownlint").Plugin} Plugin */
|
||||
|
||||
/**
|
||||
* @callback InlineCodeSpanCallback
|
||||
* @param {string} code Code content.
|
||||
* @param {number} lineIndex Line index (0-based).
|
||||
* @param {number} columnIndex Column index (0-based).
|
||||
* @param {number} ticks Count of backticks.
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calls the provided function for each inline code span's content.
|
||||
*
|
||||
* @param {string} input Markdown content.
|
||||
* @param {InlineCodeSpanCallback} handler Callback function taking (code,
|
||||
* lineIndex, columnIndex, ticks).
|
||||
* @returns {void}
|
||||
*/
|
||||
function forEachInlineCodeSpan(input, handler) {
|
||||
const backtickRe = /`+/g;
|
||||
let match = null;
|
||||
const backticksLengthAndIndex = [];
|
||||
while ((match = backtickRe.exec(input)) !== null) {
|
||||
backticksLengthAndIndex.push([ match[0].length, match.index ]);
|
||||
}
|
||||
const newLinesIndex = [];
|
||||
while ((match = newLineRe.exec(input)) !== null) {
|
||||
newLinesIndex.push(match.index);
|
||||
}
|
||||
let lineIndex = 0;
|
||||
let lineStartIndex = 0;
|
||||
let k = 0;
|
||||
for (let i = 0; i < backticksLengthAndIndex.length - 1; i++) {
|
||||
const [ startLength, startIndex ] = backticksLengthAndIndex[i];
|
||||
if ((startIndex === 0) || (input[startIndex - 1] !== "\\")) {
|
||||
for (let j = i + 1; j < backticksLengthAndIndex.length; j++) {
|
||||
const [ endLength, endIndex ] = backticksLengthAndIndex[j];
|
||||
if (startLength === endLength) {
|
||||
for (; k < newLinesIndex.length; k++) {
|
||||
const newLineIndex = newLinesIndex[k];
|
||||
if (startIndex < newLineIndex) {
|
||||
break;
|
||||
}
|
||||
lineIndex++;
|
||||
lineStartIndex = newLineIndex + 1;
|
||||
}
|
||||
const columnIndex = startIndex - lineStartIndex + startLength;
|
||||
handler(
|
||||
input.slice(startIndex + startLength, endIndex),
|
||||
lineIndex,
|
||||
columnIndex,
|
||||
startLength
|
||||
);
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze all freeze-able members of a token and its children.
|
||||
*
|
||||
* @param {MarkdownItToken} token A markdown-it token.
|
||||
* @returns {void}
|
||||
*/
|
||||
function freezeToken(token) {
|
||||
if (token.attrs) {
|
||||
for (const attr of token.attrs) {
|
||||
Object.freeze(attr);
|
||||
}
|
||||
Object.freeze(token.attrs);
|
||||
}
|
||||
if (token.children) {
|
||||
for (const child of token.children) {
|
||||
freezeToken(child);
|
||||
}
|
||||
Object.freeze(token.children);
|
||||
}
|
||||
if (token.map) {
|
||||
Object.freeze(token.map);
|
||||
}
|
||||
Object.freeze(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotate tokens with line/lineNumber and freeze them.
|
||||
*
|
||||
* @param {import("markdown-it").Token[]} tokens Array of markdown-it tokens.
|
||||
* @param {string[]} lines Lines of Markdown content.
|
||||
* @returns {void}
|
||||
*/
|
||||
function annotateAndFreezeTokens(tokens, lines) {
|
||||
let trMap = null;
|
||||
/** @type {MarkdownItToken[]} */
|
||||
// @ts-ignore
|
||||
const markdownItTokens = tokens;
|
||||
for (const token of markdownItTokens) {
|
||||
// Provide missing maps for table content
|
||||
if (token.type === "tr_open") {
|
||||
trMap = token.map;
|
||||
} else if (token.type === "tr_close") {
|
||||
trMap = null;
|
||||
}
|
||||
if (!token.map && trMap) {
|
||||
token.map = [ ...trMap ];
|
||||
}
|
||||
// Update token metadata
|
||||
if (token.map) {
|
||||
token.line = lines[token.map[0]];
|
||||
token.lineNumber = token.map[0] + 1;
|
||||
// Trim bottom of token to exclude whitespace lines
|
||||
while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) {
|
||||
token.map[1]--;
|
||||
}
|
||||
}
|
||||
// Annotate children with lineNumber
|
||||
if (token.children) {
|
||||
/** @type {number[]} */
|
||||
const codeSpanExtraLines = [];
|
||||
if (token.children.some((child) => child.type === "code_inline")) {
|
||||
forEachInlineCodeSpan(token.content, (code) => {
|
||||
codeSpanExtraLines.push(code.split(newLineRe).length - 1);
|
||||
});
|
||||
}
|
||||
let lineNumber = token.lineNumber;
|
||||
for (const child of token.children) {
|
||||
child.lineNumber = lineNumber;
|
||||
child.line = lines[lineNumber - 1];
|
||||
if ((child.type === "softbreak") || (child.type === "hardbreak")) {
|
||||
lineNumber++;
|
||||
} else if (child.type === "code_inline") {
|
||||
lineNumber += codeSpanExtraLines.shift() || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
freezeToken(token);
|
||||
}
|
||||
Object.freeze(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of markdown-it tokens for the input.
|
||||
*
|
||||
* @param {MarkdownIt} markdownIt Instance of the markdown-it parser.
|
||||
* @param {string} content Markdown content.
|
||||
* @param {string[]} lines Lines of Markdown content.
|
||||
* @returns {MarkdownItToken[]} Array of markdown-it tokens.
|
||||
*/
|
||||
function getMarkdownItTokens(markdownIt, content, lines) {
|
||||
const tokens = markdownIt.parse(content, {});
|
||||
annotateAndFreezeTokens(tokens, lines);
|
||||
return tokens;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
forEachInlineCodeSpan,
|
||||
getMarkdownItTokens
|
||||
};
|
||||
+600
@@ -0,0 +1,600 @@
|
||||
/**
|
||||
* Lint specified Markdown files.
|
||||
*
|
||||
* @param {Options | null} options Configuration options.
|
||||
* @param {LintCallback} callback Callback (err, result) function.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function lintAsync(options: Options | null, callback: LintCallback): void;
|
||||
/**
|
||||
* Lint specified Markdown files.
|
||||
*
|
||||
* @param {Options | null} options Configuration options.
|
||||
* @returns {Promise<LintResults>} Results object.
|
||||
*/
|
||||
export function lintPromise(options: Options | null): Promise<LintResults>;
|
||||
/**
|
||||
* Lint specified Markdown files.
|
||||
*
|
||||
* @param {Options | null} options Configuration options.
|
||||
* @returns {LintResults} Results object.
|
||||
*/
|
||||
export function lintSync(options: Options | null): LintResults;
|
||||
/**
|
||||
* Extend specified configuration object.
|
||||
*
|
||||
* @param {Configuration} config Configuration object.
|
||||
* @param {string} file Configuration file name.
|
||||
* @param {ConfigurationParser[] | undefined} parsers Parsing function(s).
|
||||
* @param {FsLike} fs File system implementation.
|
||||
* @returns {Promise<Configuration>} Configuration object.
|
||||
*/
|
||||
export function extendConfigPromise(config: Configuration, file: string, parsers: ConfigurationParser[] | undefined, fs: FsLike): Promise<Configuration>;
|
||||
/**
|
||||
* Read specified configuration file.
|
||||
*
|
||||
* @param {string} file Configuration file name.
|
||||
* @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing function(s).
|
||||
* @param {FsLike | ReadConfigCallback} [fs] File system implementation.
|
||||
* @param {ReadConfigCallback} [callback] Callback (err, result) function.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function readConfigAsync(file: string, parsers?: ConfigurationParser[] | ReadConfigCallback, fs?: FsLike | ReadConfigCallback, callback?: ReadConfigCallback): void;
|
||||
/**
|
||||
* Read specified configuration file.
|
||||
*
|
||||
* @param {string} file Configuration file name.
|
||||
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
|
||||
* @param {FsLike} [fs] File system implementation.
|
||||
* @returns {Promise<Configuration>} Configuration object.
|
||||
*/
|
||||
export function readConfigPromise(file: string, parsers?: ConfigurationParser[], fs?: FsLike): Promise<Configuration>;
|
||||
/**
|
||||
* Read specified configuration file.
|
||||
*
|
||||
* @param {string} file Configuration file name.
|
||||
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
|
||||
* @param {FsLike} [fs] File system implementation.
|
||||
* @returns {Configuration} Configuration object.
|
||||
*/
|
||||
export function readConfigSync(file: string, parsers?: ConfigurationParser[], fs?: FsLike): Configuration;
|
||||
/**
|
||||
* Applies the specified fix to a Markdown content line.
|
||||
*
|
||||
* @param {string} line Line of Markdown content.
|
||||
* @param {FixInfo} fixInfo FixInfo instance.
|
||||
* @param {string} [lineEnding] Line ending to use.
|
||||
* @returns {string | null} Fixed content or null if deleted.
|
||||
*/
|
||||
export function applyFix(line: string, fixInfo: FixInfo, lineEnding?: string): string | null;
|
||||
/**
|
||||
* Applies as many of the specified fixes as possible to Markdown content.
|
||||
*
|
||||
* @param {string} input Lines of Markdown content.
|
||||
* @param {LintError[]} errors LintError instances.
|
||||
* @returns {string} Fixed content.
|
||||
*/
|
||||
export function applyFixes(input: string, errors: LintError[]): string;
|
||||
/**
|
||||
* Gets the (semantic) version of the library.
|
||||
*
|
||||
* @returns {string} SemVer string.
|
||||
*/
|
||||
export function getVersion(): string;
|
||||
/**
|
||||
* Result object for removeFrontMatter.
|
||||
*/
|
||||
export type RemoveFrontMatterResult = {
|
||||
/**
|
||||
* Markdown content.
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* Front matter lines.
|
||||
*/
|
||||
frontMatterLines: string[];
|
||||
};
|
||||
/**
|
||||
* Result object for getEffectiveConfig.
|
||||
*/
|
||||
export type GetEffectiveConfigResult = {
|
||||
/**
|
||||
* Effective configuration.
|
||||
*/
|
||||
effectiveConfig: Configuration;
|
||||
/**
|
||||
* Rules enabled.
|
||||
*/
|
||||
rulesEnabled: Map<string, boolean>;
|
||||
/**
|
||||
* Rules severity.
|
||||
*/
|
||||
rulesSeverity: Map<string, "error" | "warning">;
|
||||
};
|
||||
/**
|
||||
* Result object for getEnabledRulesPerLineNumber.
|
||||
*/
|
||||
export type EnabledRulesPerLineNumberResult = {
|
||||
/**
|
||||
* Effective configuration.
|
||||
*/
|
||||
effectiveConfig: Configuration;
|
||||
/**
|
||||
* Enabled rules per line number.
|
||||
*/
|
||||
enabledRulesPerLineNumber: Map<string, boolean>[];
|
||||
/**
|
||||
* Enabled rule list.
|
||||
*/
|
||||
enabledRuleList: Rule[];
|
||||
/**
|
||||
* Rules severity.
|
||||
*/
|
||||
rulesSeverity: Map<string, "error" | "warning">;
|
||||
};
|
||||
/**
|
||||
* Node fs instance (or compatible object).
|
||||
*/
|
||||
export type FsLike = {
|
||||
/**
|
||||
* access method.
|
||||
*/
|
||||
access: (path: string, callback: (err: Error) => void) => void;
|
||||
/**
|
||||
* accessSync method.
|
||||
*/
|
||||
accessSync: (path: string) => void;
|
||||
/**
|
||||
* readFile method.
|
||||
*/
|
||||
readFile: (path: string, encoding: string, callback: (err: Error, data: string) => void) => void;
|
||||
/**
|
||||
* readFileSync method.
|
||||
*/
|
||||
readFileSync: (path: string, encoding: string) => string;
|
||||
};
|
||||
/**
|
||||
* Function to implement rule logic.
|
||||
*/
|
||||
export type RuleFunction = (params: RuleParams, onError: RuleOnError) => void;
|
||||
/**
|
||||
* Rule parameters.
|
||||
*/
|
||||
export type RuleParams = {
|
||||
/**
|
||||
* File/string name.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Markdown parser data.
|
||||
*/
|
||||
parsers: MarkdownParsers;
|
||||
/**
|
||||
* File/string lines.
|
||||
*/
|
||||
lines: readonly string[];
|
||||
/**
|
||||
* Front matter lines.
|
||||
*/
|
||||
frontMatterLines: readonly string[];
|
||||
/**
|
||||
* Rule configuration.
|
||||
*/
|
||||
config: RuleConfiguration;
|
||||
/**
|
||||
* Version of the markdownlint library.
|
||||
*/
|
||||
version: string;
|
||||
};
|
||||
/**
|
||||
* Markdown parser data.
|
||||
*/
|
||||
export type MarkdownParsers = {
|
||||
/**
|
||||
* Markdown parser data from markdown-it (only present when Rule.parser is "markdownit").
|
||||
*/
|
||||
markdownit: ParserMarkdownIt;
|
||||
/**
|
||||
* Markdown parser data from micromark (only present when Rule.parser is "micromark").
|
||||
*/
|
||||
micromark: ParserMicromark;
|
||||
};
|
||||
/**
|
||||
* Markdown parser data from markdown-it.
|
||||
*/
|
||||
export type ParserMarkdownIt = {
|
||||
/**
|
||||
* Token objects from markdown-it.
|
||||
*/
|
||||
tokens: MarkdownItToken[];
|
||||
};
|
||||
/**
|
||||
* Markdown parser data from micromark.
|
||||
*/
|
||||
export type ParserMicromark = {
|
||||
/**
|
||||
* Token objects from micromark.
|
||||
*/
|
||||
tokens: MicromarkToken[];
|
||||
};
|
||||
/**
|
||||
* markdown-it token.
|
||||
*/
|
||||
export type MarkdownItToken = {
|
||||
/**
|
||||
* HTML attributes.
|
||||
*/
|
||||
attrs: string[][];
|
||||
/**
|
||||
* Block-level token.
|
||||
*/
|
||||
block: boolean;
|
||||
/**
|
||||
* Child nodes.
|
||||
*/
|
||||
children: MarkdownItToken[];
|
||||
/**
|
||||
* Tag contents.
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* Ignore element.
|
||||
*/
|
||||
hidden: boolean;
|
||||
/**
|
||||
* Fence info.
|
||||
*/
|
||||
info: string;
|
||||
/**
|
||||
* Nesting level.
|
||||
*/
|
||||
level: number;
|
||||
/**
|
||||
* Beginning/ending line numbers.
|
||||
*/
|
||||
map: number[];
|
||||
/**
|
||||
* Markup text.
|
||||
*/
|
||||
markup: string;
|
||||
/**
|
||||
* Arbitrary data.
|
||||
*/
|
||||
meta: any;
|
||||
/**
|
||||
* Level change.
|
||||
*/
|
||||
nesting: number;
|
||||
/**
|
||||
* HTML tag name.
|
||||
*/
|
||||
tag: string;
|
||||
/**
|
||||
* Token type.
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Line number (1-based).
|
||||
*/
|
||||
lineNumber: number;
|
||||
/**
|
||||
* Line content.
|
||||
*/
|
||||
line: string;
|
||||
};
|
||||
export type MicromarkTokenType = import("micromark-util-types").TokenType;
|
||||
/**
|
||||
* micromark token.
|
||||
*/
|
||||
export type MicromarkToken = {
|
||||
/**
|
||||
* Token type.
|
||||
*/
|
||||
type: MicromarkTokenType;
|
||||
/**
|
||||
* Start line (1-based).
|
||||
*/
|
||||
startLine: number;
|
||||
/**
|
||||
* Start column (1-based).
|
||||
*/
|
||||
startColumn: number;
|
||||
/**
|
||||
* End line (1-based).
|
||||
*/
|
||||
endLine: number;
|
||||
/**
|
||||
* End column (1-based).
|
||||
*/
|
||||
endColumn: number;
|
||||
/**
|
||||
* Token text.
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* Child tokens.
|
||||
*/
|
||||
children: MicromarkToken[];
|
||||
/**
|
||||
* Parent token.
|
||||
*/
|
||||
parent: MicromarkToken | null;
|
||||
};
|
||||
/**
|
||||
* Error-reporting callback.
|
||||
*/
|
||||
export type RuleOnError = (onErrorInfo: RuleOnErrorInfo) => void;
|
||||
/**
|
||||
* Fix information for RuleOnError callback.
|
||||
*/
|
||||
export type RuleOnErrorInfo = {
|
||||
/**
|
||||
* Line number (1-based).
|
||||
*/
|
||||
lineNumber: number;
|
||||
/**
|
||||
* Detail about the error.
|
||||
*/
|
||||
detail?: string;
|
||||
/**
|
||||
* Context for the error.
|
||||
*/
|
||||
context?: string;
|
||||
/**
|
||||
* Link to more information.
|
||||
*/
|
||||
information?: URL;
|
||||
/**
|
||||
* Column number (1-based) and length.
|
||||
*/
|
||||
range?: number[];
|
||||
/**
|
||||
* Fix information.
|
||||
*/
|
||||
fixInfo?: RuleOnErrorFixInfo;
|
||||
};
|
||||
/**
|
||||
* Fix information for RuleOnErrorInfo.
|
||||
*/
|
||||
export type RuleOnErrorFixInfo = {
|
||||
/**
|
||||
* Line number (1-based).
|
||||
*/
|
||||
lineNumber?: number;
|
||||
/**
|
||||
* Column of the fix (1-based).
|
||||
*/
|
||||
editColumn?: number;
|
||||
/**
|
||||
* Count of characters to delete.
|
||||
*/
|
||||
deleteCount?: number;
|
||||
/**
|
||||
* Text to insert (after deleting).
|
||||
*/
|
||||
insertText?: string;
|
||||
};
|
||||
/**
|
||||
* Rule definition.
|
||||
*/
|
||||
export type Rule = {
|
||||
/**
|
||||
* Rule name(s).
|
||||
*/
|
||||
names: string[];
|
||||
/**
|
||||
* Rule description.
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Link to more information.
|
||||
*/
|
||||
information?: URL;
|
||||
/**
|
||||
* Rule tag(s).
|
||||
*/
|
||||
tags: string[];
|
||||
/**
|
||||
* Parser used.
|
||||
*/
|
||||
parser: "markdownit" | "micromark" | "none";
|
||||
/**
|
||||
* True if asynchronous.
|
||||
*/
|
||||
asynchronous?: boolean;
|
||||
/**
|
||||
* Rule implementation.
|
||||
*/
|
||||
function: RuleFunction;
|
||||
};
|
||||
/**
|
||||
* Method used by the markdown-it parser to parse input.
|
||||
*/
|
||||
export type MarkdownItParse = (src: string, env: any) => any[];
|
||||
/**
|
||||
* Instance of the markdown-it parser.
|
||||
*/
|
||||
export type MarkdownIt = {
|
||||
/**
|
||||
* Method to parse input.
|
||||
*/
|
||||
parse: MarkdownItParse;
|
||||
};
|
||||
/**
|
||||
* Gets an instance of the markdown-it parser. Any plugins should already have been loaded.
|
||||
*/
|
||||
export type MarkdownItFactory = () => MarkdownIt | Promise<MarkdownIt>;
|
||||
/**
|
||||
* Configuration options.
|
||||
*/
|
||||
export type Options = {
|
||||
/**
|
||||
* Configuration object.
|
||||
*/
|
||||
config?: Configuration;
|
||||
/**
|
||||
* Configuration parsers.
|
||||
*/
|
||||
configParsers?: ConfigurationParser[];
|
||||
/**
|
||||
* Custom rules.
|
||||
*/
|
||||
customRules?: Rule[] | Rule;
|
||||
/**
|
||||
* Files to lint.
|
||||
*/
|
||||
files?: string[] | string;
|
||||
/**
|
||||
* Front matter pattern.
|
||||
*/
|
||||
frontMatter?: RegExp | null;
|
||||
/**
|
||||
* File system implementation.
|
||||
*/
|
||||
fs?: FsLike;
|
||||
/**
|
||||
* True to catch exceptions.
|
||||
*/
|
||||
handleRuleFailures?: boolean;
|
||||
/**
|
||||
* Function to create a markdown-it parser.
|
||||
*/
|
||||
markdownItFactory?: MarkdownItFactory;
|
||||
/**
|
||||
* True to ignore HTML directives.
|
||||
*/
|
||||
noInlineConfig?: boolean;
|
||||
/**
|
||||
* Strings to lint.
|
||||
*/
|
||||
strings?: {
|
||||
[x: string]: string;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* A markdown-it plugin.
|
||||
*/
|
||||
export type Plugin = any[];
|
||||
/**
|
||||
* Lint results.
|
||||
*/
|
||||
export type LintResults = {
|
||||
[x: string]: LintError[];
|
||||
};
|
||||
/**
|
||||
* Lint error.
|
||||
*/
|
||||
export type LintError = {
|
||||
/**
|
||||
* Line number (1-based).
|
||||
*/
|
||||
lineNumber: number;
|
||||
/**
|
||||
* Rule name(s).
|
||||
*/
|
||||
ruleNames: string[];
|
||||
/**
|
||||
* Rule description.
|
||||
*/
|
||||
ruleDescription: string;
|
||||
/**
|
||||
* Link to more information.
|
||||
*/
|
||||
ruleInformation: string | null;
|
||||
/**
|
||||
* Detail about the error.
|
||||
*/
|
||||
errorDetail: string | null;
|
||||
/**
|
||||
* Context for the error.
|
||||
*/
|
||||
errorContext: string | null;
|
||||
/**
|
||||
* Column number (1-based) and length.
|
||||
*/
|
||||
errorRange: number[] | null;
|
||||
/**
|
||||
* Fix information.
|
||||
*/
|
||||
fixInfo: FixInfo | null;
|
||||
/**
|
||||
* Severity of the error.
|
||||
*/
|
||||
severity: "error" | "warning";
|
||||
};
|
||||
/**
|
||||
* Fix information.
|
||||
*/
|
||||
export type FixInfo = {
|
||||
/**
|
||||
* Line number (1-based).
|
||||
*/
|
||||
lineNumber?: number;
|
||||
/**
|
||||
* Column of the fix (1-based).
|
||||
*/
|
||||
editColumn?: number;
|
||||
/**
|
||||
* Count of characters to delete.
|
||||
*/
|
||||
deleteCount?: number;
|
||||
/**
|
||||
* Text to insert (after deleting).
|
||||
*/
|
||||
insertText?: string;
|
||||
};
|
||||
/**
|
||||
* FixInfo with all optional properties present.
|
||||
*/
|
||||
export type FixInfoNormalized = {
|
||||
/**
|
||||
* Line number (1-based).
|
||||
*/
|
||||
lineNumber: number;
|
||||
/**
|
||||
* Column of the fix (1-based).
|
||||
*/
|
||||
editColumn: number;
|
||||
/**
|
||||
* Count of characters to delete.
|
||||
*/
|
||||
deleteCount: number;
|
||||
/**
|
||||
* Text to insert (after deleting).
|
||||
*/
|
||||
insertText: string;
|
||||
};
|
||||
/**
|
||||
* Called with the result of linting a string or document.
|
||||
*/
|
||||
export type LintContentCallback = (error: Error | null, result?: LintError[]) => void;
|
||||
/**
|
||||
* Called with the result of the lint function.
|
||||
*/
|
||||
export type LintCallback = (error: Error | null, results?: LintResults) => void;
|
||||
/**
|
||||
* Configuration object for linting rules. For the JSON schema, see
|
||||
* {@link ../schema/markdownlint-config-schema.json}.
|
||||
*/
|
||||
export type Configuration = import("./configuration.d.ts").Configuration;
|
||||
/**
|
||||
* Configuration object for linting rules strictly. For the JSON schema, see
|
||||
* {@link ../schema/markdownlint-config-schema-strict.json}.
|
||||
*/
|
||||
export type ConfigurationStrict = import("./configuration-strict.d.ts").ConfigurationStrict;
|
||||
/**
|
||||
* Rule configuration.
|
||||
*/
|
||||
export type RuleConfiguration = boolean | any;
|
||||
/**
|
||||
* Parses a configuration string and returns a configuration object.
|
||||
*/
|
||||
export type ConfigurationParser = (text: string) => Configuration;
|
||||
/**
|
||||
* Called with the result of the readConfig function.
|
||||
*/
|
||||
export type ReadConfigCallback = (err: Error | null, config?: Configuration) => void;
|
||||
/**
|
||||
* Called with the result of the resolveConfigExtends function.
|
||||
*/
|
||||
export type ResolveConfigExtendsCallback = (err: Error | null, path?: string) => void;
|
||||
+1660
File diff suppressed because it is too large
Load Diff
+32
@@ -0,0 +1,32 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorDetailIf, frontMatterHasTitle } from "../helpers/helpers.cjs";
|
||||
import { getHeadingLevel } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD001", "heading-increment" ],
|
||||
"description": "Heading levels should only increment by one level at a time",
|
||||
"tags": [ "headings" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD001(params, onError) {
|
||||
const hasTitle = frontMatterHasTitle(
|
||||
params.frontMatterLines,
|
||||
params.config.front_matter_title
|
||||
);
|
||||
let prevLevel = hasTitle ? 1 : Number.MAX_SAFE_INTEGER;
|
||||
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
|
||||
const level = getHeadingLevel(heading);
|
||||
if (level > prevLevel) {
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
heading.startLine,
|
||||
`h${prevLevel + 1}`,
|
||||
`h${level}`
|
||||
);
|
||||
}
|
||||
prevLevel = level;
|
||||
}
|
||||
}
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorDetailIf } from "../helpers/helpers.cjs";
|
||||
import { getHeadingLevel, getHeadingStyle } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD003", "heading-style" ],
|
||||
"description": "Heading style",
|
||||
"tags": [ "headings" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD003(params, onError) {
|
||||
let style = String(params.config.style || "consistent");
|
||||
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
|
||||
const styleForToken = getHeadingStyle(heading);
|
||||
if (style === "consistent") {
|
||||
style = styleForToken;
|
||||
}
|
||||
if (styleForToken !== style) {
|
||||
const h12 = getHeadingLevel(heading) <= 2;
|
||||
const setextWithAtx =
|
||||
(style === "setext_with_atx") &&
|
||||
((h12 && (styleForToken === "setext")) ||
|
||||
(!h12 && (styleForToken === "atx")));
|
||||
const setextWithAtxClosed =
|
||||
(style === "setext_with_atx_closed") &&
|
||||
((h12 && (styleForToken === "setext")) ||
|
||||
(!h12 && (styleForToken === "atx_closed")));
|
||||
if (!setextWithAtx && !setextWithAtxClosed) {
|
||||
let expected = style;
|
||||
if (style === "setext_with_atx") {
|
||||
expected = h12 ? "setext" : "atx";
|
||||
} else if (style === "setext_with_atx_closed") {
|
||||
expected = h12 ? "setext" : "atx_closed";
|
||||
}
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
heading.startLine,
|
||||
expected,
|
||||
styleForToken
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorDetailIf } from "../helpers/helpers.cjs";
|
||||
import { getDescendantsByType, getParentOfType } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
const markerToStyle = (/** @type {string} */ marker) => (marker === "-") ? "dash" : ((marker === "+") ? "plus" : "asterisk");
|
||||
const styleToMarker = (/** @type {string} */ style) => (style === "dash") ? "-" : ((style === "plus") ? "+" : "*");
|
||||
const differentItemStyle = (/** @type {string} */ style) => (style === "dash") ? "plus" : ((style === "plus") ? "asterisk" : "dash");
|
||||
const validStyles = new Set([
|
||||
"asterisk",
|
||||
"consistent",
|
||||
"dash",
|
||||
"plus",
|
||||
"sublist"
|
||||
]);
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD004", "ul-style" ],
|
||||
"description": "Unordered list style",
|
||||
"tags": [ "bullet", "ul" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD004(params, onError) {
|
||||
const style = String(params.config.style || "consistent");
|
||||
let expectedStyle = validStyles.has(style) ? style : "dash";
|
||||
/** @type {("asterisk"|"dash"|"plus")[]} */
|
||||
const nestingStyles = [];
|
||||
for (const listUnordered of filterByTypesCached([ "listUnordered" ])) {
|
||||
let nesting = 0;
|
||||
if (style === "sublist") {
|
||||
/** @type {import("markdownlint").MicromarkToken | null} */
|
||||
let parent = listUnordered;
|
||||
// @ts-ignore
|
||||
while ((parent = getParentOfType(parent, [ "listOrdered", "listUnordered" ]))) {
|
||||
nesting++;
|
||||
}
|
||||
}
|
||||
const listItemMarkers = getDescendantsByType(listUnordered, [ "listItemPrefix", "listItemMarker" ]);
|
||||
for (const listItemMarker of listItemMarkers) {
|
||||
const itemStyle = markerToStyle(listItemMarker.text);
|
||||
if (style === "sublist") {
|
||||
if (!nestingStyles[nesting]) {
|
||||
nestingStyles[nesting] =
|
||||
(itemStyle === nestingStyles[nesting - 1]) ?
|
||||
differentItemStyle(itemStyle) :
|
||||
itemStyle;
|
||||
}
|
||||
expectedStyle = nestingStyles[nesting];
|
||||
} else if (expectedStyle === "consistent") {
|
||||
expectedStyle = itemStyle;
|
||||
}
|
||||
const column = listItemMarker.startColumn;
|
||||
const length = listItemMarker.endColumn - listItemMarker.startColumn;
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
listItemMarker.startLine,
|
||||
expectedStyle,
|
||||
itemStyle,
|
||||
undefined,
|
||||
undefined,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": styleToMarker(expectedStyle)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// @ts-check
|
||||
|
||||
import { addError, addErrorDetailIf } from "../helpers/helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD005", "list-indent" ],
|
||||
"description": "Inconsistent indentation for list items at the same level",
|
||||
"tags": [ "bullet", "ul", "indentation" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD005(params, onError) {
|
||||
for (const list of filterByTypesCached([ "listOrdered", "listUnordered" ])) {
|
||||
const expectedIndent = list.startColumn - 1;
|
||||
let expectedEnd = 0;
|
||||
let endMatching = false;
|
||||
const listItemPrefixes =
|
||||
list.children.filter((token) => (token.type === "listItemPrefix"));
|
||||
for (const listItemPrefix of listItemPrefixes) {
|
||||
const lineNumber = listItemPrefix.startLine;
|
||||
const actualIndent = listItemPrefix.startColumn - 1;
|
||||
const range = [ 1, listItemPrefix.endColumn - 1 ];
|
||||
if (list.type === "listUnordered") {
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
lineNumber,
|
||||
expectedIndent,
|
||||
actualIndent,
|
||||
undefined,
|
||||
undefined,
|
||||
range
|
||||
// No fixInfo; MD007 handles this scenario better
|
||||
);
|
||||
} else {
|
||||
const markerLength = listItemPrefix.text.trim().length;
|
||||
const actualEnd = listItemPrefix.startColumn + markerLength - 1;
|
||||
expectedEnd = expectedEnd || actualEnd;
|
||||
if ((expectedIndent !== actualIndent) || endMatching) {
|
||||
if (expectedEnd === actualEnd) {
|
||||
endMatching = true;
|
||||
} else {
|
||||
const detail = endMatching ?
|
||||
`Expected: (${expectedEnd}); Actual: (${actualEnd})` :
|
||||
`Expected: ${expectedIndent}; Actual: ${actualIndent}`;
|
||||
const expected = endMatching ?
|
||||
expectedEnd - markerLength :
|
||||
expectedIndent;
|
||||
const actual = endMatching ?
|
||||
actualEnd - markerLength :
|
||||
actualIndent;
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
detail,
|
||||
undefined,
|
||||
range,
|
||||
{
|
||||
"editColumn": Math.min(actual, expected) + 1,
|
||||
"deleteCount": Math.max(actual - expected, 0),
|
||||
"insertText": "".padEnd(Math.max(expected - actual, 0))
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorDetailIf } from "../helpers/helpers.cjs";
|
||||
import { getParentOfType } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("micromark-util-types").TokenType[]} */
|
||||
const unorderedListTypes =
|
||||
[ "blockQuotePrefix", "listItemPrefix", "listUnordered" ];
|
||||
/** @type {import("micromark-util-types").TokenType[]} */
|
||||
const unorderedParentTypes =
|
||||
[ "blockQuote", "listOrdered", "listUnordered" ];
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD007", "ul-indent" ],
|
||||
"description": "Unordered list indentation",
|
||||
"tags": [ "bullet", "ul", "indentation" ],
|
||||
"parser": "micromark",
|
||||
"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);
|
||||
const unorderedListNesting = new Map();
|
||||
let lastBlockQuotePrefix = null;
|
||||
const tokens = filterByTypesCached(unorderedListTypes);
|
||||
for (const token of tokens) {
|
||||
const { endColumn, parent, startColumn, startLine, type } = token;
|
||||
if (type === "blockQuotePrefix") {
|
||||
lastBlockQuotePrefix = token;
|
||||
} else if (type === "listUnordered") {
|
||||
let nesting = 0;
|
||||
/** @type {import("markdownlint").MicromarkToken | null} */
|
||||
let current = token;
|
||||
while (
|
||||
// @ts-ignore
|
||||
(current = getParentOfType(current, unorderedParentTypes))
|
||||
) {
|
||||
if (current.type === "listUnordered") {
|
||||
nesting++;
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
} else if (current.type === "listOrdered") {
|
||||
nesting = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (nesting >= 0) {
|
||||
unorderedListNesting.set(token, nesting);
|
||||
}
|
||||
} else {
|
||||
// listItemPrefix
|
||||
const nesting = unorderedListNesting.get(parent);
|
||||
if (nesting !== undefined) {
|
||||
// listItemPrefix for listUnordered
|
||||
const baseIndent = (getParentOfType(token, [ "gfmFootnoteDefinition" ])) ? 4 : 0;
|
||||
const expectedIndent =
|
||||
baseIndent + (startIndented ? startIndent : 0) + (nesting * indent);
|
||||
const blockQuoteAdjustment =
|
||||
(lastBlockQuotePrefix?.endLine === startLine) ?
|
||||
(lastBlockQuotePrefix.endColumn - 1) :
|
||||
0;
|
||||
const actualIndent = startColumn - 1 - blockQuoteAdjustment;
|
||||
const range = [ 1, endColumn - 1 ];
|
||||
const fixInfo = {
|
||||
"editColumn": startColumn - actualIndent,
|
||||
"deleteCount": Math.max(actualIndent - expectedIndent, 0),
|
||||
"insertText": "".padEnd(Math.max(expectedIndent - actualIndent, 0))
|
||||
};
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
startLine,
|
||||
expectedIndent,
|
||||
actualIndent,
|
||||
undefined,
|
||||
undefined,
|
||||
range,
|
||||
fixInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// @ts-check
|
||||
|
||||
import { addError } from "../helpers/helpers.cjs";
|
||||
import { addRangeToSet } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD009", "no-trailing-spaces" ],
|
||||
"description": "Trailing spaces",
|
||||
"tags": [ "whitespace" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD009(params, onError) {
|
||||
let brSpaces = params.config.br_spaces;
|
||||
brSpaces = Number((brSpaces === undefined) ? 2 : brSpaces);
|
||||
const codeBlocks = params.config.code_blocks;
|
||||
const includeCode = (codeBlocks === undefined) ? false : !!codeBlocks;
|
||||
const listItemEmptyLines = !!params.config.list_item_empty_lines;
|
||||
const strict = !!params.config.strict;
|
||||
const codeBlockLineNumbers = new Set();
|
||||
if (!includeCode) {
|
||||
for (const codeBlock of filterByTypesCached([ "codeFenced" ])) {
|
||||
addRangeToSet(codeBlockLineNumbers, codeBlock.startLine + 1, codeBlock.endLine - 1);
|
||||
}
|
||||
for (const codeBlock of filterByTypesCached([ "codeIndented" ])) {
|
||||
addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine);
|
||||
}
|
||||
}
|
||||
const listItemLineNumbers = new Set();
|
||||
if (listItemEmptyLines) {
|
||||
for (const listBlock of filterByTypesCached([ "listOrdered", "listUnordered" ])) {
|
||||
addRangeToSet(listItemLineNumbers, listBlock.startLine, listBlock.endLine);
|
||||
let trailingIndent = true;
|
||||
for (let i = listBlock.children.length - 1; i >= 0; i--) {
|
||||
const child = listBlock.children[i];
|
||||
switch (child.type) {
|
||||
case "content":
|
||||
trailingIndent = false;
|
||||
break;
|
||||
case "listItemIndent":
|
||||
if (trailingIndent) {
|
||||
listItemLineNumbers.delete(child.startLine);
|
||||
}
|
||||
break;
|
||||
case "listItemPrefix":
|
||||
trailingIndent = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const paragraphLineNumbers = new Set();
|
||||
const codeInlineLineNumbers = new Set();
|
||||
if (strict) {
|
||||
for (const paragraph of filterByTypesCached([ "paragraph" ])) {
|
||||
addRangeToSet(paragraphLineNumbers, paragraph.startLine, paragraph.endLine - 1);
|
||||
}
|
||||
for (const codeText of filterByTypesCached([ "codeText" ])) {
|
||||
addRangeToSet(codeInlineLineNumbers, codeText.startLine, codeText.endLine - 1);
|
||||
}
|
||||
}
|
||||
const expected = (brSpaces < 2) ? 0 : brSpaces;
|
||||
for (let lineIndex = 0; lineIndex < params.lines.length; lineIndex++) {
|
||||
const line = params.lines[lineIndex];
|
||||
const lineNumber = lineIndex + 1;
|
||||
const trailingSpaces = line.length - line.trimEnd().length;
|
||||
if (
|
||||
trailingSpaces &&
|
||||
!codeBlockLineNumbers.has(lineNumber) &&
|
||||
!listItemLineNumbers.has(lineNumber) &&
|
||||
(
|
||||
(expected !== trailingSpaces) ||
|
||||
(strict &&
|
||||
(!paragraphLineNumbers.has(lineNumber) ||
|
||||
codeInlineLineNumbers.has(lineNumber)))
|
||||
)
|
||||
) {
|
||||
const column = line.length - trailingSpaces + 1;
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
"Expected: " + (expected === 0 ? "" : "0 or ") +
|
||||
expected + "; Actual: " + trailingSpaces,
|
||||
undefined,
|
||||
[ column, trailingSpaces ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": trailingSpaces
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// @ts-check
|
||||
|
||||
import { addError, hasOverlap } from "../helpers/helpers.cjs";
|
||||
import { getDescendantsByType } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
const tabRe = /\t+/g;
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD010", "no-hard-tabs" ],
|
||||
"description": "Hard tabs",
|
||||
"tags": [ "whitespace", "hard_tab" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD010(params, onError) {
|
||||
const codeBlocks = params.config.code_blocks;
|
||||
const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks;
|
||||
const ignoreCodeLanguages = new Set(
|
||||
(params.config.ignore_code_languages || [])
|
||||
.map((/** @type {void} */ language) => String(language).toLowerCase())
|
||||
);
|
||||
const spacesPerTab = params.config.spaces_per_tab;
|
||||
const spaceMultiplier = (spacesPerTab === undefined) ?
|
||||
1 :
|
||||
Math.max(0, Number(spacesPerTab));
|
||||
/** @type {import("markdownlint").MicromarkTokenType[]} */
|
||||
const exclusionTypes = [];
|
||||
if (includeCode) {
|
||||
if (ignoreCodeLanguages.size > 0) {
|
||||
exclusionTypes.push("codeFenced");
|
||||
}
|
||||
} else {
|
||||
exclusionTypes.push("codeFenced", "codeIndented", "codeText");
|
||||
}
|
||||
const codeTokens = filterByTypesCached(exclusionTypes).filter((token) => {
|
||||
if ((token.type === "codeFenced") && (ignoreCodeLanguages.size > 0)) {
|
||||
const fenceInfos = getDescendantsByType(token, [ "codeFencedFence", "codeFencedFenceInfo" ]);
|
||||
return fenceInfos.every((fenceInfo) => ignoreCodeLanguages.has(fenceInfo.text.toLowerCase()));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const codeRanges = codeTokens.map((token) => {
|
||||
const { type, startLine, startColumn, endLine, endColumn } = token;
|
||||
const codeFenced = (type === "codeFenced");
|
||||
return {
|
||||
"startLine": startLine + (codeFenced ? 1 : 0),
|
||||
"startColumn": codeFenced ? 0 : startColumn,
|
||||
"endLine": endLine - (codeFenced ? 1 : 0),
|
||||
"endColumn": codeFenced ? Number.MAX_SAFE_INTEGER : endColumn
|
||||
};
|
||||
});
|
||||
for (let lineIndex = 0; lineIndex < params.lines.length; lineIndex++) {
|
||||
const line = params.lines[lineIndex];
|
||||
let match = null;
|
||||
while ((match = tabRe.exec(line)) !== null) {
|
||||
const lineNumber = lineIndex + 1;
|
||||
const column = match.index + 1;
|
||||
const length = match[0].length;
|
||||
/** @type {import("../helpers/helpers.cjs").FileRange} */
|
||||
const range = { "startLine": lineNumber, "startColumn": column, "endLine": lineNumber, "endColumn": column + length - 1 };
|
||||
if (!codeRanges.some((codeRange) => hasOverlap(codeRange, range))) {
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
"Column: " + column,
|
||||
undefined,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": "".padEnd(length * spaceMultiplier)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// @ts-check
|
||||
|
||||
import { addError, hasOverlap } from "../helpers/helpers.cjs";
|
||||
import { addRangeToSet } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @typedef {import("micromark-extension-math")} */
|
||||
|
||||
const reversedLinkRe = /(^|[^\\])\(([^()]+)\)\[([^\]^][^\]]*)\](?!\()/g;
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD011", "no-reversed-links" ],
|
||||
"description": "Reversed link syntax",
|
||||
"tags": [ "links" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD011(params, onError) {
|
||||
const ignoreBlockLineNumbers = new Set();
|
||||
for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "mathFlow" ])) {
|
||||
addRangeToSet(ignoreBlockLineNumbers, ignoreBlock.startLine, ignoreBlock.endLine);
|
||||
}
|
||||
const ignoreTexts = filterByTypesCached([ "codeText", "mathText" ]);
|
||||
for (const [ lineIndex, line ] of params.lines.entries()) {
|
||||
const lineNumber = lineIndex + 1;
|
||||
if (!ignoreBlockLineNumbers.has(lineNumber)) {
|
||||
let match = null;
|
||||
while ((match = reversedLinkRe.exec(line)) !== null) {
|
||||
const [ reversedLink, preChar, linkText, linkDestination ] = match;
|
||||
if (
|
||||
!linkText.endsWith("\\") &&
|
||||
!linkDestination.endsWith("\\")
|
||||
) {
|
||||
const column = match.index + preChar.length + 1;
|
||||
const length = match[0].length - preChar.length;
|
||||
/** @type {import("../helpers/helpers.cjs").FileRange} */
|
||||
const range = { "startLine": lineNumber, "startColumn": column, "endLine": lineNumber, "endColumn": column + length - 1 };
|
||||
if (!ignoreTexts.some((ignoreText) => hasOverlap(ignoreText, range))) {
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
reversedLink.slice(preChar.length),
|
||||
undefined,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length,
|
||||
"insertText": `[${linkText}](${linkDestination})`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorDetailIf } from "../helpers/helpers.cjs";
|
||||
import { addRangeToSet } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD012", "no-multiple-blanks" ],
|
||||
"description": "Multiple consecutive blank lines",
|
||||
"tags": [ "whitespace", "blank_lines" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD012(params, onError) {
|
||||
const maximum = Number(params.config.maximum || 1);
|
||||
const { lines } = params;
|
||||
const codeBlockLineNumbers = new Set();
|
||||
for (const codeBlock of filterByTypesCached([ "codeFenced", "codeIndented" ])) {
|
||||
addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine);
|
||||
}
|
||||
let count = 0;
|
||||
for (const [ lineIndex, line ] of lines.entries()) {
|
||||
const inCode = codeBlockLineNumbers.has(lineIndex + 1);
|
||||
count = (inCode || (line.trim().length > 0)) ? 0 : count + 1;
|
||||
if (maximum < count) {
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
maximum,
|
||||
count,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
"deleteCount": -1
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorDetailIf } from "../helpers/helpers.cjs";
|
||||
import { filterByTypesCached, getReferenceLinkImageData } from "./cache.mjs";
|
||||
import { addRangeToSet, getDescendantsByType } from "../helpers/micromark-helpers.cjs";
|
||||
|
||||
// Regular expression for a line that is not wrappable
|
||||
const notWrappableRe = /^(?:[#>\s]*\s)?\S*$/;
|
||||
|
||||
/** @typedef {import("micromark-extension-gfm-autolink-literal")} */
|
||||
/** @typedef {import("micromark-extension-gfm-table")} */
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD013", "line-length" ],
|
||||
"description": "Line length",
|
||||
"tags": [ "line_length" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD013(params, onError) {
|
||||
const lineLength = Number(params.config.line_length || 80);
|
||||
const headingLineLength = Number(params.config.heading_line_length || lineLength);
|
||||
const codeLineLength = Number(params.config.code_block_line_length || lineLength);
|
||||
const strict = !!params.config.strict;
|
||||
const stern = !!params.config.stern;
|
||||
const codeBlocks = params.config.code_blocks;
|
||||
const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks;
|
||||
const tables = params.config.tables;
|
||||
const includeTables = (tables === undefined) ? true : !!tables;
|
||||
const headings = params.config.headings;
|
||||
const includeHeadings = (headings === undefined) ? true : !!headings;
|
||||
const headingLineNumbers = new Set();
|
||||
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
|
||||
addRangeToSet(headingLineNumbers, heading.startLine, heading.endLine);
|
||||
}
|
||||
const codeBlockLineNumbers = new Set();
|
||||
for (const codeBlock of filterByTypesCached([ "codeFenced", "codeIndented" ])) {
|
||||
addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine);
|
||||
}
|
||||
const tableLineNumbers = new Set();
|
||||
for (const table of filterByTypesCached([ "table" ])) {
|
||||
addRangeToSet(tableLineNumbers, table.startLine, table.endLine);
|
||||
}
|
||||
const linkLineNumbers = new Set();
|
||||
for (const link of filterByTypesCached([ "autolink", "image", "link", "literalAutolink" ])) {
|
||||
addRangeToSet(linkLineNumbers, link.startLine, link.endLine);
|
||||
}
|
||||
const paragraphDataLineNumbers = new Set();
|
||||
for (const paragraph of filterByTypesCached([ "paragraph" ])) {
|
||||
for (const data of getDescendantsByType(paragraph, [ "data" ])) {
|
||||
addRangeToSet(paragraphDataLineNumbers, data.startLine, data.endLine);
|
||||
}
|
||||
}
|
||||
const linkOnlyLineNumbers = new Set();
|
||||
for (const lineNumber of linkLineNumbers) {
|
||||
if (!paragraphDataLineNumbers.has(lineNumber)) {
|
||||
linkOnlyLineNumbers.add(lineNumber);
|
||||
}
|
||||
}
|
||||
const definitionLineIndices = new Set(getReferenceLinkImageData().definitionLineIndices);
|
||||
for (let lineIndex = 0; lineIndex < params.lines.length; lineIndex++) {
|
||||
const line = params.lines[lineIndex];
|
||||
const lineNumber = lineIndex + 1;
|
||||
const isHeading = headingLineNumbers.has(lineNumber);
|
||||
const inCode = codeBlockLineNumbers.has(lineNumber);
|
||||
const inTable = tableLineNumbers.has(lineNumber);
|
||||
const maxLength = inCode ? codeLineLength : (isHeading ? headingLineLength : lineLength);
|
||||
// If not strict/stern, the last run of non-whitespace is allowed to go
|
||||
// beyond the limit as long as it begins within the limit
|
||||
const text = (strict || stern) ? line : line.replace(/\S*$/u, "#");
|
||||
if ((maxLength > 0) &&
|
||||
(includeCodeBlocks || !inCode) &&
|
||||
(includeTables || !inTable) &&
|
||||
(includeHeadings || !isHeading) &&
|
||||
!definitionLineIndices.has(lineIndex) &&
|
||||
(strict ||
|
||||
(!(stern && notWrappableRe.test(line)) &&
|
||||
!linkOnlyLineNumbers.has(lineNumber))) &&
|
||||
(text.length > maxLength)) {
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
lineNumber,
|
||||
maxLength,
|
||||
line.length,
|
||||
undefined,
|
||||
undefined,
|
||||
[ maxLength + 1, line.length - maxLength ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorContext } from "../helpers/helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
const dollarCommandRe = /^(\s*)(\$\s+)/;
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD014", "commands-show-output" ],
|
||||
"description": "Dollar signs used before commands without showing output",
|
||||
"tags": [ "code" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD014(params, onError) {
|
||||
for (const codeBlock of filterByTypesCached([ "codeFenced", "codeIndented" ])) {
|
||||
const codeFlowValues = codeBlock.children.filter((child) => child.type === "codeFlowValue");
|
||||
const dollarMatches = codeFlowValues
|
||||
.map((codeFlowValue) => ({
|
||||
"result": codeFlowValue.text.match(dollarCommandRe),
|
||||
"startColumn": codeFlowValue.startColumn,
|
||||
"startLine": codeFlowValue.startLine,
|
||||
"text": codeFlowValue.text
|
||||
}))
|
||||
.filter((dollarMatch) => dollarMatch.result);
|
||||
if (dollarMatches.length === codeFlowValues.length) {
|
||||
for (const dollarMatch of dollarMatches) {
|
||||
// @ts-ignore
|
||||
const column = dollarMatch.startColumn + dollarMatch.result[1].length;
|
||||
// @ts-ignore
|
||||
const length = dollarMatch.result[2].length;
|
||||
addErrorContext(
|
||||
onError,
|
||||
dollarMatch.startLine,
|
||||
dollarMatch.text,
|
||||
undefined,
|
||||
undefined,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorContext } from "../helpers/helpers.cjs";
|
||||
import { addRangeToSet } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD018", "no-missing-space-atx" ],
|
||||
"description": "No space after hash on atx style heading",
|
||||
"tags": [ "headings", "atx", "spaces" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD018(params, onError) {
|
||||
const { lines } = params;
|
||||
const ignoreBlockLineNumbers = new Set();
|
||||
for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "htmlFlow" ])) {
|
||||
addRangeToSet(ignoreBlockLineNumbers, ignoreBlock.startLine, ignoreBlock.endLine);
|
||||
}
|
||||
for (const [ lineIndex, line ] of lines.entries()) {
|
||||
if (
|
||||
!ignoreBlockLineNumbers.has(lineIndex + 1) &&
|
||||
/^#+[^# \t]/.test(line) &&
|
||||
!/#\s*$/.test(line) &&
|
||||
!line.startsWith("#️⃣")
|
||||
) {
|
||||
// @ts-ignore
|
||||
const hashCount = /^#+/.exec(line)[0].length;
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
line.trim(),
|
||||
undefined,
|
||||
undefined,
|
||||
[ 1, hashCount + 1 ],
|
||||
{
|
||||
"editColumn": hashCount + 1,
|
||||
"insertText": " "
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorContext } from "../helpers/helpers.cjs";
|
||||
import { getHeadingStyle } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/**
|
||||
* Validate heading sequence and whitespace length at start or end.
|
||||
*
|
||||
* @param {import("markdownlint").RuleOnError} onError Error-reporting callback.
|
||||
* @param {import("markdownlint").MicromarkToken} heading ATX heading token.
|
||||
* @param {number} delta Direction to scan.
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateHeadingSpaces(onError, heading, delta) {
|
||||
const { children, startLine, text } = heading;
|
||||
let index = (delta > 0) ? 0 : (children.length - 1);
|
||||
while (
|
||||
children[index] &&
|
||||
(children[index].type !== "atxHeadingSequence")
|
||||
) {
|
||||
index += delta;
|
||||
}
|
||||
const headingSequence = children[index];
|
||||
const whitespace = children[index + delta];
|
||||
if (
|
||||
(headingSequence?.type === "atxHeadingSequence") &&
|
||||
(whitespace?.type === "whitespace") &&
|
||||
(whitespace.text.length > 1)
|
||||
) {
|
||||
const column = whitespace.startColumn + 1;
|
||||
const length = whitespace.endColumn - column;
|
||||
addErrorContext(
|
||||
onError,
|
||||
startLine,
|
||||
text.trim(),
|
||||
delta > 0,
|
||||
delta < 0,
|
||||
[ column, length ],
|
||||
{
|
||||
"editColumn": column,
|
||||
"deleteCount": length
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import("markdownlint").Rule[]} */
|
||||
export default [
|
||||
{
|
||||
"names": [ "MD019", "no-multiple-space-atx" ],
|
||||
"description": "Multiple spaces after hash on atx style heading",
|
||||
"tags": [ "headings", "atx", "spaces" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD019(params, onError) {
|
||||
const atxHeadings = filterByTypesCached([ "atxHeading" ])
|
||||
.filter((heading) => getHeadingStyle(heading) === "atx");
|
||||
for (const atxHeading of atxHeadings) {
|
||||
validateHeadingSpaces(onError, atxHeading, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"names": [ "MD021", "no-multiple-space-closed-atx" ],
|
||||
"description": "Multiple spaces inside hashes on closed atx style heading",
|
||||
"tags": [ "headings", "atx_closed", "spaces" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD021(params, onError) {
|
||||
const atxClosedHeadings = filterByTypesCached([ "atxHeading" ])
|
||||
.filter((heading) => getHeadingStyle(heading) === "atx_closed");
|
||||
for (const atxClosedHeading of atxClosedHeadings) {
|
||||
validateHeadingSpaces(onError, atxClosedHeading, 1);
|
||||
validateHeadingSpaces(onError, atxClosedHeading, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorContext } from "../helpers/helpers.cjs";
|
||||
import { addRangeToSet } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD020", "no-missing-space-closed-atx" ],
|
||||
"description": "No space inside hashes on closed atx style heading",
|
||||
"tags": [ "headings", "atx_closed", "spaces" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD020(params, onError) {
|
||||
const { lines } = params;
|
||||
const ignoreBlockLineNumbers = new Set();
|
||||
for (const ignoreBlock of filterByTypesCached([ "codeFenced", "codeIndented", "htmlFlow" ])) {
|
||||
addRangeToSet(ignoreBlockLineNumbers, ignoreBlock.startLine, ignoreBlock.endLine);
|
||||
}
|
||||
for (const [ lineIndex, line ] of lines.entries()) {
|
||||
if (!ignoreBlockLineNumbers.has(lineIndex + 1)) {
|
||||
const match =
|
||||
/^(#+)([ \t]*)([^# \t\\]|[^# \t][^#]*?[^# \t\\])([ \t]*)((?:\\#)?)(#+)(\s*)$/.exec(line);
|
||||
if (match) {
|
||||
const [
|
||||
,
|
||||
leftHash,
|
||||
{ "length": leftSpaceLength },
|
||||
content,
|
||||
{ "length": rightSpaceLength },
|
||||
rightEscape,
|
||||
rightHash,
|
||||
{ "length": trailSpaceLength }
|
||||
] = match;
|
||||
const leftHashLength = leftHash.length;
|
||||
const rightHashLength = rightHash.length;
|
||||
const left = !leftSpaceLength;
|
||||
const right = !rightSpaceLength || !!rightEscape;
|
||||
const rightEscapeReplacement = rightEscape ? `${rightEscape} ` : "";
|
||||
if (left || right) {
|
||||
const range = left ?
|
||||
[
|
||||
1,
|
||||
leftHashLength + 1
|
||||
] :
|
||||
[
|
||||
line.length - trailSpaceLength - rightHashLength,
|
||||
rightHashLength + 1
|
||||
];
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineIndex + 1,
|
||||
line.trim(),
|
||||
left,
|
||||
right,
|
||||
range,
|
||||
{
|
||||
"editColumn": 1,
|
||||
"deleteCount": line.length,
|
||||
"insertText":
|
||||
`${leftHash} ${content} ${rightEscapeReplacement}${rightHash}`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorDetailIf, isBlankLine } from "../helpers/helpers.cjs";
|
||||
import { getBlockQuotePrefixText, getHeadingLevel } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
|
||||
|
||||
const defaultLines = 1;
|
||||
|
||||
// eslint-disable-next-line jsdoc/reject-any-type
|
||||
const getLinesFunction = (/** @type {any} */ linesParam) => {
|
||||
if (Array.isArray(linesParam)) {
|
||||
const linesArray = new Array(6).fill(defaultLines);
|
||||
for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) {
|
||||
linesArray[index] = value;
|
||||
}
|
||||
return (/** @type {MicromarkToken} */ heading) => linesArray[getHeadingLevel(heading) - 1];
|
||||
}
|
||||
// Coerce linesParam to a number
|
||||
const lines = (linesParam === undefined) ? defaultLines : Number(linesParam);
|
||||
return () => lines;
|
||||
};
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD022", "blanks-around-headings" ],
|
||||
"description": "Headings should be surrounded by blank lines",
|
||||
"tags": [ "headings", "blank_lines" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD022(params, onError) {
|
||||
const getLinesAbove = getLinesFunction(params.config.lines_above);
|
||||
const getLinesBelow = getLinesFunction(params.config.lines_below);
|
||||
const { lines } = params;
|
||||
const blockQuotePrefixes = filterByTypesCached([ "blockQuotePrefix", "linePrefix" ]);
|
||||
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
|
||||
const { startLine, endLine } = heading;
|
||||
const line = lines[startLine - 1].trim();
|
||||
|
||||
// Check lines above
|
||||
const linesAbove = getLinesAbove(heading);
|
||||
if (linesAbove >= 0) {
|
||||
let actualAbove = 0;
|
||||
for (
|
||||
let i = 0;
|
||||
(i < linesAbove) && isBlankLine(lines[startLine - 2 - i]);
|
||||
i++
|
||||
) {
|
||||
actualAbove++;
|
||||
}
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
startLine,
|
||||
linesAbove,
|
||||
actualAbove,
|
||||
"Above",
|
||||
line,
|
||||
undefined,
|
||||
{
|
||||
"insertText": getBlockQuotePrefixText(
|
||||
blockQuotePrefixes,
|
||||
startLine - 1,
|
||||
linesAbove - actualAbove
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Check lines below
|
||||
const linesBelow = getLinesBelow(heading);
|
||||
if (linesBelow >= 0) {
|
||||
let actualBelow = 0;
|
||||
for (
|
||||
let i = 0;
|
||||
(i < linesBelow) && isBlankLine(lines[endLine + i]);
|
||||
i++
|
||||
) {
|
||||
actualBelow++;
|
||||
}
|
||||
addErrorDetailIf(
|
||||
onError,
|
||||
startLine,
|
||||
linesBelow,
|
||||
actualBelow,
|
||||
"Below",
|
||||
line,
|
||||
undefined,
|
||||
{
|
||||
"lineNumber": endLine + 1,
|
||||
"insertText": getBlockQuotePrefixText(
|
||||
blockQuotePrefixes,
|
||||
endLine + 1,
|
||||
linesBelow - actualBelow
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorContext } from "../helpers/helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD023", "heading-start-left" ],
|
||||
"description": "Headings must start at the beginning of the line",
|
||||
"tags": [ "headings", "spaces" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD023(params, onError) {
|
||||
const headings = filterByTypesCached([ "atxHeading", "linePrefix", "setextHeading" ]);
|
||||
for (let i = 0; i < headings.length - 1; i++) {
|
||||
if (
|
||||
(headings[i].type === "linePrefix") &&
|
||||
(headings[i + 1].type !== "linePrefix") &&
|
||||
(headings[i].startLine === headings[i + 1].startLine)
|
||||
) {
|
||||
const { endColumn, startColumn, startLine } = headings[i];
|
||||
const length = endColumn - startColumn;
|
||||
addErrorContext(
|
||||
onError,
|
||||
startLine,
|
||||
params.lines[startLine - 1],
|
||||
true,
|
||||
false,
|
||||
[ startColumn, length ],
|
||||
{
|
||||
"editColumn": startColumn,
|
||||
"deleteCount": length
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// @ts-check
|
||||
|
||||
import { addErrorContext } from "../helpers/helpers.cjs";
|
||||
import { getHeadingLevel, getHeadingText } from "../helpers/micromark-helpers.cjs";
|
||||
import { filterByTypesCached } from "./cache.mjs";
|
||||
|
||||
/** @type {import("markdownlint").Rule} */
|
||||
export default {
|
||||
"names": [ "MD024", "no-duplicate-heading" ],
|
||||
"description": "Multiple headings with the same content",
|
||||
"tags": [ "headings" ],
|
||||
"parser": "micromark",
|
||||
"function": function MD024(params, onError) {
|
||||
const siblingsOnly = !!params.config.siblings_only || false;
|
||||
const knownContents = [ null, [] ];
|
||||
let lastLevel = 1;
|
||||
let knownContent = knownContents[lastLevel];
|
||||
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
|
||||
const headingText = getHeadingText(heading);
|
||||
if (siblingsOnly) {
|
||||
const newLevel = getHeadingLevel(heading);
|
||||
while (lastLevel < newLevel) {
|
||||
lastLevel++;
|
||||
knownContents[lastLevel] = [];
|
||||
}
|
||||
while (lastLevel > newLevel) {
|
||||
knownContents[lastLevel] = [];
|
||||
lastLevel--;
|
||||
}
|
||||
knownContent = knownContents[newLevel];
|
||||
}
|
||||
// @ts-ignore
|
||||
if (knownContent.includes(headingText)) {
|
||||
addErrorContext(
|
||||
onError,
|
||||
heading.startLine,
|
||||
headingText.trim()
|
||||
);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
knownContent.push(headingText);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user