markdownlint 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/md051.js CHANGED
@@ -2,25 +2,27 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addError, addErrorDetailIf, escapeForRegExp, filterTokens,
6
- forEachInlineChild, forEachHeading, htmlElementRe } = require("../helpers");
5
+ const { addError, addErrorDetailIf } = require("../helpers");
6
+ const { filterByHtmlTokens, filterByTypes, getHtmlTagInfo } =
7
+ require("../helpers/micromark.cjs");
7
8
 
8
9
  // Regular expression for identifying HTML anchor names
9
10
  const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu;
10
11
  const nameRe = /\sname\s*=\s*['"]?([^'"\s>]+)/iu;
12
+ const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu;
11
13
 
12
14
  /**
13
15
  * Converts a Markdown heading into an HTML fragment according to the rules
14
16
  * used by GitHub.
15
17
  *
16
- * @param {Object} inline Inline token for heading.
18
+ * @param {Object} headingText Heading text token.
17
19
  * @returns {string} Fragment string for heading.
18
20
  */
19
- function convertHeadingToHTMLFragment(inline) {
20
- const inlineText = inline.children
21
- .filter((token) => token.type !== "html_inline")
22
- .map((token) => token.content)
23
- .join("");
21
+ function convertHeadingToHTMLFragment(headingText) {
22
+ const inlineText =
23
+ filterByTypes(headingText.children, [ "codeTextData", "data" ])
24
+ .map((token) => token.text)
25
+ .join("");
24
26
  return "#" + encodeURIComponent(
25
27
  inlineText
26
28
  .toLowerCase()
@@ -41,79 +43,99 @@ module.exports = {
41
43
  "description": "Link fragments should be valid",
42
44
  "tags": [ "links" ],
43
45
  "function": function MD051(params, onError) {
46
+ const { tokens } = params.parsers.micromark;
44
47
  const fragments = new Map();
48
+
45
49
  // Process headings
46
- forEachHeading(params, (heading, content, inline) => {
47
- const fragment = convertHeadingToHTMLFragment(inline);
50
+ const headingTexts = filterByTypes(
51
+ tokens,
52
+ [ "atxHeadingText", "setextHeadingText" ]
53
+ );
54
+ for (const headingText of headingTexts) {
55
+ const fragment = convertHeadingToHTMLFragment(headingText);
48
56
  const count = fragments.get(fragment) || 0;
49
57
  if (count) {
50
58
  fragments.set(`${fragment}-${count}`, 0);
51
59
  }
52
60
  fragments.set(fragment, count + 1);
53
- });
54
- // Process HTML anchors
55
- const processHtmlToken = (token) => {
56
61
  let match = null;
57
- while ((match = htmlElementRe.exec(token.content)) !== null) {
58
- const [ tag, , element ] = match;
59
- const anchorMatch = idRe.exec(tag) ||
60
- (element.toLowerCase() === "a" && nameRe.exec(tag));
62
+ while ((match = anchorRe.exec(headingText.text)) !== null) {
63
+ const [ , anchor ] = match;
64
+ if (!fragments.has(anchor)) {
65
+ fragments.set(anchor, 1);
66
+ }
67
+ }
68
+ }
69
+
70
+ // Process HTML anchors
71
+ for (const token of filterByHtmlTokens(tokens)) {
72
+ const htmlTagInfo = getHtmlTagInfo(token);
73
+ if (htmlTagInfo && !htmlTagInfo.close) {
74
+ const anchorMatch = idRe.exec(token.text) ||
75
+ (htmlTagInfo.name.toLowerCase() === "a" && nameRe.exec(token.text));
61
76
  if (anchorMatch) {
62
77
  fragments.set(`#${anchorMatch[1]}`, 0);
63
78
  }
64
79
  }
65
- };
66
- filterTokens(params, "html_block", processHtmlToken);
67
- forEachInlineChild(params, "html_inline", processHtmlToken);
68
- // Process link fragments
69
- forEachInlineChild(params, "link_open", (token) => {
70
- const { attrs, lineNumber, line } = token;
71
- const href = attrs.find((attr) => attr[0] === "href");
72
- const id = href && href[1];
73
- if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) {
74
- let context = id;
75
- let range = null;
76
- let fixInfo = null;
77
- const match = line.match(
78
- new RegExp(`\\[.*?\\]\\(${escapeForRegExp(context)}\\)`)
79
- );
80
- if (match) {
81
- [ context ] = match;
82
- const index = match.index;
83
- const length = context.length;
84
- range = [ index + 1, length ];
85
- fixInfo = {
86
- "editColumn": index + (length - id.length),
87
- "deleteCount": id.length,
88
- "insertText": null
89
- };
90
- }
91
- const idLower = id.toLowerCase();
92
- const mixedCaseKey = [ ...fragments.keys() ]
93
- .find((key) => idLower === key.toLowerCase());
94
- if (mixedCaseKey) {
95
- (fixInfo || {}).insertText = mixedCaseKey;
96
- addErrorDetailIf(
97
- onError,
98
- lineNumber,
99
- mixedCaseKey,
100
- id,
101
- undefined,
102
- context,
103
- range,
104
- fixInfo
105
- );
106
- } else {
107
- addError(
108
- onError,
109
- lineNumber,
110
- undefined,
111
- context,
112
- // @ts-ignore
113
- range
114
- );
80
+ }
81
+
82
+ // Process link and definition fragments
83
+ const parentChilds = [
84
+ [ "link", "resourceDestinationString" ],
85
+ [ "definition", "definitionDestinationString" ]
86
+ ];
87
+ for (const [ parentType, definitionType ] of parentChilds) {
88
+ const links = filterByTypes(tokens, [ parentType ]);
89
+ for (const link of links) {
90
+ const definitions = filterByTypes(link.children, [ definitionType ]);
91
+ for (const definition of definitions) {
92
+ if (
93
+ (definition.text.length > 1) &&
94
+ definition.text.startsWith("#") &&
95
+ !fragments.has(definition.text)
96
+ ) {
97
+ // eslint-disable-next-line no-undef-init
98
+ let context = undefined;
99
+ // eslint-disable-next-line no-undef-init
100
+ let range = undefined;
101
+ // eslint-disable-next-line no-undef-init
102
+ let fixInfo = undefined;
103
+ if (link.startLine === link.endLine) {
104
+ context = link.text;
105
+ range = [ link.startColumn, link.endColumn - link.startColumn ];
106
+ fixInfo = {
107
+ "editColumn": definition.startColumn,
108
+ "deleteCount": definition.endColumn - definition.startColumn
109
+ };
110
+ }
111
+ const definitionTextLower = definition.text.toLowerCase();
112
+ const mixedCaseKey = [ ...fragments.keys() ]
113
+ .find((key) => definitionTextLower === key.toLowerCase());
114
+ if (mixedCaseKey) {
115
+ // @ts-ignore
116
+ (fixInfo || {}).insertText = mixedCaseKey;
117
+ addErrorDetailIf(
118
+ onError,
119
+ link.startLine,
120
+ mixedCaseKey,
121
+ definition.text,
122
+ undefined,
123
+ context,
124
+ range,
125
+ fixInfo
126
+ );
127
+ } else {
128
+ addError(
129
+ onError,
130
+ link.startLine,
131
+ undefined,
132
+ context,
133
+ range
134
+ );
135
+ }
136
+ }
115
137
  }
116
138
  }
117
- });
139
+ }
118
140
  }
119
141
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdownlint",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "A Node.js style checker and lint tool for Markdown/CommonMark files.",
5
5
  "type": "commonjs",
6
6
  "main": "./lib/markdownlint.js",
@@ -33,17 +33,16 @@
33
33
  "ci": "npm-run-all --continue-on-error --parallel lint serial-config-docs serial-declaration-demo test-cover && git diff --exit-code",
34
34
  "clone-test-repos-apache-airflow": "cd test-repos && git clone https://github.com/apache/airflow apache-airflow --depth 1 --no-tags --quiet",
35
35
  "clone-test-repos-dotnet-docs": "cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet",
36
- "clone-test-repos-electron-electron": "cd test-repos && git clone https://github.com/electron/electron electron-electron --depth 1 --no-tags --quiet && cd electron-electron && npm install --ignore-scripts @electron/lint-roller",
36
+ "clone-test-repos-electron-electron": "cd test-repos && git clone https://github.com/electron/electron electron-electron --depth 1 --no-tags --quiet && cd electron-electron && npm install --ignore-scripts @electron/lint-roller typescript@4",
37
37
  "clone-test-repos-eslint-eslint": "cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet",
38
38
  "clone-test-repos-mdn-content": "cd test-repos && git clone https://github.com/mdn/content mdn-content --depth 1 --no-tags --quiet",
39
- "clone-test-repos-mdn-translated-content": "cd test-repos && git clone https://github.com/mdn/translated-content mdn-translated-content --depth 1 --no-tags --quiet",
40
39
  "clone-test-repos-mkdocs-mkdocs": "cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet",
41
40
  "clone-test-repos-mochajs-mocha": "cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet",
42
41
  "clone-test-repos-pi-hole-docs": "cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet",
43
42
  "clone-test-repos-v8-v8-dev": "cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet",
44
43
  "clone-test-repos-webhintio-hint": "cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet",
45
44
  "clone-test-repos-webpack-webpack-js-org": "cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet",
46
- "clone-test-repos": "mkdir test-repos && cd test-repos && npm run clone-test-repos-apache-airflow && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-electron-electron && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mdn-content && npm run clone-test-repos-mdn-translated-content && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-v8-v8-dev && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org",
45
+ "clone-test-repos": "mkdir test-repos && cd test-repos && npm run clone-test-repos-apache-airflow && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-electron-electron && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mdn-content && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-v8-v8-dev && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org",
47
46
  "declaration": "npm run build-declaration && npm run test-declaration",
48
47
  "example": "cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint",
49
48
  "docker-npm-install": "docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:latest npm install",
@@ -54,10 +53,11 @@
54
53
  "serial-config-docs": "npm run build-config && npm run build-docs",
55
54
  "serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration",
56
55
  "test": "ava test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js",
57
- "test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --exclude 'test/**' --exclude 'micromark/**' npm test",
56
+ "test-cover": "c8 --100 npm test",
58
57
  "test-declaration": "cd example/typescript && tsc && node type-check.js",
59
58
  "test-extra": "ava --timeout=5m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js",
60
59
  "update-snapshots": "ava --update-snapshots test/markdownlint-test-micromark.mjs test/markdownlint-test-scenarios.js",
60
+ "update-snapshots-test-repos": "ava --timeout=10m --update-snapshots test/markdownlint-test-repos.js",
61
61
  "upgrade": "npx --yes npm-check-updates --upgrade"
62
62
  },
63
63
  "engines": {
@@ -65,35 +65,36 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "markdown-it": "13.0.1",
68
- "markdownlint-micromark": "0.1.5"
68
+ "markdownlint-micromark": "0.1.7"
69
69
  },
70
70
  "devDependencies": {
71
- "@babel/core": "7.22.1",
72
- "@babel/preset-env": "7.22.4",
73
- "ava": "5.3.0",
74
- "babel-loader": "9.1.2",
75
- "c8": "7.14.0",
76
- "eslint": "8.41.0",
71
+ "@babel/core": "7.22.10",
72
+ "@babel/preset-env": "7.22.10",
73
+ "ava": "5.3.1",
74
+ "babel-loader": "9.1.3",
75
+ "c8": "8.0.1",
76
+ "character-entities": "2.0.2",
77
+ "eslint": "8.46.0",
77
78
  "eslint-plugin-es": "4.1.0",
78
- "eslint-plugin-jsdoc": "46.1.0",
79
- "eslint-plugin-n": "16.0.0",
79
+ "eslint-plugin-jsdoc": "46.4.6",
80
+ "eslint-plugin-n": "16.0.1",
80
81
  "eslint-plugin-regexp": "1.15.0",
81
- "eslint-plugin-unicorn": "47.0.0",
82
- "globby": "13.1.4",
82
+ "eslint-plugin-unicorn": "48.0.1",
83
+ "gemoji": "8.1.0",
84
+ "globby": "13.2.2",
83
85
  "js-yaml": "4.1.0",
84
86
  "markdown-it-for-inline": "0.1.1",
85
87
  "markdown-it-sub": "1.0.0",
86
88
  "markdown-it-sup": "1.0.0",
87
- "markdown-it-texmath": "1.0.0",
88
- "markdownlint-rule-helpers": "0.19.0",
89
+ "markdownlint-rule-helpers": "0.20.0",
89
90
  "npm-run-all": "4.1.5",
90
- "strip-json-comments": "5.0.0",
91
+ "strip-json-comments": "5.0.1",
91
92
  "terser-webpack-plugin": "5.3.9",
92
93
  "toml": "3.0.0",
93
94
  "tv4": "1.3.0",
94
- "typescript": "5.1.3",
95
- "webpack": "5.85.0",
96
- "webpack-cli": "5.1.1",
95
+ "typescript": "5.1.6",
96
+ "webpack": "5.88.2",
97
+ "webpack-cli": "5.1.4",
97
98
  "yaml": "2.3.1"
98
99
  },
99
100
  "keywords": [
@@ -139,7 +139,7 @@
139
139
 
140
140
  // MD026/no-trailing-punctuation - Trailing punctuation in heading
141
141
  "MD026": {
142
- // Punctuation characters not allowed at end of headings
142
+ // Punctuation characters
143
143
  "punctuation": ".,;:!。,;:!"
144
144
  },
145
145
 
@@ -265,13 +265,13 @@
265
265
 
266
266
  // MD049/emphasis-style - Emphasis style should be consistent
267
267
  "MD049": {
268
- // Emphasis style should be consistent
268
+ // Emphasis style
269
269
  "style": "consistent"
270
270
  },
271
271
 
272
272
  // MD050/strong-style - Strong style should be consistent
273
273
  "MD050": {
274
- // Strong style should be consistent
274
+ // Strong style
275
275
  "style": "consistent"
276
276
  },
277
277
 
@@ -127,7 +127,7 @@ MD025:
127
127
 
128
128
  # MD026/no-trailing-punctuation - Trailing punctuation in heading
129
129
  MD026:
130
- # Punctuation characters not allowed at end of headings
130
+ # Punctuation characters
131
131
  punctuation: ".,;:!。,;:!"
132
132
 
133
133
  # MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol
@@ -240,12 +240,12 @@ MD048:
240
240
 
241
241
  # MD049/emphasis-style - Emphasis style should be consistent
242
242
  MD049:
243
- # Emphasis style should be consistent
243
+ # Emphasis style
244
244
  style: "consistent"
245
245
 
246
246
  # MD050/strong-style - Strong style should be consistent
247
247
  MD050:
248
- # Strong style should be consistent
248
+ # Strong style
249
249
  style: "consistent"
250
250
 
251
251
  # MD051/link-fragments - Link fragments should be valid
@@ -361,14 +361,26 @@
361
361
  "properties": {
362
362
  "lines_above": {
363
363
  "description": "Blank lines above heading",
364
- "type": "integer",
365
- "minimum": 0,
364
+ "type": [
365
+ "integer",
366
+ "array"
367
+ ],
368
+ "items": {
369
+ "type": "integer"
370
+ },
371
+ "minimum": -1,
366
372
  "default": 1
367
373
  },
368
374
  "lines_below": {
369
375
  "description": "Blank lines below heading",
370
- "type": "integer",
371
- "minimum": 0,
376
+ "type": [
377
+ "integer",
378
+ "array"
379
+ ],
380
+ "items": {
381
+ "type": "integer"
382
+ },
383
+ "minimum": -1,
372
384
  "default": 1
373
385
  }
374
386
  },
@@ -456,7 +468,7 @@
456
468
  "default": true,
457
469
  "properties": {
458
470
  "punctuation": {
459
- "description": "Punctuation characters not allowed at end of headings",
471
+ "description": "Punctuation characters",
460
472
  "type": "string",
461
473
  "default": ".,;:!。,;:!"
462
474
  }
@@ -876,7 +888,7 @@
876
888
  "default": true,
877
889
  "properties": {
878
890
  "style": {
879
- "description": "Emphasis style should be consistent",
891
+ "description": "Emphasis style",
880
892
  "type": "string",
881
893
  "enum": [
882
894
  "consistent",
@@ -900,7 +912,7 @@
900
912
  "default": true,
901
913
  "properties": {
902
914
  "style": {
903
- "description": "Strong style should be consistent",
915
+ "description": "Strong style",
904
916
  "type": "string",
905
917
  "enum": [
906
918
  "consistent",