eslint-cannoli-plugins 1.0.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/LICENSE +15 -0
- package/README.md +75 -0
- package/dist/plugins/markdown/enforce-link-convention.d.ts +7 -0
- package/dist/plugins/markdown/enforce-link-convention.d.ts.map +1 -0
- package/dist/plugins/markdown/enforce-link-convention.js +120 -0
- package/dist/plugins/markdown/enforce-link-convention.js.map +1 -0
- package/dist/plugins/markdown/index.ts +5 -0
- package/dist/plugins/markdown/inline-math-alone-on-line.d.ts +10 -0
- package/dist/plugins/markdown/inline-math-alone-on-line.d.ts.map +1 -0
- package/dist/plugins/markdown/inline-math-alone-on-line.js +70 -0
- package/dist/plugins/markdown/inline-math-alone-on-line.js.map +1 -0
- package/dist/plugins/markdown/no-h1-headers.d.ts +3 -0
- package/dist/plugins/markdown/no-h1-headers.d.ts.map +1 -0
- package/dist/plugins/markdown/no-h1-headers.js +41 -0
- package/dist/plugins/markdown/no-h1-headers.js.map +1 -0
- package/dist/plugins/markdown/require-blank-line-after-html.d.ts +3 -0
- package/dist/plugins/markdown/require-blank-line-after-html.d.ts.map +1 -0
- package/dist/plugins/markdown/require-blank-line-after-html.js +164 -0
- package/dist/plugins/markdown/require-blank-line-after-html.js.map +1 -0
- package/dist/plugins/markdown/require-display-math-formatting.d.ts +12 -0
- package/dist/plugins/markdown/require-display-math-formatting.d.ts.map +1 -0
- package/dist/plugins/markdown/require-display-math-formatting.js +108 -0
- package/dist/plugins/markdown/require-display-math-formatting.js.map +1 -0
- package/dist/plugins/markdown/require-frontmatter.d.ts +3 -0
- package/dist/plugins/markdown/require-frontmatter.d.ts.map +1 -0
- package/dist/plugins/markdown/require-frontmatter.js +39 -0
- package/dist/plugins/markdown/require-frontmatter.js.map +1 -0
- package/dist/plugins/markdown/utils.d.ts +41 -0
- package/dist/plugins/markdown/utils.d.ts.map +1 -0
- package/dist/plugins/markdown/utils.js +181 -0
- package/dist/plugins/markdown/utils.js.map +1 -0
- package/dist/plugins/markdown/validate-latex-delimiters.d.ts +8 -0
- package/dist/plugins/markdown/validate-latex-delimiters.d.ts.map +1 -0
- package/dist/plugins/markdown/validate-latex-delimiters.js +83 -0
- package/dist/plugins/markdown/validate-latex-delimiters.js.map +1 -0
- package/index.d.ts +7 -0
- package/index.js +7 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ESLint Markdown Cannoli Plugins
|
|
2
|
+
|
|
3
|
+
A collection of ESLint plugins for linting Markdown files with custom rules.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev eslint-md-cannoli-plugins @eslint/markdown eslint
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Add the plugins to your ESLint configuration (`eslint.config.js` or `eslint.config.ts`):
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import markdown from "@eslint/markdown";
|
|
17
|
+
import {
|
|
18
|
+
enforceLinkConvention,
|
|
19
|
+
inlineMathAloneOnLine,
|
|
20
|
+
noH1Headers,
|
|
21
|
+
requireBlankLineAfterHtml,
|
|
22
|
+
requireDisplayMathFormatting,
|
|
23
|
+
requireFrontmatter,
|
|
24
|
+
validateLatexDelimiters,
|
|
25
|
+
} from "eslint-md-cannoli-plugins";
|
|
26
|
+
|
|
27
|
+
export default [
|
|
28
|
+
{
|
|
29
|
+
files: ["**/*.md"],
|
|
30
|
+
plugins: {
|
|
31
|
+
markdown,
|
|
32
|
+
cannoli: {
|
|
33
|
+
rules: {
|
|
34
|
+
"require-frontmatter": requireFrontmatter,
|
|
35
|
+
"no-h1-headers": noH1Headers,
|
|
36
|
+
"require-blank-line-after-html": requireBlankLineAfterHtml,
|
|
37
|
+
"require-display-math-formatting": requireDisplayMathFormatting,
|
|
38
|
+
"inline-math-alone-on-line": inlineMathAloneOnLine,
|
|
39
|
+
"validate-latex-delimiters": validateLatexDelimiters,
|
|
40
|
+
"enforce-link-convention": enforceLinkConvention,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
language: "markdown/gfm",
|
|
45
|
+
languageOptions: {
|
|
46
|
+
parser: "@eslint/markdown",
|
|
47
|
+
frontmatter: "yaml",
|
|
48
|
+
},
|
|
49
|
+
extends: ["markdown/recommended"],
|
|
50
|
+
rules: {
|
|
51
|
+
"cannoli/require-frontmatter": "error",
|
|
52
|
+
"cannoli/no-h1-headers": "error",
|
|
53
|
+
"cannoli/require-blank-line-after-html": "error",
|
|
54
|
+
"cannoli/require-display-math-formatting": "error",
|
|
55
|
+
"cannoli/inline-math-alone-on-line": "warn",
|
|
56
|
+
"cannoli/validate-latex-delimiters": "error",
|
|
57
|
+
"cannoli/enforce-link-convention": "warn",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Available Rules
|
|
64
|
+
|
|
65
|
+
- **require-frontmatter** - Ensure markdown files have frontmatter
|
|
66
|
+
- **no-h1-headers** - Disallow H1 headers (use frontmatter title instead)
|
|
67
|
+
- **require-blank-line-after-html** - Require blank lines after HTML blocks
|
|
68
|
+
- **require-display-math-formatting** - Enforce proper display math formatting
|
|
69
|
+
- **inline-math-alone-on-line** - Ensure inline math equations are on their own line
|
|
70
|
+
- **validate-latex-delimiters** - Validate LaTeX delimiter usage
|
|
71
|
+
- **enforce-link-convention** - Enforce link naming conventions
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
ISC
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
/**
|
|
3
|
+
* Enforce link convention: all lowercase and no offending characters.
|
|
4
|
+
* Suggests slugified version as the proper convention.
|
|
5
|
+
*/
|
|
6
|
+
export declare const enforceLinkConvention: Rule.RuleModule;
|
|
7
|
+
//# sourceMappingURL=enforce-link-convention.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforce-link-convention.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/enforce-link-convention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAInC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,IAAI,CAAC,UAqIxC,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { FencedCodeBlockTracker, getFrontmatterEndLine, isValidLinkFormat, slugify } from "./utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Enforce link convention: all lowercase and no offending characters.
|
|
4
|
+
* Suggests slugified version as the proper convention.
|
|
5
|
+
*/
|
|
6
|
+
export const enforceLinkConvention = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "suggestion",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Enforce that links are all lowercase and contain only valid characters (no spaces, special chars, etc.)",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
create(context) {
|
|
14
|
+
let alreadyProcessed = false;
|
|
15
|
+
return {
|
|
16
|
+
"*": (node) => {
|
|
17
|
+
if (alreadyProcessed || node.type !== "root")
|
|
18
|
+
return;
|
|
19
|
+
alreadyProcessed = true;
|
|
20
|
+
const sourceCode = context.sourceCode;
|
|
21
|
+
if (!sourceCode)
|
|
22
|
+
return;
|
|
23
|
+
const text = sourceCode.getText();
|
|
24
|
+
const lines = text.split("\n");
|
|
25
|
+
const frontmatterEndLine = getFrontmatterEndLine(text);
|
|
26
|
+
const codeBlockTracker = new FencedCodeBlockTracker(text);
|
|
27
|
+
for (let i = frontmatterEndLine; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i];
|
|
29
|
+
if (codeBlockTracker.isLineInFencedCodeBlock(i)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Match markdown links: [text](link) and [text]: link
|
|
33
|
+
const inlineLinksRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
34
|
+
const referenceLinkRegex = /^\s*\[([^\]]+)\]:\s*(.+?)(?:\s+"[^"]*")?$/;
|
|
35
|
+
// Check inline links [text](link)
|
|
36
|
+
let inlineMatch;
|
|
37
|
+
while ((inlineMatch = inlineLinksRegex.exec(line)) !== null) {
|
|
38
|
+
const link = inlineMatch[2];
|
|
39
|
+
// Calculate column position: find the opening ( and start from the first char after it
|
|
40
|
+
const fullMatch = inlineMatch[0];
|
|
41
|
+
const openParenIndex = fullMatch.indexOf("(");
|
|
42
|
+
const linkColumn = inlineMatch.index + openParenIndex + 1;
|
|
43
|
+
checkLink(link, i, linkColumn, context);
|
|
44
|
+
}
|
|
45
|
+
// Check reference links [text]: link
|
|
46
|
+
const refMatch = line.match(referenceLinkRegex);
|
|
47
|
+
if (refMatch) {
|
|
48
|
+
const link = refMatch[2].trim();
|
|
49
|
+
// Calculate exact column position where the link starts
|
|
50
|
+
const linkPosition = line.indexOf(link);
|
|
51
|
+
checkLink(link, i, linkPosition, context);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function slugifyPath(path) {
|
|
55
|
+
// Split by slash and slugify each component separately to preserve directory structure
|
|
56
|
+
const parts = path.split("/");
|
|
57
|
+
return parts
|
|
58
|
+
.map((part) => {
|
|
59
|
+
// Don't slugify empty parts or special directory references
|
|
60
|
+
if (part === "" || part === "." || part === "..") {
|
|
61
|
+
return part;
|
|
62
|
+
}
|
|
63
|
+
return slugify(part);
|
|
64
|
+
})
|
|
65
|
+
.join("/");
|
|
66
|
+
}
|
|
67
|
+
function extractExtension(path) {
|
|
68
|
+
// Find the last slash to separate directory from filename
|
|
69
|
+
const lastSlashIndex = path.lastIndexOf("/");
|
|
70
|
+
const filename = path.substring(lastSlashIndex + 1);
|
|
71
|
+
// Find the last dot in the filename
|
|
72
|
+
const lastDotIndex = filename.lastIndexOf(".");
|
|
73
|
+
// Only extract extension if dot is not at the start (e.g., not ".gitignore")
|
|
74
|
+
if (lastDotIndex > 0) {
|
|
75
|
+
const basename = filename.substring(0, lastDotIndex);
|
|
76
|
+
const extension = filename.substring(lastDotIndex);
|
|
77
|
+
const dirPart = lastSlashIndex >= 0 ? path.substring(0, lastSlashIndex + 1) : "";
|
|
78
|
+
return {
|
|
79
|
+
basename: dirPart + basename,
|
|
80
|
+
extension: extension,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { basename: path, extension: "" };
|
|
84
|
+
}
|
|
85
|
+
function checkLink(link, lineIndex, columnIndex, ctx) {
|
|
86
|
+
// Skip external links (any URL protocol) and anchors
|
|
87
|
+
const externalProtocols = [
|
|
88
|
+
"http://",
|
|
89
|
+
"https://",
|
|
90
|
+
"mailto:",
|
|
91
|
+
"ftp://",
|
|
92
|
+
"ftps://",
|
|
93
|
+
"sftp://",
|
|
94
|
+
"ssh://",
|
|
95
|
+
"tel:",
|
|
96
|
+
"sms:",
|
|
97
|
+
"data:",
|
|
98
|
+
];
|
|
99
|
+
if (externalProtocols.some((proto) => link.startsWith(proto)) || link.startsWith("#")) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Check if link is valid format
|
|
103
|
+
if (!isValidLinkFormat(link)) {
|
|
104
|
+
// Separate basename and extension to preserve the extension
|
|
105
|
+
const { basename, extension } = extractExtension(link);
|
|
106
|
+
const suggestion = slugifyPath(basename) + extension;
|
|
107
|
+
ctx.report({
|
|
108
|
+
loc: {
|
|
109
|
+
start: { line: lineIndex + 1, column: columnIndex },
|
|
110
|
+
end: { line: lineIndex + 1, column: columnIndex + link.length },
|
|
111
|
+
},
|
|
112
|
+
message: `Link contains invalid characters or uppercase letters. Links should be lowercase and contain only alphanumeric characters, hyphens, underscores, slashes, and dots. Suggested format: "${suggestion}"`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=enforce-link-convention.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforce-link-convention.js","sourceRoot":"","sources":["../../../src/plugins/markdown/enforce-link-convention.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
/**
|
|
3
|
+
* Detect inline math expressions ($...$) that exist alone on their own line.
|
|
4
|
+
* This usually indicates the expression was meant to be display/block math ($$...$$).
|
|
5
|
+
*
|
|
6
|
+
* Bad: $x = 2$
|
|
7
|
+
* Good: $$x = 2$$ OR inline text $x = 2$ more text
|
|
8
|
+
*/
|
|
9
|
+
export declare const inlineMathAloneOnLine: Rule.RuleModule;
|
|
10
|
+
//# sourceMappingURL=inline-math-alone-on-line.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-math-alone-on-line.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/inline-math-alone-on-line.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAInC;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,IAAI,CAAC,UA0ExC,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { FencedCodeBlockTracker, getFrontmatterEndLine } from "./utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Detect inline math expressions ($...$) that exist alone on their own line.
|
|
4
|
+
* This usually indicates the expression was meant to be display/block math ($$...$$).
|
|
5
|
+
*
|
|
6
|
+
* Bad: $x = 2$
|
|
7
|
+
* Good: $$x = 2$$ OR inline text $x = 2$ more text
|
|
8
|
+
*/
|
|
9
|
+
export const inlineMathAloneOnLine = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "suggestion",
|
|
12
|
+
fixable: "code",
|
|
13
|
+
docs: {
|
|
14
|
+
description: "Detect inline math expressions that exist alone on their own line (likely meant to be display math)",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
let alreadyProcessed = false;
|
|
19
|
+
return {
|
|
20
|
+
"*": (node) => {
|
|
21
|
+
if (alreadyProcessed || node.type !== "root")
|
|
22
|
+
return;
|
|
23
|
+
alreadyProcessed = true;
|
|
24
|
+
const sourceCode = context.sourceCode;
|
|
25
|
+
if (!sourceCode)
|
|
26
|
+
return;
|
|
27
|
+
const text = sourceCode.getText();
|
|
28
|
+
const lines = text.split("\n");
|
|
29
|
+
const frontmatterEndLine = getFrontmatterEndLine(text);
|
|
30
|
+
const codeBlockTracker = new FencedCodeBlockTracker(text);
|
|
31
|
+
for (let i = frontmatterEndLine; i < lines.length; i++) {
|
|
32
|
+
const line = lines[i];
|
|
33
|
+
// Skip lines inside fenced code blocks
|
|
34
|
+
if (codeBlockTracker.isLineInFencedCodeBlock(i)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const trimmed = line.trim();
|
|
38
|
+
// Skip empty lines
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// Check for display math ($$) - not what we're looking for
|
|
43
|
+
if (trimmed.includes("$$")) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Check for inline math ($...$)
|
|
47
|
+
const singleDollarRegex = /^\$[^$]+\$\s*$/;
|
|
48
|
+
const match = trimmed.match(singleDollarRegex);
|
|
49
|
+
if (match) {
|
|
50
|
+
// Found inline math alone on a line
|
|
51
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? "";
|
|
52
|
+
const expression = trimmed.slice(1, -1); // Remove the $...$
|
|
53
|
+
context.report({
|
|
54
|
+
loc: { line: i + 1, column: 0 },
|
|
55
|
+
message: "Inline math expression found alone on its own line. Did you mean to use display math ($$...$$) instead?",
|
|
56
|
+
fix(fixer) {
|
|
57
|
+
// Convert from $...$ to $$...$$
|
|
58
|
+
const replacement = `${indent}$$${expression}$$`;
|
|
59
|
+
const lineStart = sourceCode.getIndexFromLoc({ line: i + 1, column: 1 });
|
|
60
|
+
const lineEnd = sourceCode.getIndexFromLoc({ line: i + 1, column: line.length + 1 });
|
|
61
|
+
return fixer.replaceTextRange([lineStart, lineEnd], replacement);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=inline-math-alone-on-line.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-math-alone-on-line.js","sourceRoot":"","sources":["../../../src/plugins/markdown/inline-math-alone-on-line.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-h1-headers.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/no-h1-headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAInC,eAAO,MAAM,WAAW,EAAE,IAAI,CAAC,UA6C9B,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FencedCodeBlockTracker, getFrontmatterEndLine } from "./utils.js";
|
|
2
|
+
export const noH1Headers = {
|
|
3
|
+
meta: {
|
|
4
|
+
type: "problem",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "Disallow h1 headers in markdown files since the frontmatter title already serves that purpose",
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
create(context) {
|
|
10
|
+
let alreadyProcessed = false;
|
|
11
|
+
return {
|
|
12
|
+
"*": (node) => {
|
|
13
|
+
if (alreadyProcessed || node.type !== "root")
|
|
14
|
+
return;
|
|
15
|
+
alreadyProcessed = true;
|
|
16
|
+
const sourceCode = context.sourceCode;
|
|
17
|
+
if (!sourceCode)
|
|
18
|
+
return;
|
|
19
|
+
const text = sourceCode.getText();
|
|
20
|
+
const lines = text.split("\n");
|
|
21
|
+
const frontmatterEndLine = getFrontmatterEndLine(text);
|
|
22
|
+
const codeBlockTracker = new FencedCodeBlockTracker(text);
|
|
23
|
+
// check for h1 headers
|
|
24
|
+
for (let i = frontmatterEndLine; i < lines.length; i++) {
|
|
25
|
+
const line = lines[i];
|
|
26
|
+
// skip lines inside fenced code blocks
|
|
27
|
+
if (codeBlockTracker.isLineInFencedCodeBlock(i)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (/^#\s+/.test(line)) {
|
|
31
|
+
context.report({
|
|
32
|
+
loc: { line: i + 1, column: 0 },
|
|
33
|
+
message: "H1 headings are not allowed in Astro markdown files; use the frontmatter title field instead",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=no-h1-headers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-h1-headers.js","sourceRoot":"","sources":["../../../src/plugins/markdown/no-h1-headers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,CAAC,MAAM,WAAW,GAAoB;IAC1C,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EACT,+FAA+F;SAClG;KACO;IACV,MAAM,CAAC,OAAyB;QAC9B,IAAI,gBAAgB,GAAG,KAAK,CAAC;QAE7B,OAAO;YACL,GAAG,EAAE,CAAC,IAAe,EAAE,EAAE;gBACvB,IAAI,gBAAgB,IAAK,IAAoC,CAAC,IAAI,KAAK,MAAM;oBAAE,OAAO;gBAEtF,gBAAgB,GAAG,IAAI,CAAC;gBAExB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;gBACtC,IAAI,CAAC,UAAU;oBAAE,OAAO;gBAExB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBACvD,MAAM,gBAAgB,GAAG,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC;gBAE1D,uBAAuB;gBACvB,KAAK,IAAI,CAAC,GAAG,kBAAkB,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACvD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBAEtB,uCAAuC;oBACvC,IAAI,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;wBAChD,SAAS;oBACX,CAAC;oBAED,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACvB,OAAO,CAAC,MAAM,CAAC;4BACb,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;4BAC/B,OAAO,EACL,8FAA8F;yBACjG,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-blank-line-after-html.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/require-blank-line-after-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAuCnC,eAAO,MAAM,yBAAyB,EAAE,IAAI,CAAC,UAyJ5C,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { FencedCodeBlockTracker, getFrontmatterEndLine } from "./utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check if a line contains a problematic markdown construct that requires a blank line before/after HTML.
|
|
4
|
+
* Block-level constructs like headings are excluded since they don't interfere with HTML rendering.
|
|
5
|
+
*/
|
|
6
|
+
function isProblematicMarkdownConstruct(line) {
|
|
7
|
+
const trimmed = line.trim();
|
|
8
|
+
// Blockquote
|
|
9
|
+
if (/^>/.test(trimmed))
|
|
10
|
+
return true;
|
|
11
|
+
// List (-, *, +)
|
|
12
|
+
if (/^[-*+]\s/.test(trimmed))
|
|
13
|
+
return true;
|
|
14
|
+
// Image
|
|
15
|
+
if (/^!\[/.test(trimmed))
|
|
16
|
+
return true;
|
|
17
|
+
// Link (starts with [)
|
|
18
|
+
if (/^\[/.test(trimmed))
|
|
19
|
+
return true;
|
|
20
|
+
// Inline code (starts with backtick)
|
|
21
|
+
if (/^`/.test(trimmed))
|
|
22
|
+
return true;
|
|
23
|
+
// Bold/Strong (**, __)
|
|
24
|
+
if (/^\*\*/.test(trimmed) || /^__/.test(trimmed))
|
|
25
|
+
return true;
|
|
26
|
+
// Emphasis (*, _) - but not list markers (those are checked above with space)
|
|
27
|
+
if (/^\*[^\s*]/.test(trimmed) || /^_[^\s_]/.test(trimmed))
|
|
28
|
+
return true;
|
|
29
|
+
// Do NOT include headings - they're block-level structural elements
|
|
30
|
+
// Do NOT include normal text - it's not problematic
|
|
31
|
+
// Do NOT flag HTML tags here - they're handled separately
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
export const requireBlankLineAfterHtml = {
|
|
35
|
+
meta: {
|
|
36
|
+
type: "problem",
|
|
37
|
+
docs: {
|
|
38
|
+
description: "Require blank line after closing HTML tags unless followed by another HTML tag",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
create(context) {
|
|
42
|
+
let alreadyProcessed = false;
|
|
43
|
+
return {
|
|
44
|
+
"*": (node) => {
|
|
45
|
+
if (alreadyProcessed || node.type !== "root")
|
|
46
|
+
return;
|
|
47
|
+
alreadyProcessed = true;
|
|
48
|
+
const sourceCode = context.sourceCode;
|
|
49
|
+
if (!sourceCode)
|
|
50
|
+
return;
|
|
51
|
+
const text = sourceCode.getText();
|
|
52
|
+
const lines = text.split("\n");
|
|
53
|
+
const frontmatterEndLine = getFrontmatterEndLine(text);
|
|
54
|
+
const codeBlockTracker = new FencedCodeBlockTracker(text);
|
|
55
|
+
for (let i = frontmatterEndLine; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
// Skip lines inside fenced code blocks
|
|
58
|
+
if (codeBlockTracker.isLineInFencedCodeBlock(i)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Check if current line is a problematic markdown construct
|
|
62
|
+
if (isProblematicMarkdownConstruct(line)) {
|
|
63
|
+
// Look back to find previous non-empty line
|
|
64
|
+
let prevNonEmptyLine = null;
|
|
65
|
+
let prevNonEmptyLineNum = -1;
|
|
66
|
+
for (let j = i - 1; j >= frontmatterEndLine; j--) {
|
|
67
|
+
if (lines[j].trim()) {
|
|
68
|
+
prevNonEmptyLine = lines[j];
|
|
69
|
+
prevNonEmptyLineNum = j;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// If previous line is directly before (no blank line)
|
|
74
|
+
if (prevNonEmptyLine && prevNonEmptyLineNum === i - 1) {
|
|
75
|
+
// Check if previous line is an HTML closing tag
|
|
76
|
+
const prevIsHtmlClosingTag = /<\/\w+>\s*$/.test(prevNonEmptyLine.trim());
|
|
77
|
+
if (prevIsHtmlClosingTag) {
|
|
78
|
+
context.report({
|
|
79
|
+
loc: { line: i + 1, column: 0 },
|
|
80
|
+
message: `Blank line required before markdown construct when preceded by closing HTML tag`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Check for HTML tags (opening or closing)
|
|
86
|
+
const htmlTagMatch = line.match(/^(.*)(<\/?(\w+)>)(.*)$/);
|
|
87
|
+
if (htmlTagMatch) {
|
|
88
|
+
const beforeTag = htmlTagMatch[1];
|
|
89
|
+
const htmlTag = htmlTagMatch[2];
|
|
90
|
+
const afterTag = htmlTagMatch[4];
|
|
91
|
+
// If there's content before the tag on the same line, it's inline - check after
|
|
92
|
+
if (beforeTag.trim()) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// If there's content after the tag on the same line, no check needed
|
|
96
|
+
if (afterTag.trim()) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// Look backward to find the previous non-empty line
|
|
100
|
+
let prevNonEmptyLine = null;
|
|
101
|
+
let prevNonEmptyLineNum = -1;
|
|
102
|
+
for (let j = i - 1; j >= frontmatterEndLine; j--) {
|
|
103
|
+
if (lines[j].trim()) {
|
|
104
|
+
prevNonEmptyLine = lines[j];
|
|
105
|
+
prevNonEmptyLineNum = j;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Check if previous line is a problematic markdown construct (not headings)
|
|
110
|
+
if (prevNonEmptyLine && prevNonEmptyLineNum === i - 1) {
|
|
111
|
+
// Previous line is directly before (no blank line)
|
|
112
|
+
const prevIsProblematicConstruct = isProblematicMarkdownConstruct(prevNonEmptyLine);
|
|
113
|
+
if (prevIsProblematicConstruct) {
|
|
114
|
+
context.report({
|
|
115
|
+
loc: { line: i + 1, column: 0 },
|
|
116
|
+
message: `Blank line required before HTML tag "${htmlTag}" when preceded by markdown construct`,
|
|
117
|
+
});
|
|
118
|
+
// Continue to next iteration to avoid reporting multiple errors
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Look ahead to find the next non-empty line (for closing tags)
|
|
123
|
+
let nextNonEmptyLine = null;
|
|
124
|
+
let nextNonEmptyLineNum = -1;
|
|
125
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
126
|
+
if (lines[j].trim()) {
|
|
127
|
+
nextNonEmptyLine = lines[j];
|
|
128
|
+
nextNonEmptyLineNum = j;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// If no next line found, no error
|
|
133
|
+
if (!nextNonEmptyLine) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// Only check blank line after for closing tags
|
|
137
|
+
if (/<\//.test(htmlTag)) {
|
|
138
|
+
// Check if the next line is another HTML tag
|
|
139
|
+
const isNextLineHtmlTag = /^<[a-zA-Z/]/.test(nextNonEmptyLine.trim());
|
|
140
|
+
// If next line is an HTML tag, blank line is optional (exception)
|
|
141
|
+
if (isNextLineHtmlTag) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// If there are blank lines between closing tag and next content, no error
|
|
145
|
+
if (nextNonEmptyLineNum > i + 1) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Check if the next line is a problematic markdown construct that requires blank line
|
|
149
|
+
const nextIsProblematicConstruct = isProblematicMarkdownConstruct(nextNonEmptyLine);
|
|
150
|
+
// If next line is a problematic markdown construct, require blank line (report error)
|
|
151
|
+
if (nextIsProblematicConstruct) {
|
|
152
|
+
context.report({
|
|
153
|
+
loc: { line: i + 1, column: 0 },
|
|
154
|
+
message: `Blank line required after closing HTML tag "${htmlTag}" when followed by markdown construct`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=require-blank-line-after-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-blank-line-after-html.js","sourceRoot":"","sources":["../../../src/plugins/markdown/require-blank-line-after-html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
/**
|
|
3
|
+
* Check if a line contains display math ($$) that's not properly formatted.
|
|
4
|
+
* Display math should have $$ on its own line, not inline with the expression.
|
|
5
|
+
*
|
|
6
|
+
* Bad: $$2+2$$
|
|
7
|
+
* Good: $$
|
|
8
|
+
* 2+2
|
|
9
|
+
* $$
|
|
10
|
+
*/
|
|
11
|
+
export declare const requireDisplayMathFormatting: Rule.RuleModule;
|
|
12
|
+
//# sourceMappingURL=require-display-math-formatting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-display-math-formatting.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/require-display-math-formatting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAInC;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,EAAE,IAAI,CAAC,UA8G/C,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { FencedCodeBlockTracker, getFrontmatterEndLine } from "./utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check if a line contains display math ($$) that's not properly formatted.
|
|
4
|
+
* Display math should have $$ on its own line, not inline with the expression.
|
|
5
|
+
*
|
|
6
|
+
* Bad: $$2+2$$
|
|
7
|
+
* Good: $$
|
|
8
|
+
* 2+2
|
|
9
|
+
* $$
|
|
10
|
+
*/
|
|
11
|
+
export const requireDisplayMathFormatting = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
fixable: "code",
|
|
15
|
+
docs: {
|
|
16
|
+
description: "Require display math ($$) to be on separate lines from the expression",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
let alreadyProcessed = false;
|
|
21
|
+
return {
|
|
22
|
+
"*": (node) => {
|
|
23
|
+
if (alreadyProcessed || node.type !== "root")
|
|
24
|
+
return;
|
|
25
|
+
alreadyProcessed = true;
|
|
26
|
+
const sourceCode = context.sourceCode;
|
|
27
|
+
if (!sourceCode)
|
|
28
|
+
return;
|
|
29
|
+
const text = sourceCode.getText();
|
|
30
|
+
const lines = text.split("\n");
|
|
31
|
+
const frontmatterEndLine = getFrontmatterEndLine(text);
|
|
32
|
+
const codeBlockTracker = new FencedCodeBlockTracker(text);
|
|
33
|
+
for (let i = frontmatterEndLine; i < lines.length; i++) {
|
|
34
|
+
const line = lines[i];
|
|
35
|
+
// Skip lines inside fenced code blocks
|
|
36
|
+
if (codeBlockTracker.isLineInFencedCodeBlock(i)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const trimmed = line.trim();
|
|
40
|
+
// Remove inline code (backtick-enclosed content) before checking for $$
|
|
41
|
+
const withoutInlineCode = trimmed.replace(/`[^`]*`/g, "");
|
|
42
|
+
// Check for display math markers
|
|
43
|
+
if (!withoutInlineCode.includes("$$")) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Count $$ occurrences in the line (excluding those in inline code)
|
|
47
|
+
const dollarCount = (withoutInlineCode.match(/\$\$/g) || []).length;
|
|
48
|
+
// If there's only one $$, it might be an opening or closing on its own line (good)
|
|
49
|
+
if (dollarCount === 1) {
|
|
50
|
+
// Check if the line is ONLY $$ (possibly with whitespace)
|
|
51
|
+
const isOnlyDollarSigns = /^\$\$\s*$/.test(withoutInlineCode);
|
|
52
|
+
if (isOnlyDollarSigns) {
|
|
53
|
+
// Good: $$ is on its own line
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// If there's content on the same line as $$, that's bad
|
|
57
|
+
// Example: "$$2+2" or "expression$$"
|
|
58
|
+
if (withoutInlineCode !== "$$") {
|
|
59
|
+
context.report({
|
|
60
|
+
loc: { line: i + 1, column: 0 },
|
|
61
|
+
message: "Display math ($$) should be on its own line, separate from the expression",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (dollarCount === 2) {
|
|
66
|
+
// Two $$ on the same line
|
|
67
|
+
// Check if they form a complete math block on one line: $$expression$$
|
|
68
|
+
const doubleRegex = /^(\s*)\$\$(.*?)\$\$\s*$/;
|
|
69
|
+
const match = withoutInlineCode.match(doubleRegex);
|
|
70
|
+
if (match) {
|
|
71
|
+
// The entire line is $$something$$, which is bad
|
|
72
|
+
const indent = match[1];
|
|
73
|
+
const expression = match[2];
|
|
74
|
+
context.report({
|
|
75
|
+
loc: { line: i + 1, column: 0 },
|
|
76
|
+
message: 'Display math delimiters ($$) must be on separate lines. Use:\n$$\nexpression\n$$',
|
|
77
|
+
fix(fixer) {
|
|
78
|
+
// Build the replacement text - just the three lines without trailing newline
|
|
79
|
+
// The line itself will have its trailing newline preserved by ESLint
|
|
80
|
+
const replacement = `${indent}$$\n${indent}${expression}\n${indent}$$`;
|
|
81
|
+
// Get the start of the line and the end (before the newline)
|
|
82
|
+
const lineStart = sourceCode.getIndexFromLoc({ line: i + 1, column: 1 });
|
|
83
|
+
const lineEnd = sourceCode.getIndexFromLoc({ line: i + 1, column: line.length + 1 });
|
|
84
|
+
return fixer.replaceTextRange([lineStart, lineEnd], replacement);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Two $$ but not in the expected format - could be overlapping or weird
|
|
90
|
+
context.report({
|
|
91
|
+
loc: { line: i + 1, column: 0 },
|
|
92
|
+
message: "Malformed display math notation. Display math ($$) should be on separate lines",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (dollarCount > 2) {
|
|
97
|
+
// Multiple $$ pairs on the same line - likely multiple math expressions or malformed
|
|
98
|
+
context.report({
|
|
99
|
+
loc: { line: i + 1, column: 0 },
|
|
100
|
+
message: "Multiple display math expressions ($$) found on same line. Each should be on separate lines",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=require-display-math-formatting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-display-math-formatting.js","sourceRoot":"","sources":["../../../src/plugins/markdown/require-display-math-formatting.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAoB;IAC3D,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,MAAM;QACf,IAAI,EAAE;YACJ,WAAW,EACT,uEAAuE;SAC1E;KACO;IACV,MAAM,CAAC,OAAyB;QAC9B,IAAI,gBAAgB,GAAG,KAAK,CAAC;QAE7B,OAAO;YACL,GAAG,EAAE,CAAC,IAAe,EAAE,EAAE;gBACvB,IAAI,gBAAgB,IAAK,IAAoC,CAAC,IAAI,KAAK,MAAM;oBAAE,OAAO;gBAEtF,gBAAgB,GAAG,IAAI,CAAC;gBAExB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;gBACtC,IAAI,CAAC,UAAU;oBAAE,OAAO;gBAExB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBACvD,MAAM,gBAAgB,GAAG,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC;gBAE1D,KAAK,IAAI,CAAC,GAAG,kBAAkB,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACvD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBAEtB,uCAAuC;oBACvC,IAAI,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;wBAChD,SAAS;oBACX,CAAC;oBAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;oBAE5B,wEAAwE;oBACxE,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;oBAE1D,iCAAiC;oBACjC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;wBACtC,SAAS;oBACX,CAAC;oBAED,oEAAoE;oBACpE,MAAM,WAAW,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;oBAEpE,mFAAmF;oBACnF,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;wBACtB,0DAA0D;wBAC1D,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;wBAC9D,IAAI,iBAAiB,EAAE,CAAC;4BACtB,8BAA8B;4BAC9B,SAAS;wBACX,CAAC;wBAED,wDAAwD;wBACxD,qCAAqC;wBACrC,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;4BAC/B,OAAO,CAAC,MAAM,CAAC;gCACb,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;gCAC/B,OAAO,EACL,2EAA2E;6BAC9E,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;yBAAM,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;wBAC7B,0BAA0B;wBAC1B,uEAAuE;wBACvE,MAAM,WAAW,GAAG,yBAAyB,CAAC;wBAC9C,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;wBACnD,IAAI,KAAK,EAAE,CAAC;4BACV,iDAAiD;4BACjD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;4BACxB,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;4BAC5B,OAAO,CAAC,MAAM,CAAC;gCACb,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;gCAC/B,OAAO,EACL,kFAAkF;gCACpF,GAAG,CAAC,KAAK;oCACP,6EAA6E;oCAC7E,qEAAqE;oCACrE,MAAM,WAAW,GAAG,GAAG,MAAM,OAAO,MAAM,GAAG,UAAU,KAAK,MAAM,IAAI,CAAC;oCAEvE,6DAA6D;oCAC7D,MAAM,SAAS,GAAG,UAAU,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;oCACzE,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;oCAErF,OAAO,KAAK,CAAC,gBAAgB,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC;gCACnE,CAAC;6BACF,CAAC,CAAC;wBACL,CAAC;6BAAM,CAAC;4BACN,wEAAwE;4BACxE,OAAO,CAAC,MAAM,CAAC;gCACb,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;gCAC/B,OAAO,EACL,gFAAgF;6BACnF,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;yBAAM,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;wBAC3B,qFAAqF;wBACrF,OAAO,CAAC,MAAM,CAAC;4BACb,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;4BAC/B,OAAO,EACL,6FAA6F;yBAChG,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-frontmatter.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/require-frontmatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAInC,eAAO,MAAM,kBAAkB,EAAE,IAAI,CAAC,UAsCrC,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { extractFrontmatter } from "./utils.js";
|
|
2
|
+
export const requireFrontmatter = {
|
|
3
|
+
meta: {
|
|
4
|
+
type: "problem",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "Require title field in frontmatter",
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
create(context) {
|
|
10
|
+
let alreadyProcessed = false;
|
|
11
|
+
return {
|
|
12
|
+
"*": (node) => {
|
|
13
|
+
if (alreadyProcessed || node.type !== "root")
|
|
14
|
+
return;
|
|
15
|
+
alreadyProcessed = true;
|
|
16
|
+
const sourceCode = context.sourceCode;
|
|
17
|
+
if (!sourceCode)
|
|
18
|
+
return;
|
|
19
|
+
const text = sourceCode.getText();
|
|
20
|
+
const frontmatter = extractFrontmatter(text);
|
|
21
|
+
if (frontmatter) {
|
|
22
|
+
if (!/^\s*title\s*:/m.test(frontmatter)) {
|
|
23
|
+
context.report({
|
|
24
|
+
loc: { line: 1, column: 0 },
|
|
25
|
+
message: "Missing required 'title' field in frontmatter",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
context.report({
|
|
31
|
+
loc: { line: 1, column: 0 },
|
|
32
|
+
message: "Missing frontmatter",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=require-frontmatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-frontmatter.js","sourceRoot":"","sources":["../../../src/plugins/markdown/require-frontmatter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,oCAAoC;SAClD;KACO;IACV,MAAM,CAAC,OAAyB;QAC9B,IAAI,gBAAgB,GAAG,KAAK,CAAC;QAE7B,OAAO;YACL,GAAG,EAAE,CAAC,IAAe,EAAE,EAAE;gBACvB,IAAI,gBAAgB,IAAK,IAAoC,CAAC,IAAI,KAAK,MAAM;oBAAE,OAAO;gBAEtF,gBAAgB,GAAG,IAAI,CAAC;gBAExB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;gBACtC,IAAI,CAAC,UAAU;oBAAE,OAAO;gBAExB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;gBAClC,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;gBAE7C,IAAI,WAAW,EAAE,CAAC;oBAChB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;wBACxC,OAAO,CAAC,MAAM,CAAC;4BACb,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;4BAC3B,OAAO,EAAE,+CAA+C;yBACzD,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC;wBACb,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;wBAC3B,OAAO,EAAE,qBAAqB;qBAC/B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the frontmatter string (without the --- delimiters) if it exists
|
|
3
|
+
*/
|
|
4
|
+
export declare function extractFrontmatter(text: string): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Find the line number where frontmatter ends after the closing --- delimiter
|
|
7
|
+
*/
|
|
8
|
+
export declare function getFrontmatterEndLine(text: string): number;
|
|
9
|
+
export declare class FencedCodeBlockTracker {
|
|
10
|
+
private ranges;
|
|
11
|
+
private currentRangeIndex;
|
|
12
|
+
constructor(text: string);
|
|
13
|
+
/**
|
|
14
|
+
* Check if a given line index falls within any fenced code block range.
|
|
15
|
+
*/
|
|
16
|
+
isLineInFencedCodeBlock(lineIndex: number): boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Remove escaped LaTeX delimiters from text before counting
|
|
20
|
+
* Handles both inline ($) and display ($$) math delimiters
|
|
21
|
+
*/
|
|
22
|
+
export declare function removeEscapedDelimiters(text: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Count occurrences of a delimiter in text (after removing escaped versions)
|
|
25
|
+
* Returns the count and tracks the line where the count becomes odd
|
|
26
|
+
*/
|
|
27
|
+
export interface DelimiterCount {
|
|
28
|
+
count: number;
|
|
29
|
+
firstUnclosedLine: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Slugify a filename by converting to lowercase, replacing spaces/underscores/slashes with hyphens,
|
|
33
|
+
* removing special characters, and normalizing diacritics to ASCII.
|
|
34
|
+
* Ported from Python: scripts/organizer/util/__init__.py
|
|
35
|
+
*/
|
|
36
|
+
export declare function slugify(name: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Check if a string contains only valid local file link characters (lowercase, alphanumeric, hyphens, underscores, slashes, dots)
|
|
39
|
+
*/
|
|
40
|
+
export declare function isValidLinkFormat(link: string): boolean;
|
|
41
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc1D;AAyCD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,iBAAiB,CAAa;gBAE1B,IAAI,EAAE,MAAM;IAIxB;;OAEG;IACH,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;CAyBpD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+B5C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIvD"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the frontmatter string (without the --- delimiters) if it exists
|
|
3
|
+
*/
|
|
4
|
+
export function extractFrontmatter(text) {
|
|
5
|
+
const frontmatterMatch = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
6
|
+
return frontmatterMatch ? frontmatterMatch[1] : null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Find the line number where frontmatter ends after the closing --- delimiter
|
|
10
|
+
*/
|
|
11
|
+
export function getFrontmatterEndLine(text) {
|
|
12
|
+
const lines = text.split("\n");
|
|
13
|
+
if (lines[0] !== "---") {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
for (let i = 1; i < lines.length; i++) {
|
|
17
|
+
if (lines[i] === "---") {
|
|
18
|
+
return i + 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return 0; // no closing --- found
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Find all fenced code block ranges in the text
|
|
25
|
+
* Returns an array of [startLine, endLine] pairs (0-indexed)
|
|
26
|
+
* Correctly handles indented code blocks (e.g., nested under list items)
|
|
27
|
+
* because fence delimiters are detected anywhere on the line after trimming
|
|
28
|
+
*/
|
|
29
|
+
function getFencedCodeBlockRanges(text) {
|
|
30
|
+
const lines = text.split("\n");
|
|
31
|
+
const ranges = [];
|
|
32
|
+
let inCodeBlock = false;
|
|
33
|
+
let blockStartLine = -1;
|
|
34
|
+
for (let i = 0; i < lines.length; i++) {
|
|
35
|
+
const line = lines[i];
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
// Check for fence delimiter regardless of indentation level
|
|
38
|
+
if (/^(```|~~~)/.test(trimmed)) {
|
|
39
|
+
if (inCodeBlock) {
|
|
40
|
+
// End of code block
|
|
41
|
+
ranges.push([blockStartLine, i]);
|
|
42
|
+
inCodeBlock = false;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Start of code block
|
|
46
|
+
inCodeBlock = true;
|
|
47
|
+
blockStartLine = i;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// If there's an unclosed code block at EOF, still track it
|
|
52
|
+
if (inCodeBlock) {
|
|
53
|
+
ranges.push([blockStartLine, lines.length - 1]);
|
|
54
|
+
}
|
|
55
|
+
return ranges;
|
|
56
|
+
}
|
|
57
|
+
export class FencedCodeBlockTracker {
|
|
58
|
+
constructor(text) {
|
|
59
|
+
this.currentRangeIndex = 0;
|
|
60
|
+
this.ranges = getFencedCodeBlockRanges(text);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if a given line index falls within any fenced code block range.
|
|
64
|
+
*/
|
|
65
|
+
isLineInFencedCodeBlock(lineIndex) {
|
|
66
|
+
if (this.ranges.length === 0) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
while (this.currentRangeIndex < this.ranges.length) {
|
|
70
|
+
const [start, end] = this.ranges[this.currentRangeIndex];
|
|
71
|
+
if (lineIndex < start) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (lineIndex >= start && lineIndex <= end) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (lineIndex > end) {
|
|
78
|
+
this.currentRangeIndex++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// lineIndex is past all ranges
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Remove escaped LaTeX delimiters from text before counting
|
|
88
|
+
* Handles both inline ($) and display ($$) math delimiters
|
|
89
|
+
*/
|
|
90
|
+
export function removeEscapedDelimiters(text) {
|
|
91
|
+
// Remove escaped dollar signs (\$) before counting delimiters
|
|
92
|
+
return text.replace(/\\\$/g, "");
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Slugify a filename by converting to lowercase, replacing spaces/underscores/slashes with hyphens,
|
|
96
|
+
* removing special characters, and normalizing diacritics to ASCII.
|
|
97
|
+
* Ported from Python: scripts/organizer/util/__init__.py
|
|
98
|
+
*/
|
|
99
|
+
export function slugify(name) {
|
|
100
|
+
let slug = name;
|
|
101
|
+
// replace C++ with 'cpp'
|
|
102
|
+
slug = slug.replace(/(_+)?c\+\+/gi, "$1cpp");
|
|
103
|
+
slug = slug.replace("---", "___");
|
|
104
|
+
// Replace & with ' and '
|
|
105
|
+
slug = slug.replace(/&/g, " and ");
|
|
106
|
+
// Replace spaces, underscores, and forward slashes with hyphens
|
|
107
|
+
slug = slug.replace(/ /g, "-").replace(/\//g, "-");
|
|
108
|
+
// Normalize Unicode (decompose diacritics)
|
|
109
|
+
slug = slug
|
|
110
|
+
.normalize("NFKD")
|
|
111
|
+
.split("")
|
|
112
|
+
.filter((char) => char.charCodeAt(0) < 128)
|
|
113
|
+
.join("");
|
|
114
|
+
// Replace non-alphanumeric characters (except hyphen, underscore, plus) with hyphens
|
|
115
|
+
slug = slug.replace(/[^-+_a-zA-Z0-9]/g, "-");
|
|
116
|
+
// Collapse multiple consecutive hyphens into a single hyphen
|
|
117
|
+
slug = slug.replace(/-+/g, "-");
|
|
118
|
+
slug = slug.replace(/-*(_+)-*/g, "$1");
|
|
119
|
+
slug = normalizeCamelPascalCase(slug);
|
|
120
|
+
// Strip leading/trailing hyphens and lowercase
|
|
121
|
+
return slug.replace(/^-+|-+$/g, "").toLowerCase();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if a string contains only valid local file link characters (lowercase, alphanumeric, hyphens, underscores, slashes, dots)
|
|
125
|
+
*/
|
|
126
|
+
export function isValidLinkFormat(link) {
|
|
127
|
+
// Allow: lowercase alphanumeric, hyphens, underscores, slashes, dots, hash, question mark
|
|
128
|
+
// For local files, we don't allow & or = (those are query params for external URLs)
|
|
129
|
+
return /^[a-z0-9\-_/.#?]+$/.test(link);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Insert dashes at camelCase transitions for preparation before slugifying.
|
|
133
|
+
* Ported from Python: scripts/organizer/util/__init__.py
|
|
134
|
+
*/
|
|
135
|
+
function normalizeCamelPascalCase(text) {
|
|
136
|
+
if (text.length < 2) {
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
// Handle PascalCase starting with single uppercase letter
|
|
140
|
+
if (text[0] === text[0].toUpperCase() && text[1] === text[1].toLowerCase()) {
|
|
141
|
+
text = text[0].toLowerCase() + text.slice(1);
|
|
142
|
+
}
|
|
143
|
+
const r1 = /([a-z0-9]+)([A-Z]+[a-z0-9]*)/;
|
|
144
|
+
const r2 = /([A-Z]+)([A-Z][a-z])/;
|
|
145
|
+
const m1 = text.match(r1);
|
|
146
|
+
const m2 = text.match(r2);
|
|
147
|
+
if (!m1 && !m2) {
|
|
148
|
+
return text;
|
|
149
|
+
}
|
|
150
|
+
let result = "";
|
|
151
|
+
if (m1) {
|
|
152
|
+
const [left, right] = splitBetweenTwoGroups(m1, text);
|
|
153
|
+
const part1 = normalizeCamelPascalCase(left);
|
|
154
|
+
const part2 = normalizeCamelPascalCase(right);
|
|
155
|
+
result = part1 + "-" + part2;
|
|
156
|
+
}
|
|
157
|
+
else if (m2) {
|
|
158
|
+
const [left, right] = splitBetweenTwoGroups(m2, text);
|
|
159
|
+
const part1 = normalizeCamelPascalCase(left);
|
|
160
|
+
const part2 = normalizeCamelPascalCase(right);
|
|
161
|
+
result = part1 + "-" + part2;
|
|
162
|
+
}
|
|
163
|
+
if (result === "") {
|
|
164
|
+
throw new Error("Result string should not be empty");
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Split original regex match string into (left, right) at the boundary between 2 adjacent capture groups.
|
|
170
|
+
* Ported from Python: scripts/organizer/util/__init__.py
|
|
171
|
+
*/
|
|
172
|
+
function splitBetweenTwoGroups(match, originalString) {
|
|
173
|
+
if (match.index === undefined) {
|
|
174
|
+
throw new Error("Match index is undefined");
|
|
175
|
+
}
|
|
176
|
+
// Find the boundary between the two groups
|
|
177
|
+
const firstGroupLength = match[1].length;
|
|
178
|
+
const boundary = match.index + firstGroupLength;
|
|
179
|
+
return [originalString.slice(0, boundary), originalString.slice(boundary)];
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/plugins/markdown/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
/**
|
|
3
|
+
* Validate that LaTeX delimiters ($...$) and ($$...$$) are balanced.
|
|
4
|
+
* Catches broken math rendering from mismatched or unclosed delimiters.
|
|
5
|
+
* Works with the original source text to properly handle escaped sequences.
|
|
6
|
+
*/
|
|
7
|
+
export declare const validateLatexDelimiters: Rule.RuleModule;
|
|
8
|
+
//# sourceMappingURL=validate-latex-delimiters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-latex-delimiters.d.ts","sourceRoot":"","sources":["../../../src/plugins/markdown/validate-latex-delimiters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAKnC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,IAAI,CAAC,UAkF1C,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { removeEscapedDelimiters } from "./utils.js";
|
|
2
|
+
import { FencedCodeBlockTracker } from "./utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Validate that LaTeX delimiters ($...$) and ($$...$$) are balanced.
|
|
5
|
+
* Catches broken math rendering from mismatched or unclosed delimiters.
|
|
6
|
+
* Works with the original source text to properly handle escaped sequences.
|
|
7
|
+
*/
|
|
8
|
+
export const validateLatexDelimiters = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: "problem",
|
|
11
|
+
docs: {
|
|
12
|
+
description: "Validate that LaTeX delimiters ($...$ and $$...$$) are balanced and properly paired",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
let alreadyProcessed = false;
|
|
17
|
+
return {
|
|
18
|
+
"*": (node) => {
|
|
19
|
+
if (alreadyProcessed || node.type !== "root")
|
|
20
|
+
return;
|
|
21
|
+
alreadyProcessed = true;
|
|
22
|
+
const sourceCode = context.sourceCode;
|
|
23
|
+
if (!sourceCode)
|
|
24
|
+
return;
|
|
25
|
+
const text = sourceCode.getText();
|
|
26
|
+
const lines = text.split("\n");
|
|
27
|
+
const codeBlockTracker = new FencedCodeBlockTracker(text);
|
|
28
|
+
// Track unclosed delimiters
|
|
29
|
+
let inlineDelimiterCount = 0;
|
|
30
|
+
let displayDelimiterCount = 0;
|
|
31
|
+
let inlineStartLine = -1;
|
|
32
|
+
let displayStartLine = -1;
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const line = lines[i];
|
|
35
|
+
// Skip lines inside fenced code blocks
|
|
36
|
+
if (codeBlockTracker.isLineInFencedCodeBlock(i)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// Remove escaped dollar signs before counting
|
|
40
|
+
const withoutEscaped = removeEscapedDelimiters(line);
|
|
41
|
+
// Count $$ first (to avoid counting them as two $)
|
|
42
|
+
const displayMatches = withoutEscaped.match(/\$\$/g);
|
|
43
|
+
if (displayMatches) {
|
|
44
|
+
displayDelimiterCount += displayMatches.length;
|
|
45
|
+
if (displayDelimiterCount % 2 === 1 && displayStartLine === -1) {
|
|
46
|
+
displayStartLine = i;
|
|
47
|
+
}
|
|
48
|
+
else if (displayDelimiterCount % 2 === 0) {
|
|
49
|
+
displayStartLine = -1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Count remaining $ (after removing $$)
|
|
53
|
+
const withoutDisplay = withoutEscaped.replace(/\$\$/g, "");
|
|
54
|
+
const inlineMatches = withoutDisplay.match(/\$/g);
|
|
55
|
+
if (inlineMatches) {
|
|
56
|
+
inlineDelimiterCount += inlineMatches.length;
|
|
57
|
+
if (inlineDelimiterCount % 2 === 1 && inlineStartLine === -1) {
|
|
58
|
+
inlineStartLine = i;
|
|
59
|
+
}
|
|
60
|
+
else if (inlineDelimiterCount % 2 === 0) {
|
|
61
|
+
inlineStartLine = -1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Report unclosed display math
|
|
66
|
+
if (displayDelimiterCount % 2 !== 0) {
|
|
67
|
+
context.report({
|
|
68
|
+
loc: { line: displayStartLine + 1, column: 0 },
|
|
69
|
+
message: `Unclosed display math delimiter ($$). Expected closing $$ to match the opening on line ${displayStartLine + 1}.`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Report unclosed inline math
|
|
73
|
+
if (inlineDelimiterCount % 2 !== 0) {
|
|
74
|
+
context.report({
|
|
75
|
+
loc: { line: inlineStartLine + 1, column: 0 },
|
|
76
|
+
message: `Unclosed inline math delimiter ($). Expected closing $ to match the opening on line ${inlineStartLine + 1}.`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=validate-latex-delimiters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-latex-delimiters.js","sourceRoot":"","sources":["../../../src/plugins/markdown/validate-latex-delimiters.ts"],"names":[],"mappings":"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"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { enforceLinkConvention } from "./dist/plugins/markdown/enforce-link-convention";
|
|
2
|
+
export { inlineMathAloneOnLine } from "./dist/plugins/markdown/inline-math-alone-on-line";
|
|
3
|
+
export { noH1Headers } from "./dist/plugins/markdown/no-h1-headers";
|
|
4
|
+
export { requireBlankLineAfterHtml } from "./dist/plugins/markdown/require-blank-line-after-html";
|
|
5
|
+
export { requireDisplayMathFormatting } from "./dist/plugins/markdown/require-display-math-formatting";
|
|
6
|
+
export { requireFrontmatter } from "./dist/plugins/markdown/require-frontmatter";
|
|
7
|
+
export { validateLatexDelimiters } from "./dist/plugins/markdown/validate-latex-delimiters";
|
package/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { enforceLinkConvention } from "./dist/plugins/markdown/enforce-link-convention.js";
|
|
2
|
+
export { inlineMathAloneOnLine } from "./dist/plugins/markdown/inline-math-alone-on-line.js";
|
|
3
|
+
export { noH1Headers } from "./dist/plugins/markdown/no-h1-headers.js";
|
|
4
|
+
export { requireBlankLineAfterHtml } from "./dist/plugins/markdown/require-blank-line-after-html.js";
|
|
5
|
+
export { requireDisplayMathFormatting } from "./dist/plugins/markdown/require-display-math-formatting.js";
|
|
6
|
+
export { requireFrontmatter } from "./dist/plugins/markdown/require-frontmatter.js";
|
|
7
|
+
export { validateLatexDelimiters } from "./dist/plugins/markdown/validate-latex-delimiters.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-cannoli-plugins",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"generate-exports": "node scripts/generate-exports.js",
|
|
9
|
+
"prepublishOnly": "npm run build && npm run generate-exports",
|
|
10
|
+
"test": "python3 test_eslint_plugins.py",
|
|
11
|
+
"validate-rules": "python3 src/tests/validate_rules.py"
|
|
12
|
+
},
|
|
13
|
+
"author": "OccasionalCoderByTrade",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"description": "ESLint plugins for linting Markdown files",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"eslint",
|
|
18
|
+
"eslint-plugin",
|
|
19
|
+
"markdown",
|
|
20
|
+
"linting"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/OccasionalCoderByTrade/eslint-md-plugins"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/",
|
|
28
|
+
"index.js",
|
|
29
|
+
"index.d.ts"
|
|
30
|
+
],
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@eslint/js": "^10.0.1",
|
|
33
|
+
"@eslint/markdown": "^7.5.1",
|
|
34
|
+
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
35
|
+
"eslint": "^10.0.3",
|
|
36
|
+
"globals": "^17.4.0",
|
|
37
|
+
"jiti": "^2.6.1",
|
|
38
|
+
"prettier": "^3.8.1",
|
|
39
|
+
"remark-parse": "^11.0.0",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@eslint/markdown": "^7.0.0",
|
|
44
|
+
"eslint": "^10.0.3"
|
|
45
|
+
}
|
|
46
|
+
}
|