eslint-markdown 0.5.0 → 0.6.1
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/README.md +6 -0
- package/build/configs/all.d.ts +1 -0
- package/build/configs/stylistic.d.ts +1 -0
- package/build/core/ast/index.d.ts +2 -1
- package/build/core/ast/is-blank-line.d.ts +8 -0
- package/build/core/constants.d.ts +1 -1
- package/build/core/{tests/rule-tester.d.ts → rule-tester.d.ts} +1 -1
- package/build/rules/consistent-code-style.d.ts +32 -2
- package/build/rules/consistent-inline-code-style.d.ts +29 -0
- package/build/rules/index.d.ts +2 -1
- package/package.json +4 -2
- package/src/configs/all.js +1 -0
- package/src/configs/stylistic.js +1 -0
- package/src/core/ast/html.js +1 -1
- package/src/core/ast/index.js +2 -1
- package/src/core/ast/is-blank-line.js +43 -0
- package/src/core/constants.js +5 -12
- package/src/core/{tests/rule-tester.js → rule-tester.js} +1 -1
- package/src/rules/consistent-code-style.js +134 -3
- package/src/rules/consistent-inline-code-style.js +152 -0
- package/src/rules/consistent-unordered-list-style.js +1 -1
- package/src/rules/index.js +2 -0
- package/build/core/tests/get-file-name.d.ts +0 -6
- package/build/core/tests/index.d.ts +0 -3
- package/src/core/tests/get-file-name.js +0 -23
- package/src/core/tests/index.js +0 -6
package/README.md
CHANGED
|
@@ -25,6 +25,12 @@ For full documentation, see the [official documentation of the `eslint-markdown`
|
|
|
25
25
|
- [Migration Guide](https://eslint-markdown.lumir.page/docs/get-started/migration)
|
|
26
26
|
- [Rules](https://eslint-markdown.lumir.page/docs/rules)
|
|
27
27
|
|
|
28
|
+
## Compatibility
|
|
29
|
+
|
|
30
|
+
This [`eslint-markdown`](https://github.com/lumirlumir/npm-eslint-markdown#readme) plugin does not include any rules that overlap with ESLint's built-in Markdown rules provided by [`@eslint/markdown`](https://github.com/eslint/markdown#readme).
|
|
31
|
+
|
|
32
|
+
So, we **highly recommend** using the `eslint-markdown` plugin alongside ESLint's built-in Markdown support, `@eslint/markdown`.
|
|
33
|
+
|
|
28
34
|
## Code of Conduct
|
|
29
35
|
|
|
30
36
|
See [Code of Conduct](https://github.com/lumirlumir/.github/blob/main/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct).
|
package/build/configs/all.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export default function all(plugin: ESLint.Plugin): {
|
|
|
20
20
|
readonly 'md/consistent-code-style': "error";
|
|
21
21
|
readonly 'md/consistent-delete-style': "error";
|
|
22
22
|
readonly 'md/consistent-emphasis-style': "error";
|
|
23
|
+
readonly 'md/consistent-inline-code-style': "error";
|
|
23
24
|
readonly 'md/consistent-strong-style': "error";
|
|
24
25
|
readonly 'md/consistent-thematic-break-style': "error";
|
|
25
26
|
readonly 'md/consistent-unordered-list-style': "error";
|
|
@@ -17,6 +17,7 @@ export default function stylistic(plugin: ESLint.Plugin): {
|
|
|
17
17
|
readonly 'md/consistent-code-style': "error";
|
|
18
18
|
readonly 'md/consistent-delete-style': "error";
|
|
19
19
|
readonly 'md/consistent-emphasis-style': "error";
|
|
20
|
+
readonly 'md/consistent-inline-code-style': "error";
|
|
20
21
|
readonly 'md/consistent-strong-style': "error";
|
|
21
22
|
readonly 'md/consistent-thematic-break-style': "error";
|
|
22
23
|
readonly 'md/consistent-unordered-list-style': "error";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a line is blank.
|
|
3
|
+
* - NOTE: The `blockquoteDepth` parameter is zero-based: `0` means one blockquote marker, `1` means two, and so on.
|
|
4
|
+
* @param {string} str Line string.
|
|
5
|
+
* @param {number} [blockquoteDepth] The depth of blockquotes. Default is `-1`.
|
|
6
|
+
* @returns {boolean} `true` if the line is blank. `false` otherwise.
|
|
7
|
+
*/
|
|
8
|
+
export default function isBlankLine(str: string, blockquoteDepth?: number): boolean;
|
|
@@ -8,6 +8,6 @@ export default function ruleTester(ruleName: string, rule: RuleModule<RuleOption
|
|
|
8
8
|
export type RuleOptions = MarkdownRuleDefinitionTypeOptions["RuleOptions"];
|
|
9
9
|
export type MessageIds = MarkdownRuleDefinitionTypeOptions["MessageIds"];
|
|
10
10
|
export type Tests = Parameters<RuleTester["run"]>[2];
|
|
11
|
-
import type { RuleModule } from '
|
|
11
|
+
import type { RuleModule } from './types.js';
|
|
12
12
|
import type { MarkdownRuleDefinitionTypeOptions } from '@eslint/markdown';
|
|
13
13
|
import { RuleTester } from 'eslint';
|
|
@@ -13,14 +13,40 @@ declare const _default: {
|
|
|
13
13
|
style: {
|
|
14
14
|
enum: string[];
|
|
15
15
|
};
|
|
16
|
+
blankLineAbove: {
|
|
17
|
+
oneOf: ({
|
|
18
|
+
enum: false[];
|
|
19
|
+
type?: never;
|
|
20
|
+
minimum?: never;
|
|
21
|
+
} | {
|
|
22
|
+
type: "integer";
|
|
23
|
+
minimum: number;
|
|
24
|
+
enum?: never;
|
|
25
|
+
})[];
|
|
26
|
+
};
|
|
27
|
+
blankLineBelow: {
|
|
28
|
+
oneOf: ({
|
|
29
|
+
enum: false[];
|
|
30
|
+
type?: never;
|
|
31
|
+
minimum?: never;
|
|
32
|
+
} | {
|
|
33
|
+
type: "integer";
|
|
34
|
+
minimum: number;
|
|
35
|
+
enum?: never;
|
|
36
|
+
})[];
|
|
37
|
+
};
|
|
16
38
|
};
|
|
17
39
|
additionalProperties: false;
|
|
18
40
|
}[];
|
|
19
41
|
defaultOptions: [{
|
|
20
42
|
style: "consistent";
|
|
43
|
+
blankLineAbove: false;
|
|
44
|
+
blankLineBelow: false;
|
|
21
45
|
}];
|
|
22
46
|
messages: {
|
|
23
47
|
style: string;
|
|
48
|
+
blankLineAbove: string;
|
|
49
|
+
blankLineBelow: string;
|
|
24
50
|
};
|
|
25
51
|
language: string;
|
|
26
52
|
dialects: string[];
|
|
@@ -30,14 +56,18 @@ declare const _default: {
|
|
|
30
56
|
Code: import("@eslint/markdown").MarkdownSourceCode;
|
|
31
57
|
RuleOptions: RuleOptions;
|
|
32
58
|
Node: import("mdast").Node;
|
|
33
|
-
MessageIds:
|
|
59
|
+
MessageIds: MessageIds;
|
|
34
60
|
}>): {
|
|
61
|
+
blockquote(): void;
|
|
35
62
|
code(node: import("mdast").Code): void;
|
|
63
|
+
'blockquote:exit'(): void;
|
|
36
64
|
};
|
|
37
65
|
};
|
|
38
66
|
export default _default;
|
|
39
67
|
export type CodeStyle = "indent" | "fence-backtick" | "fence-tilde";
|
|
40
68
|
export type RuleOptions = [{
|
|
41
69
|
style: "consistent" | CodeStyle;
|
|
70
|
+
blankLineAbove: number | false;
|
|
71
|
+
blankLineBelow: number | false;
|
|
42
72
|
}];
|
|
43
|
-
export type MessageIds = "style";
|
|
73
|
+
export type MessageIds = "style" | "blankLineAbove" | "blankLineBelow";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "layout";
|
|
4
|
+
docs: {
|
|
5
|
+
description: string;
|
|
6
|
+
url: string;
|
|
7
|
+
recommended: boolean;
|
|
8
|
+
stylistic: true;
|
|
9
|
+
};
|
|
10
|
+
fixable: "whitespace";
|
|
11
|
+
messages: {
|
|
12
|
+
style: string;
|
|
13
|
+
};
|
|
14
|
+
language: string;
|
|
15
|
+
dialects: string[];
|
|
16
|
+
};
|
|
17
|
+
create(context: import("@eslint/core").RuleContext<{
|
|
18
|
+
LangOptions: import("@eslint/markdown").MarkdownLanguageOptions;
|
|
19
|
+
Code: import("@eslint/markdown").MarkdownSourceCode;
|
|
20
|
+
RuleOptions: [];
|
|
21
|
+
Node: import("mdast").Node;
|
|
22
|
+
MessageIds: "style";
|
|
23
|
+
}>): {
|
|
24
|
+
inlineCode(node: import("mdast").InlineCode): void;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export default _default;
|
|
28
|
+
export type RuleOptions = [];
|
|
29
|
+
export type MessageIds = "style";
|
package/build/rules/index.d.ts
CHANGED
|
@@ -2,9 +2,10 @@ declare const _default: {
|
|
|
2
2
|
'allow-image-url': import("../core/types.js").RuleModule<import("./allow-image-url.js").RuleOptions, import("./allow-image-url.js").MessageIds>;
|
|
3
3
|
'allow-link-url': import("../core/types.js").RuleModule<import("./allow-link-url.js").RuleOptions, import("./allow-link-url.js").MessageIds>;
|
|
4
4
|
'code-lang-shorthand': import("../core/types.js").RuleModule<import("./code-lang-shorthand.js").RuleOptions, "codeLangShorthand">;
|
|
5
|
-
'consistent-code-style': import("../core/types.js").RuleModule<import("./consistent-code-style.js").RuleOptions, "style">;
|
|
5
|
+
'consistent-code-style': import("../core/types.js").RuleModule<import("./consistent-code-style.js").RuleOptions, import("./consistent-code-style.js").MessageIds>;
|
|
6
6
|
'consistent-delete-style': import("../core/types.js").RuleModule<import("./consistent-delete-style.js").RuleOptions, "style">;
|
|
7
7
|
'consistent-emphasis-style': import("../core/types.js").RuleModule<import("./consistent-emphasis-style.js").RuleOptions, "style">;
|
|
8
|
+
'consistent-inline-code-style': import("../core/types.js").RuleModule<[], "style">;
|
|
8
9
|
'consistent-strong-style': import("../core/types.js").RuleModule<import("./consistent-strong-style.js").RuleOptions, "style">;
|
|
9
10
|
'consistent-thematic-break-style': import("../core/types.js").RuleModule<import("./consistent-thematic-break-style.js").RuleOptions, "style">;
|
|
10
11
|
'consistent-unordered-list-style': import("../core/types.js").RuleModule<import("./consistent-unordered-list-style.js").RuleOptions, "style">;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lint your Markdown with ESLint.🛠️",
|
|
6
6
|
"exports": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"gfm"
|
|
41
41
|
],
|
|
42
42
|
"author": "루밀LuMir <rpfos@naver.com> (https://github.com/lumirlumir)",
|
|
43
|
+
"funding": "https://github.com/sponsors/lumirlumir",
|
|
43
44
|
"license": "MIT",
|
|
44
45
|
"homepage": "https://eslint-markdown.lumir.page",
|
|
45
46
|
"repository": {
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
},
|
|
79
80
|
"devDependencies": {
|
|
80
81
|
"@types/mdast": "^4.0.4",
|
|
81
|
-
"
|
|
82
|
+
"@types/unist": "^3.0.3",
|
|
83
|
+
"eslint": "^9.39.4"
|
|
82
84
|
}
|
|
83
85
|
}
|
package/src/configs/all.js
CHANGED
|
@@ -44,6 +44,7 @@ export default function all(plugin) {
|
|
|
44
44
|
'md/consistent-code-style': 'error',
|
|
45
45
|
'md/consistent-delete-style': 'error',
|
|
46
46
|
'md/consistent-emphasis-style': 'error',
|
|
47
|
+
'md/consistent-inline-code-style': 'error',
|
|
47
48
|
'md/consistent-strong-style': 'error',
|
|
48
49
|
'md/consistent-thematic-break-style': 'error',
|
|
49
50
|
'md/consistent-unordered-list-style': 'error',
|
package/src/configs/stylistic.js
CHANGED
|
@@ -41,6 +41,7 @@ export default function stylistic(plugin) {
|
|
|
41
41
|
'md/consistent-code-style': 'error',
|
|
42
42
|
'md/consistent-delete-style': 'error',
|
|
43
43
|
'md/consistent-emphasis-style': 'error',
|
|
44
|
+
'md/consistent-inline-code-style': 'error',
|
|
44
45
|
'md/consistent-strong-style': 'error',
|
|
45
46
|
'md/consistent-thematic-break-style': 'error',
|
|
46
47
|
'md/consistent-unordered-list-style': 'error',
|
package/src/core/ast/html.js
CHANGED
package/src/core/ast/index.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Check if a line is blank.
|
|
3
|
+
* @see https://spec.commonmark.org/0.31.2/#blank-line
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// --------------------------------------------------------------------------------
|
|
7
|
+
// Helper
|
|
8
|
+
// --------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
const whitespaceChars = new Set([' ', '\t']);
|
|
11
|
+
const blockquoteChar = '>';
|
|
12
|
+
|
|
13
|
+
// --------------------------------------------------------------------------------
|
|
14
|
+
// Export
|
|
15
|
+
// --------------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if a line is blank.
|
|
19
|
+
* - NOTE: The `blockquoteDepth` parameter is zero-based: `0` means one blockquote marker, `1` means two, and so on.
|
|
20
|
+
* @param {string} str Line string.
|
|
21
|
+
* @param {number} [blockquoteDepth] The depth of blockquotes. Default is `-1`.
|
|
22
|
+
* @returns {boolean} `true` if the line is blank. `false` otherwise.
|
|
23
|
+
*/
|
|
24
|
+
export default function isBlankLine(str, blockquoteDepth = -1) {
|
|
25
|
+
// `.length` is cached for performance.
|
|
26
|
+
const strLength = str.length;
|
|
27
|
+
let remainingBlockquotes = blockquoteDepth + 1;
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < strLength; i++) {
|
|
30
|
+
const char = str[i];
|
|
31
|
+
|
|
32
|
+
if (!whitespaceChars.has(char)) {
|
|
33
|
+
if (remainingBlockquotes > 0 && char === blockquoteChar) {
|
|
34
|
+
remainingBlockquotes--;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
}
|
package/src/core/constants.js
CHANGED
|
@@ -6,22 +6,15 @@
|
|
|
6
6
|
// Import
|
|
7
7
|
// --------------------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
// --------------------------------------------------------------------------------
|
|
12
|
-
// Declaration
|
|
13
|
-
// --------------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
/** @type {{ homepage: string, name: 'eslint-markdown', version: string }} */
|
|
16
|
-
const { homepage, name, version } = createRequire(import.meta.url)('../../package.json');
|
|
9
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
17
10
|
|
|
18
11
|
// --------------------------------------------------------------------------------
|
|
19
12
|
// Export
|
|
20
13
|
// --------------------------------------------------------------------------------
|
|
21
14
|
|
|
15
|
+
/** @type {'eslint-markdown'} */
|
|
16
|
+
export const PKG_NAME = /** @type {'eslint-markdown'} */ (pkg.name);
|
|
22
17
|
/** @satisfies {string} */
|
|
23
|
-
export const
|
|
24
|
-
/** @satisfies {string} */
|
|
25
|
-
export const PKG_VERSION = version;
|
|
18
|
+
export const PKG_VERSION = pkg.version;
|
|
26
19
|
/** Get the URL for the rule documentation based on the rule name. @param {string} [ruleName] */
|
|
27
|
-
export const URL_RULE_DOCS = (ruleName = '') => `${homepage}/docs/rules/${ruleName}`;
|
|
20
|
+
export const URL_RULE_DOCS = (ruleName = '') => `${pkg.homepage}/docs/rules/${ruleName}`;
|
|
@@ -17,7 +17,7 @@ import markdown from '@eslint/markdown';
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @import { MarkdownRuleDefinitionTypeOptions } from '@eslint/markdown';
|
|
20
|
-
* @import { RuleModule } from '
|
|
20
|
+
* @import { RuleModule } from './types.js';
|
|
21
21
|
* @typedef {MarkdownRuleDefinitionTypeOptions['RuleOptions']} RuleOptions
|
|
22
22
|
* @typedef {MarkdownRuleDefinitionTypeOptions['MessageIds']} MessageIds
|
|
23
23
|
* @typedef {Parameters<RuleTester['run']>[2]} Tests
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// Import
|
|
18
18
|
// --------------------------------------------------------------------------------
|
|
19
19
|
|
|
20
|
+
import { isBlankLine } from '../core/ast/index.js';
|
|
20
21
|
import { URL_RULE_DOCS } from '../core/constants.js';
|
|
21
22
|
|
|
22
23
|
// --------------------------------------------------------------------------------
|
|
@@ -26,8 +27,8 @@ import { URL_RULE_DOCS } from '../core/constants.js';
|
|
|
26
27
|
/**
|
|
27
28
|
* @import { RuleModule } from '../core/types.js';
|
|
28
29
|
* @typedef {'indent' | 'fence-backtick' | 'fence-tilde'} CodeStyle
|
|
29
|
-
* @typedef {[{ style: 'consistent' | CodeStyle }]} RuleOptions
|
|
30
|
-
* @typedef {'style'} MessageIds
|
|
30
|
+
* @typedef {[{ style: 'consistent' | CodeStyle, blankLineAbove: number | false, blankLineBelow: number | false }]} RuleOptions
|
|
31
|
+
* @typedef {'style' | 'blankLineAbove' | 'blankLineBelow'} MessageIds
|
|
31
32
|
*/
|
|
32
33
|
|
|
33
34
|
// --------------------------------------------------------------------------------
|
|
@@ -76,6 +77,28 @@ export default {
|
|
|
76
77
|
style: {
|
|
77
78
|
enum: ['consistent', 'indent', 'fence-backtick', 'fence-tilde'],
|
|
78
79
|
},
|
|
80
|
+
blankLineAbove: {
|
|
81
|
+
oneOf: [
|
|
82
|
+
{
|
|
83
|
+
enum: [false],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'integer',
|
|
87
|
+
minimum: 1,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
blankLineBelow: {
|
|
92
|
+
oneOf: [
|
|
93
|
+
{
|
|
94
|
+
enum: [false],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'integer',
|
|
98
|
+
minimum: 1,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
79
102
|
},
|
|
80
103
|
additionalProperties: false,
|
|
81
104
|
},
|
|
@@ -84,11 +107,17 @@ export default {
|
|
|
84
107
|
defaultOptions: [
|
|
85
108
|
{
|
|
86
109
|
style: 'consistent',
|
|
110
|
+
blankLineAbove: false,
|
|
111
|
+
blankLineBelow: false,
|
|
87
112
|
},
|
|
88
113
|
],
|
|
89
114
|
|
|
90
115
|
messages: {
|
|
91
116
|
style: 'Code style should be `{{ style }}`.',
|
|
117
|
+
blankLineAbove:
|
|
118
|
+
'Code should be surrounded by {{ blankLineAbove }} blank line(s) above.',
|
|
119
|
+
blankLineBelow:
|
|
120
|
+
'Code should be surrounded by {{ blankLineBelow }} blank line(s) below.',
|
|
92
121
|
},
|
|
93
122
|
|
|
94
123
|
language: 'markdown',
|
|
@@ -98,13 +127,24 @@ export default {
|
|
|
98
127
|
|
|
99
128
|
create(context) {
|
|
100
129
|
const { sourceCode } = context;
|
|
101
|
-
const
|
|
130
|
+
const { lines } = sourceCode;
|
|
131
|
+
const [{ style, blankLineAbove, blankLineBelow }] = context.options;
|
|
102
132
|
|
|
103
133
|
/** @type {CodeStyle | null} */
|
|
104
134
|
let codeStyle = style === 'consistent' ? null : style;
|
|
135
|
+
let blockquoteDepth = -1; // NOTE: Depth `0` is the first blockquote level, which is the top level.
|
|
105
136
|
|
|
106
137
|
return {
|
|
138
|
+
blockquote() {
|
|
139
|
+
// When entering a `blockquote` node, increase the depth.
|
|
140
|
+
blockquoteDepth++;
|
|
141
|
+
},
|
|
142
|
+
|
|
107
143
|
code(node) {
|
|
144
|
+
// ------------------------------------------------------------------------
|
|
145
|
+
// 1. Check code style consistency.
|
|
146
|
+
// ------------------------------------------------------------------------
|
|
147
|
+
|
|
108
148
|
const [nodeStartOffset] = sourceCode.getRange(node);
|
|
109
149
|
const currentCodeStyle = getCurrentCodeStyle(sourceCode.text[nodeStartOffset]);
|
|
110
150
|
|
|
@@ -123,6 +163,97 @@ export default {
|
|
|
123
163
|
},
|
|
124
164
|
});
|
|
125
165
|
}
|
|
166
|
+
|
|
167
|
+
// ------------------------------------------------------------------------
|
|
168
|
+
// 2. Check blank lines above the code block.
|
|
169
|
+
// ------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
// `markdownlint` doesn't check blank lines above indented code blocks, so we skip this check for the `indent` style.
|
|
172
|
+
if (blankLineAbove !== false && currentCodeStyle !== 'indent') {
|
|
173
|
+
const {
|
|
174
|
+
start: { line: nodeStartLine },
|
|
175
|
+
} = sourceCode.getLoc(node);
|
|
176
|
+
const nodeStartLineIndex = nodeStartLine - 1;
|
|
177
|
+
|
|
178
|
+
for (
|
|
179
|
+
let i = nodeStartLineIndex - 1; // Start checking from the line above the code block.
|
|
180
|
+
i >= nodeStartLineIndex - blankLineAbove; // Check up to the specified number of blank lines.
|
|
181
|
+
i-- // Move upwards through the lines.
|
|
182
|
+
) {
|
|
183
|
+
const line = lines[i];
|
|
184
|
+
|
|
185
|
+
// If the line is `undefined`, it means we've reached the beginning of the file.
|
|
186
|
+
if (line === undefined) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// If the line is blank, continue checking the next line. If it's not blank, report the issue.
|
|
191
|
+
if (isBlankLine(line, blockquoteDepth)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
context.report({
|
|
196
|
+
node,
|
|
197
|
+
|
|
198
|
+
messageId: 'blankLineAbove',
|
|
199
|
+
|
|
200
|
+
data: {
|
|
201
|
+
blankLineAbove,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// No need to check further once we've found a non-blank line.
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ------------------------------------------------------------------------
|
|
211
|
+
// 3. Check blank lines below the code block.
|
|
212
|
+
// ------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
// `markdownlint` doesn't check blank lines below indented code blocks, so we skip this check for the `indent` style.
|
|
215
|
+
if (blankLineBelow !== false && currentCodeStyle !== 'indent') {
|
|
216
|
+
const {
|
|
217
|
+
end: { line: nodeEndLine },
|
|
218
|
+
} = sourceCode.getLoc(node);
|
|
219
|
+
const nodeEndLineIndex = nodeEndLine - 1;
|
|
220
|
+
|
|
221
|
+
for (
|
|
222
|
+
let i = nodeEndLineIndex + 1; // Start checking from the line below the code block.
|
|
223
|
+
i <= nodeEndLineIndex + blankLineBelow; // Check up to the specified number of blank lines.
|
|
224
|
+
i++ // Move downwards through the lines.
|
|
225
|
+
) {
|
|
226
|
+
const line = lines[i];
|
|
227
|
+
|
|
228
|
+
// If the line is `undefined`, it means we've reached the end of the file.
|
|
229
|
+
if (line === undefined) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// If the line is blank, continue checking the next line. If it's not blank, report the issue.
|
|
234
|
+
if (isBlankLine(line, blockquoteDepth)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
context.report({
|
|
239
|
+
node,
|
|
240
|
+
|
|
241
|
+
messageId: 'blankLineBelow',
|
|
242
|
+
|
|
243
|
+
data: {
|
|
244
|
+
blankLineBelow,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// No need to check further once we've found a non-blank line.
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
'blockquote:exit'() {
|
|
255
|
+
// When exiting a `blockquote` node, decrease the depth.
|
|
256
|
+
blockquoteDepth--;
|
|
126
257
|
},
|
|
127
258
|
};
|
|
128
259
|
},
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule to enforce consistent inline code style.
|
|
3
|
+
* @author 루밀LuMir(lumirlumir)
|
|
4
|
+
* @see https://github.com/DavidAnson/markdownlint/blob/v0.40.0/lib/md038.mjs
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// --------------------------------------------------------------------------------
|
|
8
|
+
// Import
|
|
9
|
+
// --------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
import { URL_RULE_DOCS } from '../core/constants.js';
|
|
12
|
+
|
|
13
|
+
// --------------------------------------------------------------------------------
|
|
14
|
+
// Typedef
|
|
15
|
+
// --------------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @import { RuleModule } from '../core/types.js';
|
|
19
|
+
* @typedef {[]} RuleOptions
|
|
20
|
+
* @typedef {'style'} MessageIds
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// --------------------------------------------------------------------------------
|
|
24
|
+
// Helper
|
|
25
|
+
// --------------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
// `\s` in regular expressions matches whitespace characters beyond `\r`, `\n`, ` `, and `\t`,
|
|
28
|
+
// so we explicitly use `[\r\n \t]` to match those characters to avoid unexpected matches.
|
|
29
|
+
const leadingInlineCodeRegex =
|
|
30
|
+
/^(?<leadingBackticks>`*)(?<leadingSpaces>[\r\n \t]+)(?<firstChar>[^\r\n \t])/;
|
|
31
|
+
const trailingInlineCodeRegex =
|
|
32
|
+
/(?<lastChar>[^\r\n \t])(?<trailingSpaces>[\r\n \t]+)(?<trailingBackticks>`*)$/;
|
|
33
|
+
|
|
34
|
+
// --------------------------------------------------------------------------------
|
|
35
|
+
// Rule Definition
|
|
36
|
+
// --------------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/** @type {RuleModule<RuleOptions, MessageIds>} */
|
|
39
|
+
export default {
|
|
40
|
+
meta: {
|
|
41
|
+
type: 'layout',
|
|
42
|
+
|
|
43
|
+
docs: {
|
|
44
|
+
description: 'Enforce consistent inline code style',
|
|
45
|
+
url: URL_RULE_DOCS('consistent-inline-code-style'),
|
|
46
|
+
recommended: false,
|
|
47
|
+
stylistic: true,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
fixable: 'whitespace',
|
|
51
|
+
|
|
52
|
+
messages: {
|
|
53
|
+
style: 'Inline code should not have extra spaces or tabs next to backticks.',
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
language: 'markdown',
|
|
57
|
+
|
|
58
|
+
dialects: ['commonmark', 'gfm'],
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
create(context) {
|
|
62
|
+
const { sourceCode } = context;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {number} startOffset
|
|
66
|
+
* @param {number} endOffset
|
|
67
|
+
*/
|
|
68
|
+
function reportStyle(startOffset, endOffset) {
|
|
69
|
+
context.report({
|
|
70
|
+
loc: {
|
|
71
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
72
|
+
end: sourceCode.getLocFromIndex(endOffset),
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
messageId: 'style',
|
|
76
|
+
|
|
77
|
+
fix(fixer) {
|
|
78
|
+
return fixer.removeRange([startOffset, endOffset]);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
inlineCode(node) {
|
|
85
|
+
// ------------------------------------------------------------------------
|
|
86
|
+
// 1. Extract the text and offsets of the inline code node.
|
|
87
|
+
// ------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
const text = sourceCode.getText(node);
|
|
90
|
+
const [nodeStartOffset, nodeEndOffset] = sourceCode.getRange(node);
|
|
91
|
+
|
|
92
|
+
// ------------------------------------------------------------------------
|
|
93
|
+
// 2. Extract the leading spaces and backticks of the inline code node.
|
|
94
|
+
// ------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
const { leadingBackticks = '', leadingSpaces: leadingSpacesText = '' } =
|
|
97
|
+
text.match(leadingInlineCodeRegex)?.groups ?? {};
|
|
98
|
+
const { leadingSpaces: leadingSpacesValue = '', firstChar = '' } =
|
|
99
|
+
node.value.match(leadingInlineCodeRegex)?.groups ?? {};
|
|
100
|
+
|
|
101
|
+
const startBacktick = firstChar === '`';
|
|
102
|
+
const startPaddingLength = /** @type {0 | 1} */ (
|
|
103
|
+
// `startPaddingLength` is always `0` or `1` because the parser consumes at most one padding space on each side.
|
|
104
|
+
leadingSpacesText.length - leadingSpacesValue.length
|
|
105
|
+
);
|
|
106
|
+
const startBacktickSpaceAdjustment = startBacktick && !startPaddingLength ? 1 : 0;
|
|
107
|
+
const startSpaces = leadingSpacesValue.length > startBacktickSpaceAdjustment;
|
|
108
|
+
|
|
109
|
+
// ------------------------------------------------------------------------
|
|
110
|
+
// 3. Extract the trailing spaces and backticks of the inline code node.
|
|
111
|
+
// ------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const { trailingSpaces: trailingSpacesText = '', trailingBackticks = '' } =
|
|
114
|
+
text.match(trailingInlineCodeRegex)?.groups ?? {};
|
|
115
|
+
const { lastChar = '', trailingSpaces: trailingSpacesValue = '' } =
|
|
116
|
+
node.value.match(trailingInlineCodeRegex)?.groups ?? {};
|
|
117
|
+
|
|
118
|
+
const endBacktick = lastChar === '`';
|
|
119
|
+
const endPaddingLength = /** @type {0 | 1} */ (
|
|
120
|
+
// `endPaddingLength` is always `0` or `1` because the parser consumes at most one padding space on each side.
|
|
121
|
+
trailingSpacesText.length - trailingSpacesValue.length
|
|
122
|
+
);
|
|
123
|
+
const endBacktickSpaceAdjustment = endBacktick && !endPaddingLength ? 1 : 0;
|
|
124
|
+
const endSpaces = trailingSpacesValue.length > endBacktickSpaceAdjustment;
|
|
125
|
+
|
|
126
|
+
// ------------------------------------------------------------------------
|
|
127
|
+
// 4. Report if there are extra spaces or tabs next to backticks.
|
|
128
|
+
// ------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
const removePadding = startSpaces && endSpaces && !startBacktick && !endBacktick;
|
|
131
|
+
|
|
132
|
+
if (startSpaces) {
|
|
133
|
+
const baseOffset = nodeStartOffset + leadingBackticks.length;
|
|
134
|
+
|
|
135
|
+
reportStyle(
|
|
136
|
+
baseOffset + (removePadding ? 0 : startPaddingLength),
|
|
137
|
+
baseOffset + leadingSpacesText.length - startBacktickSpaceAdjustment,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (endSpaces) {
|
|
142
|
+
const baseOffset = nodeEndOffset - trailingBackticks.length;
|
|
143
|
+
|
|
144
|
+
reportStyle(
|
|
145
|
+
baseOffset - trailingSpacesText.length + endBacktickSpaceAdjustment,
|
|
146
|
+
baseOffset - (removePadding ? 0 : endPaddingLength),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
};
|
|
@@ -94,7 +94,7 @@ export default {
|
|
|
94
94
|
const unorderedListStyle = [
|
|
95
95
|
style === 'consistent' || style === 'sublist' ? null : style,
|
|
96
96
|
];
|
|
97
|
-
let listDepth = -1;
|
|
97
|
+
let listDepth = -1; // NOTE: Depth `0` is the first list level, which is the top level.
|
|
98
98
|
|
|
99
99
|
return {
|
|
100
100
|
list() {
|
package/src/rules/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import codeLangShorthand from './code-lang-shorthand.js';
|
|
|
9
9
|
import consistentCodeStyle from './consistent-code-style.js';
|
|
10
10
|
import consistentDeleteStyle from './consistent-delete-style.js';
|
|
11
11
|
import consistentEmphasisStyle from './consistent-emphasis-style.js';
|
|
12
|
+
import consistentInlineCodeStyle from './consistent-inline-code-style.js';
|
|
12
13
|
import consistentStrongStyle from './consistent-strong-style.js';
|
|
13
14
|
import consistentThematicBreakStyle from './consistent-thematic-break-style.js';
|
|
14
15
|
import consistentUnorderedListStyle from './consistent-unordered-list-style.js';
|
|
@@ -35,6 +36,7 @@ export default {
|
|
|
35
36
|
'consistent-code-style': consistentCodeStyle,
|
|
36
37
|
'consistent-delete-style': consistentDeleteStyle,
|
|
37
38
|
'consistent-emphasis-style': consistentEmphasisStyle,
|
|
39
|
+
'consistent-inline-code-style': consistentInlineCodeStyle,
|
|
38
40
|
'consistent-strong-style': consistentStrongStyle,
|
|
39
41
|
'consistent-thematic-break-style': consistentThematicBreakStyle,
|
|
40
42
|
'consistent-unordered-list-style': consistentUnorderedListStyle,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Get the file name of the module.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// --------------------------------------------------------------------------------
|
|
6
|
-
// Import
|
|
7
|
-
// --------------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { parse } from 'node:path';
|
|
11
|
-
|
|
12
|
-
// --------------------------------------------------------------------------------
|
|
13
|
-
// Export
|
|
14
|
-
// --------------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Get the file name of the module.
|
|
18
|
-
* @param {string} importMetaUrl The absolute `file:` URL of the module.
|
|
19
|
-
* @returns {string} The file name of the module.
|
|
20
|
-
*/
|
|
21
|
-
export default function getFileName(importMetaUrl) {
|
|
22
|
-
return parse(fileURLToPath(importMetaUrl)).name.replace(/\.test$/, '');
|
|
23
|
-
}
|