eslint-cannoli-plugins 1.0.13 → 1.1.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/dist/index.d.ts +1 -0
- package/dist/index.js +93 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as eslint from 'eslint';
|
|
|
2
2
|
|
|
3
3
|
declare const _default: {
|
|
4
4
|
rules: {
|
|
5
|
+
"enforce-frontmatter-schema": eslint.Rule.RuleModule;
|
|
5
6
|
"require-frontmatter": eslint.Rule.RuleModule;
|
|
6
7
|
"no-h1-headers": eslint.Rule.RuleModule;
|
|
7
8
|
"require-blank-line-after-html": eslint.Rule.RuleModule;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
// src/plugins/markdown/enforce-frontmatter-schema.ts
|
|
2
|
+
import * as jsYaml from "js-yaml";
|
|
3
|
+
import { minimatch } from "minimatch";
|
|
4
|
+
|
|
1
5
|
// src/plugins/markdown/utils.ts
|
|
2
6
|
function extractFrontmatter(text) {
|
|
3
|
-
const frontmatterMatch = text.match(
|
|
7
|
+
const frontmatterMatch = text.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---/);
|
|
4
8
|
return frontmatterMatch ? frontmatterMatch[1] : null;
|
|
5
9
|
}
|
|
6
10
|
function getFrontmatterEndLine(text) {
|
|
@@ -125,6 +129,93 @@ function splitBetweenTwoGroups(match, originalString) {
|
|
|
125
129
|
return [originalString.slice(0, boundary), originalString.slice(boundary)];
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
// src/plugins/markdown/enforce-frontmatter-schema.ts
|
|
133
|
+
function getNestedValue(obj, path) {
|
|
134
|
+
let current = obj;
|
|
135
|
+
for (const key of path) {
|
|
136
|
+
if (current === null || typeof current !== "object") {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
current = current[key];
|
|
140
|
+
}
|
|
141
|
+
return current;
|
|
142
|
+
}
|
|
143
|
+
function matchesPattern(value, pattern) {
|
|
144
|
+
const strValue = String(value);
|
|
145
|
+
if (pattern instanceof RegExp) {
|
|
146
|
+
return pattern.test(strValue);
|
|
147
|
+
}
|
|
148
|
+
return minimatch(strValue, pattern);
|
|
149
|
+
}
|
|
150
|
+
function matchesAny(value, patterns) {
|
|
151
|
+
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
152
|
+
return list.some((p) => matchesPattern(value, p));
|
|
153
|
+
}
|
|
154
|
+
function describePatterns(patterns) {
|
|
155
|
+
const list = Array.isArray(patterns) ? patterns : [patterns];
|
|
156
|
+
const descs = list.map((p) => p instanceof RegExp ? p.toString() : `"${p}"`);
|
|
157
|
+
return descs.length === 1 ? descs[0] : `[${descs.join(", ")}]`;
|
|
158
|
+
}
|
|
159
|
+
var enforceFrontmatterSchema = {
|
|
160
|
+
meta: {
|
|
161
|
+
type: "problem",
|
|
162
|
+
docs: {
|
|
163
|
+
description: "Enforce frontmatter field presence and value patterns. Keys use __ to denote nesting (e.g. course__code checks course.code). Values are glob strings (matched with minimatch) or RegExp, or arrays of either."
|
|
164
|
+
},
|
|
165
|
+
schema: [{}]
|
|
166
|
+
},
|
|
167
|
+
create(context) {
|
|
168
|
+
let alreadyProcessed = false;
|
|
169
|
+
return {
|
|
170
|
+
"*": (node) => {
|
|
171
|
+
if (alreadyProcessed || node.type !== "root") return;
|
|
172
|
+
alreadyProcessed = true;
|
|
173
|
+
const options = context.options[0];
|
|
174
|
+
if (!options || Object.keys(options).length === 0) return;
|
|
175
|
+
const sourceCode = context.sourceCode;
|
|
176
|
+
if (!sourceCode) return;
|
|
177
|
+
const text = sourceCode.getText();
|
|
178
|
+
const frontmatterText = extractFrontmatter(text);
|
|
179
|
+
if (!frontmatterText) {
|
|
180
|
+
context.report({
|
|
181
|
+
loc: { line: 1, column: 0 },
|
|
182
|
+
message: "Missing frontmatter"
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let parsed;
|
|
187
|
+
try {
|
|
188
|
+
parsed = jsYaml.load(frontmatterText);
|
|
189
|
+
} catch {
|
|
190
|
+
context.report({
|
|
191
|
+
loc: { line: 1, column: 0 },
|
|
192
|
+
message: "Frontmatter is invalid YAML"
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (const [key, patterns] of Object.entries(options)) {
|
|
197
|
+
const path = key.split("__");
|
|
198
|
+
const value = getNestedValue(parsed, path);
|
|
199
|
+
const fieldLabel = path.join(".");
|
|
200
|
+
if (value === void 0 || value === null) {
|
|
201
|
+
context.report({
|
|
202
|
+
loc: { line: 1, column: 0 },
|
|
203
|
+
message: `Required frontmatter field '${fieldLabel}' is missing or uninitialized`
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!matchesAny(value, patterns)) {
|
|
208
|
+
context.report({
|
|
209
|
+
loc: { line: 1, column: 0 },
|
|
210
|
+
message: `Frontmatter field '${fieldLabel}' has invalid value "${value}". It must match any pattern in: ${describePatterns(patterns)}`
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
128
219
|
// src/plugins/markdown/enforce-link-convention.ts
|
|
129
220
|
var enforceLinkConvention = {
|
|
130
221
|
meta: {
|
|
@@ -765,6 +856,7 @@ var validateLatexDelimiters = {
|
|
|
765
856
|
// src/index.ts
|
|
766
857
|
var src_default = {
|
|
767
858
|
rules: {
|
|
859
|
+
"enforce-frontmatter-schema": enforceFrontmatterSchema,
|
|
768
860
|
"require-frontmatter": requireFrontmatter,
|
|
769
861
|
"no-h1-headers": noH1Headers,
|
|
770
862
|
"require-blank-line-after-html": requireBlankLineAfterHtml,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugins/markdown/utils.ts","../src/plugins/markdown/enforce-link-convention.ts","../src/plugins/markdown/inline-math-alone-on-line.ts","../src/plugins/markdown/limit-display-math-length.ts","../src/plugins/markdown/no-escape-latex-delimiters.ts","../src/plugins/markdown/no-h1-headers.ts","../src/plugins/markdown/require-blank-line-after-html.ts","../src/plugins/markdown/require-display-math-formatting.ts","../src/plugins/markdown/require-frontmatter.ts","../src/plugins/markdown/validate-latex-delimiters.ts","../src/index.ts"],"sourcesContent":["/**\n * Returns the frontmatter string (without the --- delimiters) if it exists\n */\nexport function extractFrontmatter(text: string): string | null {\n const frontmatterMatch = text.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n return frontmatterMatch ? frontmatterMatch[1] : null;\n}\n\n/**\n * Find the line number where frontmatter ends after the closing --- delimiter\n */\nexport function getFrontmatterEndLine(text: string): number {\n const lines = text.split(\"\\n\");\n\n if (lines[0] !== \"---\") {\n return 0;\n }\n\n for (let i = 1; i < lines.length; i++) {\n if (lines[i] === \"---\") {\n return i + 1;\n }\n }\n\n return 0; // no closing --- found\n}\n\n/**\n * Find all fenced code block ranges in the text\n * Returns an array of [startLine, endLine] pairs (0-indexed)\n * Correctly handles indented code blocks (e.g., nested under list items)\n * because fence delimiters are detected anywhere on the line after trimming\n */\nfunction getFencedCodeBlockRanges(text: string): Array<[number, number]> {\n const lines = text.split(\"\\n\");\n const ranges: Array<[number, number]> = [];\n\n let inCodeBlock = false;\n let blockStartLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const trimmed = line.trim();\n\n // Check for fence delimiter regardless of indentation level\n if (/^(```|~~~)/.test(trimmed)) {\n if (inCodeBlock) {\n // End of code block\n ranges.push([blockStartLine, i]);\n inCodeBlock = false;\n } else {\n // Start of code block\n inCodeBlock = true;\n blockStartLine = i;\n }\n }\n }\n\n // If there's an unclosed code block at EOF, still track it\n if (inCodeBlock) {\n ranges.push([blockStartLine, lines.length - 1]);\n }\n\n return ranges;\n}\n\nexport class FencedCodeBlockTracker {\n private ranges: Array<[number, number]>;\n private currentRangeIndex: number = 0;\n\n constructor(text: string) {\n this.ranges = getFencedCodeBlockRanges(text);\n }\n\n /**\n * Check if a given line index falls within any fenced code block range.\n */\n isLineInFencedCodeBlock(lineIndex: number): boolean {\n if (this.ranges.length === 0) {\n return false;\n }\n\n while (this.currentRangeIndex < this.ranges.length) {\n const [start, end] = this.ranges[this.currentRangeIndex];\n\n if (lineIndex < start) {\n return false;\n }\n\n if (lineIndex >= start && lineIndex <= end) {\n return true;\n }\n\n if (lineIndex > end) {\n this.currentRangeIndex++;\n continue;\n }\n }\n\n // lineIndex is past all ranges\n return false;\n }\n}\n\n/**\n * Remove escaped LaTeX delimiters from text before counting\n * Handles both inline ($) and display ($$) math delimiters\n */\nexport function removeEscapedDelimiters(text: string): string {\n // Remove escaped dollar signs (\\$) before counting delimiters\n return text.replace(/\\\\\\$/g, \"\");\n}\n\n/**\n * Count occurrences of a delimiter in text (after removing escaped versions)\n * Returns the count and tracks the line where the count becomes odd\n */\nexport interface DelimiterCount {\n count: number;\n firstUnclosedLine: number; // 0-indexed line number, or -1 if balanced\n}\n\n/**\n * Slugify a filename by converting to lowercase, replacing spaces/underscores/slashes with hyphens,\n * removing special characters, and normalizing diacritics to ASCII.\n * Ported from Python: scripts/organizer/util/__init__.py\n */\nexport function slugify(name: string): string {\n let slug = name;\n\n // replace C++ with 'cpp'\n slug = slug.replace(/(_+)?c\\+\\+/gi, \"$1cpp\");\n slug = slug.replace(\"---\", \"___\");\n\n // Replace & with ' and '\n slug = slug.replace(/&/g, \" and \");\n\n // Replace spaces, underscores, and forward slashes with hyphens\n slug = slug.replace(/ /g, \"-\").replace(/\\//g, \"-\");\n\n // Normalize Unicode (decompose diacritics)\n slug = slug\n .normalize(\"NFKD\")\n .split(\"\")\n .filter((char) => char.charCodeAt(0) < 128)\n .join(\"\");\n\n // Replace non-alphanumeric characters (except hyphen, underscore, plus) with hyphens\n slug = slug.replace(/[^-+_a-zA-Z0-9]/g, \"-\");\n\n // Collapse multiple consecutive hyphens into a single hyphen\n slug = slug.replace(/-+/g, \"-\");\n slug = slug.replace(/-*(_+)-*/g, \"$1\");\n\n slug = normalizeCamelPascalCase(slug);\n\n // Strip leading/trailing hyphens and lowercase\n return slug.replace(/^-+|-+$/g, \"\").toLowerCase();\n}\n\n/**\n * Check if a string contains only valid local file link characters (lowercase, alphanumeric, hyphens, underscores, slashes, dots)\n */\nexport function isValidLinkFormat(link: string): boolean {\n // Allow: lowercase alphanumeric, hyphens, underscores, slashes, dots, hash, question mark\n // For local files, we don't allow & or = (those are query params for external URLs)\n return /^[a-z0-9\\-_/.#?]+$/.test(link);\n}\n\n/**\n * Insert dashes at camelCase transitions for preparation before slugifying.\n * Ported from Python: scripts/organizer/util/__init__.py\n */\nfunction normalizeCamelPascalCase(text: string): string {\n if (text.length < 2) {\n return text;\n }\n\n // Handle PascalCase starting with single uppercase letter\n if (text[0] === text[0].toUpperCase() && text[1] === text[1].toLowerCase()) {\n text = text[0].toLowerCase() + text.slice(1);\n }\n\n const r1 = /([a-z0-9]+)([A-Z]+[a-z0-9]*)/;\n const r2 = /([A-Z]+)([A-Z][a-z])/;\n\n const m1 = text.match(r1);\n const m2 = text.match(r2);\n\n if (!m1 && !m2) {\n return text;\n }\n\n let result = \"\";\n if (m1) {\n const [left, right] = splitBetweenTwoGroups(m1, text);\n const part1 = normalizeCamelPascalCase(left);\n const part2 = normalizeCamelPascalCase(right);\n result = part1 + \"-\" + part2;\n } else if (m2) {\n const [left, right] = splitBetweenTwoGroups(m2, text);\n const part1 = normalizeCamelPascalCase(left);\n const part2 = normalizeCamelPascalCase(right);\n result = part1 + \"-\" + part2;\n }\n\n if (result === \"\") {\n throw new Error(\"Result string should not be empty\");\n }\n\n return result;\n}\n\n/**\n * Split original regex match string into (left, right) at the boundary between 2 adjacent capture groups.\n * Ported from Python: scripts/organizer/util/__init__.py\n */\nfunction splitBetweenTwoGroups(match: RegExpMatchArray, originalString: string): [string, string] {\n if (match.index === undefined) {\n throw new Error(\"Match index is undefined\");\n }\n\n // Find the boundary between the two groups\n const firstGroupLength = match[1].length;\n const boundary = match.index + firstGroupLength;\n\n return [originalString.slice(0, boundary), originalString.slice(boundary)];\n}\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine, isValidLinkFormat, slugify } from \"./utils.js\";\n\n/**\n * Enforce link convention: all lowercase and no offending characters.\n * Suggests slugified version as the proper convention.\n */\nexport const enforceLinkConvention: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Enforce that links are all lowercase and contain only valid characters (no spaces, special chars, etc.)\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n // Match markdown links: [text](link) and [text]: link\n // Use negative lookbehind to avoid matching escaped brackets like \\[text\\]\n const inlineLinksRegex = /(?<!\\\\)\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n const referenceLinkRegex = /^\\s*\\[([^\\]]+)\\]:\\s*([^<\\n]+?)(?:\\s+\"[^\"]*\")?(?:\\s*<!--.*)?$/;\n\n // Check inline links [text](link)\n let inlineMatch;\n while ((inlineMatch = inlineLinksRegex.exec(line)) !== null) {\n const link = inlineMatch[2];\n // Calculate column position: find the opening ( and start from the first char after it\n const fullMatch = inlineMatch[0];\n const openParenIndex = fullMatch.indexOf(\"(\");\n const linkColumn = inlineMatch.index + openParenIndex + 1;\n checkLink(link, i, linkColumn, context);\n }\n\n // Check reference links [text]: link\n const refMatch = line.match(referenceLinkRegex);\n if (refMatch) {\n const link = refMatch[2].trim();\n // Calculate exact column position where the link starts\n const linkPosition = line.indexOf(link);\n checkLink(link, i, linkPosition, context);\n }\n }\n\n function slugifyPath(path: string): string {\n // Split by slash and slugify each component separately to preserve directory structure\n const parts = path.split(\"/\");\n return parts\n .map((part) => {\n // Don't slugify empty parts or special directory references\n if (part === \"\" || part === \".\" || part === \"..\") {\n return part;\n }\n return slugify(part);\n })\n .join(\"/\");\n }\n\n function extractExtension(path: string): { basename: string; extension: string } {\n // Find the last slash to separate directory from filename\n const lastSlashIndex = path.lastIndexOf(\"/\");\n const filename = path.substring(lastSlashIndex + 1);\n\n // Find the last dot in the filename\n const lastDotIndex = filename.lastIndexOf(\".\");\n\n // Only extract extension if dot is not at the start (e.g., not \".gitignore\")\n if (lastDotIndex > 0) {\n const basename = filename.substring(0, lastDotIndex);\n const extension = filename.substring(lastDotIndex);\n const dirPart = lastSlashIndex >= 0 ? path.substring(0, lastSlashIndex + 1) : \"\";\n return {\n basename: dirPart + basename,\n extension: extension,\n };\n }\n\n return { basename: path, extension: \"\" };\n }\n\n function checkLink(\n link: string,\n lineIndex: number,\n columnIndex: number,\n ctx: Rule.RuleContext\n ): void {\n // Skip external links (any valid URL) and anchors\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n new ((globalThis as any).URL)(link);\n return; // Valid external URL, skip it\n } catch {\n // Not a valid URL - could be a relative path or anchor\n if (link.startsWith(\"#\")) {\n return; // It's an anchor, skip it\n }\n }\n\n // Check if link is valid format\n if (!isValidLinkFormat(link)) {\n // Separate basename and extension to preserve the extension\n const { basename, extension } = extractExtension(link);\n const suggestion = slugifyPath(basename) + extension;\n\n // Only flag if the link doesn't match the suggested slug\n if (link !== suggestion) {\n ctx.report({\n loc: {\n start: { line: lineIndex + 1, column: columnIndex },\n end: { line: lineIndex + 1, column: columnIndex + link.length },\n },\n 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}\"`,\n });\n }\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Detect inline math expressions ($...$) that exist alone on their own line.\n * This usually indicates the expression was meant to be display/block math ($$...$$).\n *\n * Bad: $x = 2$\n * Good: $$x = 2$$ OR inline text $x = 2$ more text\n */\nexport const inlineMathAloneOnLine: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n fixable: \"code\",\n docs: {\n description:\n \"Detect inline math expressions that exist alone on their own line (likely meant to be display math)\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Skip empty lines\n if (!trimmed) {\n continue;\n }\n\n // Check for display math ($$) - not what we're looking for\n if (trimmed.includes(\"$$\")) {\n continue;\n }\n\n // Check for inline math ($...$)\n const singleDollarRegex = /^\\$[^$]+\\$\\s*$/;\n const match = trimmed.match(singleDollarRegex);\n\n if (match) {\n // Found inline math alone on a line\n const indent = line.match(/^(\\s*)/)?.[1] ?? \"\";\n const expression = trimmed.slice(1, -1); // Remove the $...$\n\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n \"Inline math expression found alone on its own line. Did you mean to use display math ($$...$$) instead?\",\n fix(fixer) {\n // Convert from $...$ to $$...$$\n const replacement = `${indent}$$${expression}$$`;\n\n const lineStart = sourceCode.getIndexFromLoc({ line: i + 1, column: 1 });\n const lineEnd = sourceCode.getIndexFromLoc({ line: i + 1, column: line.length + 1 });\n\n return fixer.replaceTextRange([lineStart, lineEnd], replacement);\n },\n });\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Limit display math block lengths to 60 characters for mobile view.\n * Long math expressions should be split across multiple lines using \\\\ and & for alignment.\n *\n * Bad: $$ x = y + z + a + b + c + d + e + f + g + h + i + j $$\n * Good: $$\n * x = y + z + a + b \\\\\n * & + c + d + e + f\n * $$\n */\nexport const limitDisplayMathLength: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Limit display math ($$) block lengths for better mobile view\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n charLimit: {\n type: \"number\",\n default: 60,\n description: \"Maximum number of characters per line in display math blocks\",\n },\n },\n additionalProperties: false,\n },\n ],\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n const options = (context.options[0] as { charLimit?: number }) || {};\n const charLimit = options.charLimit ?? 60;\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track which lines we've already processed to avoid duplicate reports\n const processedLines = new Set<number>();\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n // Skip already processed lines\n if (processedLines.has(i)) {\n continue;\n }\n\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Skip if line doesn't contain $$ or is empty\n if (!trimmed.includes(\"$$\")) {\n continue;\n }\n\n // Check for single-line display math: $$ content $$\n const singleLineMatch = trimmed.match(/^(\\s*)\\$\\$(.*?)\\$\\$\\s*$/);\n if (singleLineMatch) {\n const content = singleLineMatch[2];\n if (content.length > charLimit) {\n processedLines.add(i);\n const lineContent = line.trimEnd();\n const lineStart = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: 1,\n });\n const lineEnd = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: lineContent.length + 1,\n });\n context.report({\n loc: { line: i + 1, column: 1 },\n range: [lineStart, lineEnd],\n message: `Display math block is ${content.length} characters, exceeds ${charLimit} character limit. Consider splitting into multiple lines using \\\\\\\\ as line delimiter and & for alignment.`,\n } as any);\n }\n continue;\n }\n\n // Check for multi-line display math: opening $$\n if (trimmed === \"$$\") {\n // Find matching closing $$\n let closingLine = -1;\n const contentLineIndices: number[] = [];\n\n for (let j = i + 1; j < lines.length; j++) {\n if (codeBlockTracker.isLineInFencedCodeBlock(j)) {\n continue;\n }\n\n const currentLine = lines[j];\n const currentTrimmed = currentLine.trim();\n\n if (currentTrimmed === \"$$\") {\n closingLine = j;\n break;\n }\n\n contentLineIndices.push(j);\n }\n\n if (closingLine !== -1) {\n processedLines.add(i);\n processedLines.add(closingLine);\n\n // Check each content line individually for length\n for (const lineIndex of contentLineIndices) {\n const contentLine = lines[lineIndex];\n const contentTrimmed = contentLine.trim();\n const contentLength = contentTrimmed.length;\n\n // Skip empty lines and comment lines\n if (!contentTrimmed || contentTrimmed.startsWith(\"<!--\")) {\n continue;\n }\n\n if (contentLength > charLimit) {\n const contentLineText = lines[lineIndex];\n const contentLineTrimmed = contentLineText.trimEnd();\n const contentLineStart = sourceCode.getIndexFromLoc({\n line: lineIndex + 1,\n column: 1,\n });\n const contentLineEnd = sourceCode.getIndexFromLoc({\n line: lineIndex + 1,\n column: contentLineTrimmed.length + 1,\n });\n context.report({\n loc: { line: lineIndex + 1, column: 1 },\n range: [contentLineStart, contentLineEnd],\n message: `Display math line is ${contentLength} characters, exceeds ${charLimit} character limit. Consider splitting using \\\\\\\\ as line delimiter and & for alignment.`,\n } as any);\n processedLines.add(lineIndex);\n }\n }\n }\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Detect escaped LaTeX delimiters (\\[ and \\]) that exist alone on their own line.\n * When these delimiters appear alone, use $$...$$ (double dollar) display math syntax instead.\n *\n * Bad: \\[\n * Bad: \\]\n * Good: $$x = y$$\n */\nexport const noEscapeLatexDelimiters: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n fixable: \"code\",\n docs: {\n description:\n \"Detect escaped LaTeX delimiters (\\\\[ and \\\\]) that exist alone on their own line\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track which lines we've already processed to avoid duplicate reports\n const processedLines = new Set<number>();\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip already processed lines\n if (processedLines.has(i)) {\n continue;\n }\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Skip empty lines\n if (!trimmed) {\n continue;\n }\n\n // Check for \\[ alone on the line\n if (trimmed === \"\\\\[\") {\n // Find matching \\] delimiter\n let closingDelimiterLine = -1;\n for (let j = i + 1; j < lines.length; j++) {\n if (codeBlockTracker.isLineInFencedCodeBlock(j)) {\n continue;\n }\n const jTrimmed = lines[j].trim();\n if (jTrimmed === \"\\\\]\") {\n closingDelimiterLine = j;\n break;\n }\n }\n\n if (closingDelimiterLine !== -1) {\n processedLines.add(i);\n processedLines.add(closingDelimiterLine);\n\n const openingIndent = line.match(/^(\\s*)/)?.[1] ?? \"\";\n const closingLine = lines[closingDelimiterLine];\n const closingIndent = closingLine.match(/^(\\s*)/)?.[1] ?? \"\";\n\n context.report({\n loc: { line: i + 1, column: 0 },\n message: `LaTeX delimiters \\\\[ and \\\\] found. Use $$...$$ (double dollar) syntax for display math instead.`,\n fix(fixer) {\n const openingReplacement = `${openingIndent}$$`;\n const closingReplacement = `${closingIndent}$$`;\n\n const openingStart = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: 1,\n });\n const openingEnd = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: line.length + 1,\n });\n\n const closingStart = sourceCode.getIndexFromLoc({\n line: closingDelimiterLine + 1,\n column: 1,\n });\n const closingEnd = sourceCode.getIndexFromLoc({\n line: closingDelimiterLine + 1,\n column: closingLine.length + 1,\n });\n\n return [\n fixer.replaceTextRange([openingStart, openingEnd], openingReplacement),\n fixer.replaceTextRange([closingStart, closingEnd], closingReplacement),\n ];\n },\n });\n }\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\nexport const noH1Headers: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Disallow h1 headers in markdown files since the frontmatter title already serves that purpose\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // check for h1 headers\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n if (/^#\\s+/.test(line)) {\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n \"H1 headings are not allowed in Astro markdown files; use the frontmatter title field instead\",\n });\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Check if a line contains a markdown construct that requires blank line after HTML\n */\nfunction isProblematicMarkdownConstruct(line: string): boolean {\n const trimmed = line.trim();\n\n if (!trimmed) return false;\n\n // Headings (# through ###### followed by space)\n if (/^#{1,6}\\s/.test(trimmed)) return true;\n\n // Blockquote (> followed by space)\n if (/^>\\s/.test(trimmed)) return true;\n\n // List (-, *, +) followed by space\n if (/^[-*+]\\s/.test(trimmed)) return true;\n\n // Image\n if (/^!\\[/.test(trimmed)) return true;\n\n // Link (starts with [ and has matching ])\n if (/^\\[/.test(trimmed) && /\\]/.test(trimmed)) return true;\n\n // Inline code (starts with backtick)\n if (/^`/.test(trimmed)) return true;\n\n // Bold/Strong (**, __)\n if (/^\\*\\*/.test(trimmed) || /^__/.test(trimmed)) return true;\n\n // Strikethrough (~~)\n if (/^~~/.test(trimmed)) return true;\n\n // Emphasis (*, _) - but not list markers\n if (/^\\*[^\\s*]/.test(trimmed) || /^_[^\\s_]/.test(trimmed)) return true;\n\n // Table (starts with |)\n if (/^\\|/.test(trimmed)) return true;\n\n return false;\n}\n\n/**\n * Check if a line is an HTML element/tag (but not comments or blank lines)\n */\nfunction isHtmlLine(line: string): boolean {\n const trimmed = line.trim();\n // HTML tags start with < but exclude comments\n if (!trimmed.startsWith(\"<\")) return false;\n if (trimmed.startsWith(\"<!--\")) return false; // Exclude comments\n return true;\n}\n\nexport const requireBlankLineAfterHtml: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Require blank line after HTML blocks when followed by markdown constructs\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track the last HTML line seen\n let lastHtmlLine = -1;\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const line = lines[i];\n const trimmed = line.trim();\n\n // Update last HTML line if this is HTML\n if (isHtmlLine(line)) {\n lastHtmlLine = i;\n continue;\n }\n\n // If this is an empty line, reset tracking (blank line is good separator)\n if (!trimmed) {\n lastHtmlLine = -1;\n continue;\n }\n\n // We have a non-empty, non-HTML line\n // Check if it follows HTML without blank line\n if (lastHtmlLine >= 0 && i === lastHtmlLine + 1) {\n // Direct next line after HTML block\n if (isProblematicMarkdownConstruct(line)) {\n context.report({\n loc: { line: i + 1, column: 0 },\n message: \"Blank line required after HTML block when followed by markdown construct\",\n });\n }\n }\n // Reset tracking - we've seen non-HTML, non-blank content\n lastHtmlLine = -1;\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Check if a line contains display math ($$) that's not properly formatted.\n * Display math is allowed inline with other content.\n * Only enforce the separate line pattern ($$\\n...\\n$$) when the math block is alone on its line.\n *\n * Bad: $$2+2$$ (alone on line)\n * Good: $$\n * 2+2\n * $$\n * Also Good: Some text $$2+2$$ more text (inline is allowed)\n */\nexport const requireDisplayMathFormatting: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n fixable: \"code\",\n docs: {\n description:\n \"Require display math ($$) blocks alone on a line to use the multi-line format ($$\\\\n...\\\\n$$)\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Remove inline code (backtick-enclosed content) and HTML comments before checking for $$\n let withoutInlineCode = trimmed.replace(/`[^`]*`/g, \"\");\n withoutInlineCode = withoutInlineCode.replace(/<!--.*?-->/g, \"\").trim();\n\n // Check for display math markers\n if (!withoutInlineCode.includes(\"$$\")) {\n continue;\n }\n\n // Count $$ occurrences in the line (excluding those in inline code and comments)\n const dollarCount = (withoutInlineCode.match(/\\$\\$/g) || []).length;\n\n // If there's only one $$, it's an opening or closing delimiter (allowed)\n if (dollarCount === 1) {\n // Single $$ on a line is allowed - it's part of multi-line $$...$$\n continue;\n } else if (dollarCount === 2) {\n // Two $$ on the same line\n // Only enforce separate lines if the ENTIRE line is just the math block\n const doubleRegex = /^(\\s*)\\$\\$(.*?)\\$\\$\\s*$/;\n const match = withoutInlineCode.match(doubleRegex);\n if (match) {\n // The entire line is $$something$$, which requires separate lines\n const indent = match[1];\n const expression = match[2];\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n 'Display math delimiters ($$) must be on separate lines. Use:\\n$$\\nexpression\\n$$',\n fix(fixer) {\n // Build the replacement text - just the three lines without trailing newline\n // The line itself will have its trailing newline preserved by ESLint\n const replacement = `${indent}$$\\n${indent}${expression}\\n${indent}$$`;\n\n // Get the start of the line and the end (before the newline)\n const lineStart = sourceCode.getIndexFromLoc({ line: i + 1, column: 1 });\n const lineEnd = sourceCode.getIndexFromLoc({ line: i + 1, column: line.length + 1 });\n\n return fixer.replaceTextRange([lineStart, lineEnd], replacement);\n },\n });\n }\n // If match is null, the $$ is inline with other content, which is allowed - skip\n } else if (dollarCount > 2) {\n // Multiple $$ pairs on the same line - likely multiple math expressions or malformed\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n \"Multiple display math expressions ($$) found on same line. Each should be on separate lines\",\n });\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { extractFrontmatter } from \"./utils.js\";\n\nexport const requireFrontmatter: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Require title field in frontmatter\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const frontmatter = extractFrontmatter(text);\n\n if (frontmatter) {\n if (!/^\\s*title\\s*:/m.test(frontmatter)) {\n context.report({\n loc: { line: 1, column: 0 },\n message: \"Missing required 'title' field in frontmatter\",\n });\n }\n } else {\n context.report({\n loc: { line: 1, column: 0 },\n message: \"Missing frontmatter\",\n });\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { removeEscapedDelimiters } from \"./utils.js\";\nimport { FencedCodeBlockTracker } from \"./utils.js\";\n\n/**\n * Validate that LaTeX delimiters ($...$) and ($$...$$) are balanced.\n * Catches broken math rendering from mismatched or unclosed delimiters.\n * Works with the original source text to properly handle escaped sequences.\n */\nexport const validateLatexDelimiters: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Validate that LaTeX delimiters ($...$ and $$...$$) are balanced and properly paired\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track unclosed delimiters\n let inlineDelimiterCount = 0;\n let displayDelimiterCount = 0;\n let inlineStartLine = -1;\n let displayStartLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n // Remove inline code blocks (backticks) before counting\n const withoutInlineCode = line.replace(/`[^`]*`/g, \"\");\n\n // Remove HTML comments before counting\n const withoutComments = withoutInlineCode.replace(/<!--[\\s\\S]*?-->/g, \"\");\n\n // Remove escaped dollar signs before counting\n const withoutEscaped = removeEscapedDelimiters(withoutComments);\n\n // Count $$ first (to avoid counting them as two $)\n const displayMatches = withoutEscaped.match(/\\$\\$/g);\n if (displayMatches) {\n displayDelimiterCount += displayMatches.length;\n if (displayDelimiterCount % 2 === 1 && displayStartLine === -1) {\n displayStartLine = i;\n } else if (displayDelimiterCount % 2 === 0) {\n displayStartLine = -1;\n }\n }\n\n // Count remaining $ (after removing $$)\n const withoutDisplay = withoutEscaped.replace(/\\$\\$/g, \"\");\n const inlineMatches = withoutDisplay.match(/\\$/g);\n if (inlineMatches) {\n inlineDelimiterCount += inlineMatches.length;\n if (inlineDelimiterCount % 2 === 1 && inlineStartLine === -1) {\n inlineStartLine = i;\n } else if (inlineDelimiterCount % 2 === 0) {\n inlineStartLine = -1;\n }\n }\n }\n\n // Report unclosed display math\n if (displayDelimiterCount % 2 !== 0) {\n context.report({\n loc: { line: displayStartLine + 1, column: 0 },\n message: `Unclosed display math delimiter ($$). Expected closing $$ to match the opening on line ${displayStartLine + 1}.`,\n });\n }\n\n // Report unclosed inline math\n if (inlineDelimiterCount % 2 !== 0) {\n context.report({\n loc: { line: inlineStartLine + 1, column: 0 },\n message: `Unclosed inline math delimiter ($). Expected closing $ to match the opening on line ${inlineStartLine + 1}.`,\n });\n }\n },\n };\n },\n};\n","import { enforceLinkConvention } from \"./plugins/markdown/enforce-link-convention.js\";\nimport { inlineMathAloneOnLine } from \"./plugins/markdown/inline-math-alone-on-line.js\";\nimport { limitDisplayMathLength } from \"./plugins/markdown/limit-display-math-length.js\";\nimport { noEscapeLatexDelimiters } from \"./plugins/markdown/no-escape-latex-delimiters.js\";\nimport { noH1Headers } from \"./plugins/markdown/no-h1-headers.js\";\nimport { requireBlankLineAfterHtml } from \"./plugins/markdown/require-blank-line-after-html.js\";\nimport { requireDisplayMathFormatting } from \"./plugins/markdown/require-display-math-formatting.js\";\nimport { requireFrontmatter } from \"./plugins/markdown/require-frontmatter.js\";\nimport { validateLatexDelimiters } from \"./plugins/markdown/validate-latex-delimiters.js\";\n\nexport default {\n rules: {\n \"require-frontmatter\": requireFrontmatter,\n \"no-h1-headers\": noH1Headers,\n \"require-blank-line-after-html\": requireBlankLineAfterHtml,\n \"require-display-math-formatting\": requireDisplayMathFormatting,\n \"inline-math-alone-on-line\": inlineMathAloneOnLine,\n \"validate-latex-delimiters\": validateLatexDelimiters,\n \"no-escape-latex-delimiters\": noEscapeLatexDelimiters,\n \"enforce-link-convention\": enforceLinkConvention,\n \"limit-display-math-length\": limitDisplayMathLength,\n },\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/plugins/markdown/enforce-frontmatter-schema.ts","../src/plugins/markdown/utils.ts","../src/plugins/markdown/enforce-link-convention.ts","../src/plugins/markdown/inline-math-alone-on-line.ts","../src/plugins/markdown/limit-display-math-length.ts","../src/plugins/markdown/no-escape-latex-delimiters.ts","../src/plugins/markdown/no-h1-headers.ts","../src/plugins/markdown/require-blank-line-after-html.ts","../src/plugins/markdown/require-display-math-formatting.ts","../src/plugins/markdown/require-frontmatter.ts","../src/plugins/markdown/validate-latex-delimiters.ts","../src/index.ts"],"sourcesContent":["import type { Rule } from \"eslint\";\nimport * as jsYaml from \"js-yaml\";\nimport { minimatch } from \"minimatch\";\n\nimport { extractFrontmatter } from \"./utils.js\";\n\ntype FieldPattern = string | RegExp;\ntype FieldPatterns = FieldPattern | FieldPattern[];\n\nfunction getNestedValue(obj: unknown, path: string[]): unknown {\n let current: unknown = obj;\n for (const key of path) {\n if (current === null || typeof current !== \"object\") {\n return undefined;\n }\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction matchesPattern(value: unknown, pattern: FieldPattern): boolean {\n const strValue = String(value);\n if (pattern instanceof RegExp) {\n return pattern.test(strValue);\n }\n return minimatch(strValue, pattern);\n}\n\nfunction matchesAny(value: unknown, patterns: FieldPatterns): boolean {\n const list = Array.isArray(patterns) ? patterns : [patterns];\n return list.some((p) => matchesPattern(value, p));\n}\n\nfunction describePatterns(patterns: FieldPatterns): string {\n const list = Array.isArray(patterns) ? patterns : [patterns];\n const descs = list.map((p) => (p instanceof RegExp ? p.toString() : `\"${p}\"`));\n return descs.length === 1 ? descs[0] : `[${descs.join(\", \")}]`;\n}\n\nexport const enforceFrontmatterSchema: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Enforce frontmatter field presence and value patterns. Keys use __ to denote nesting (e.g. course__code checks course.code). Values are glob strings (matched with minimatch) or RegExp, or arrays of either.\",\n },\n schema: [{}],\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n alreadyProcessed = true;\n\n const options = context.options[0] as Record<string, FieldPatterns> | undefined;\n if (!options || Object.keys(options).length === 0) return;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const frontmatterText = extractFrontmatter(text);\n\n if (!frontmatterText) {\n context.report({\n loc: { line: 1, column: 0 },\n message: \"Missing frontmatter\",\n });\n return;\n }\n\n let parsed: unknown;\n try {\n parsed = jsYaml.load(frontmatterText);\n } catch {\n context.report({\n loc: { line: 1, column: 0 },\n message: \"Frontmatter is invalid YAML\",\n });\n return;\n }\n\n for (const [key, patterns] of Object.entries(options)) {\n const path = key.split(\"__\");\n const value = getNestedValue(parsed, path);\n const fieldLabel = path.join(\".\");\n\n if (value === undefined || value === null) {\n context.report({\n loc: { line: 1, column: 0 },\n message: `Required frontmatter field '${fieldLabel}' is missing or uninitialized`,\n });\n continue;\n }\n\n if (!matchesAny(value, patterns)) {\n context.report({\n loc: { line: 1, column: 0 },\n message: `Frontmatter field '${fieldLabel}' has invalid value \"${value}\". It must match any pattern in: ${describePatterns(patterns)}`,\n });\n }\n }\n },\n };\n },\n};\n","/**\n * Returns the frontmatter string (without the --- delimiters) if it exists\n */\nexport function extractFrontmatter(text: string): string | null {\n const frontmatterMatch = text.match(/^---[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n return frontmatterMatch ? frontmatterMatch[1] : null;\n}\n\n/**\n * Find the line number where frontmatter ends after the closing --- delimiter\n */\nexport function getFrontmatterEndLine(text: string): number {\n const lines = text.split(\"\\n\");\n\n if (lines[0] !== \"---\") {\n return 0;\n }\n\n for (let i = 1; i < lines.length; i++) {\n if (lines[i] === \"---\") {\n return i + 1;\n }\n }\n\n return 0; // no closing --- found\n}\n\n/**\n * Find all fenced code block ranges in the text\n * Returns an array of [startLine, endLine] pairs (0-indexed)\n * Correctly handles indented code blocks (e.g., nested under list items)\n * because fence delimiters are detected anywhere on the line after trimming\n */\nfunction getFencedCodeBlockRanges(text: string): Array<[number, number]> {\n const lines = text.split(\"\\n\");\n const ranges: Array<[number, number]> = [];\n\n let inCodeBlock = false;\n let blockStartLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const trimmed = line.trim();\n\n // Check for fence delimiter regardless of indentation level\n if (/^(```|~~~)/.test(trimmed)) {\n if (inCodeBlock) {\n // End of code block\n ranges.push([blockStartLine, i]);\n inCodeBlock = false;\n } else {\n // Start of code block\n inCodeBlock = true;\n blockStartLine = i;\n }\n }\n }\n\n // If there's an unclosed code block at EOF, still track it\n if (inCodeBlock) {\n ranges.push([blockStartLine, lines.length - 1]);\n }\n\n return ranges;\n}\n\nexport class FencedCodeBlockTracker {\n private ranges: Array<[number, number]>;\n private currentRangeIndex: number = 0;\n\n constructor(text: string) {\n this.ranges = getFencedCodeBlockRanges(text);\n }\n\n /**\n * Check if a given line index falls within any fenced code block range.\n */\n isLineInFencedCodeBlock(lineIndex: number): boolean {\n if (this.ranges.length === 0) {\n return false;\n }\n\n while (this.currentRangeIndex < this.ranges.length) {\n const [start, end] = this.ranges[this.currentRangeIndex];\n\n if (lineIndex < start) {\n return false;\n }\n\n if (lineIndex >= start && lineIndex <= end) {\n return true;\n }\n\n if (lineIndex > end) {\n this.currentRangeIndex++;\n continue;\n }\n }\n\n // lineIndex is past all ranges\n return false;\n }\n}\n\n/**\n * Remove escaped LaTeX delimiters from text before counting\n * Handles both inline ($) and display ($$) math delimiters\n */\nexport function removeEscapedDelimiters(text: string): string {\n // Remove escaped dollar signs (\\$) before counting delimiters\n return text.replace(/\\\\\\$/g, \"\");\n}\n\n/**\n * Count occurrences of a delimiter in text (after removing escaped versions)\n * Returns the count and tracks the line where the count becomes odd\n */\nexport interface DelimiterCount {\n count: number;\n firstUnclosedLine: number; // 0-indexed line number, or -1 if balanced\n}\n\n/**\n * Slugify a filename by converting to lowercase, replacing spaces/underscores/slashes with hyphens,\n * removing special characters, and normalizing diacritics to ASCII.\n * Ported from Python: scripts/organizer/util/__init__.py\n */\nexport function slugify(name: string): string {\n let slug = name;\n\n // replace C++ with 'cpp'\n slug = slug.replace(/(_+)?c\\+\\+/gi, \"$1cpp\");\n slug = slug.replace(\"---\", \"___\");\n\n // Replace & with ' and '\n slug = slug.replace(/&/g, \" and \");\n\n // Replace spaces, underscores, and forward slashes with hyphens\n slug = slug.replace(/ /g, \"-\").replace(/\\//g, \"-\");\n\n // Normalize Unicode (decompose diacritics)\n slug = slug\n .normalize(\"NFKD\")\n .split(\"\")\n .filter((char) => char.charCodeAt(0) < 128)\n .join(\"\");\n\n // Replace non-alphanumeric characters (except hyphen, underscore, plus) with hyphens\n slug = slug.replace(/[^-+_a-zA-Z0-9]/g, \"-\");\n\n // Collapse multiple consecutive hyphens into a single hyphen\n slug = slug.replace(/-+/g, \"-\");\n slug = slug.replace(/-*(_+)-*/g, \"$1\");\n\n slug = normalizeCamelPascalCase(slug);\n\n // Strip leading/trailing hyphens and lowercase\n return slug.replace(/^-+|-+$/g, \"\").toLowerCase();\n}\n\n/**\n * Check if a string contains only valid local file link characters (lowercase, alphanumeric, hyphens, underscores, slashes, dots)\n */\nexport function isValidLinkFormat(link: string): boolean {\n // Allow: lowercase alphanumeric, hyphens, underscores, slashes, dots, hash, question mark\n // For local files, we don't allow & or = (those are query params for external URLs)\n return /^[a-z0-9\\-_/.#?]+$/.test(link);\n}\n\n/**\n * Insert dashes at camelCase transitions for preparation before slugifying.\n * Ported from Python: scripts/organizer/util/__init__.py\n */\nfunction normalizeCamelPascalCase(text: string): string {\n if (text.length < 2) {\n return text;\n }\n\n // Handle PascalCase starting with single uppercase letter\n if (text[0] === text[0].toUpperCase() && text[1] === text[1].toLowerCase()) {\n text = text[0].toLowerCase() + text.slice(1);\n }\n\n const r1 = /([a-z0-9]+)([A-Z]+[a-z0-9]*)/;\n const r2 = /([A-Z]+)([A-Z][a-z])/;\n\n const m1 = text.match(r1);\n const m2 = text.match(r2);\n\n if (!m1 && !m2) {\n return text;\n }\n\n let result = \"\";\n if (m1) {\n const [left, right] = splitBetweenTwoGroups(m1, text);\n const part1 = normalizeCamelPascalCase(left);\n const part2 = normalizeCamelPascalCase(right);\n result = part1 + \"-\" + part2;\n } else if (m2) {\n const [left, right] = splitBetweenTwoGroups(m2, text);\n const part1 = normalizeCamelPascalCase(left);\n const part2 = normalizeCamelPascalCase(right);\n result = part1 + \"-\" + part2;\n }\n\n if (result === \"\") {\n throw new Error(\"Result string should not be empty\");\n }\n\n return result;\n}\n\n/**\n * Split original regex match string into (left, right) at the boundary between 2 adjacent capture groups.\n * Ported from Python: scripts/organizer/util/__init__.py\n */\nfunction splitBetweenTwoGroups(match: RegExpMatchArray, originalString: string): [string, string] {\n if (match.index === undefined) {\n throw new Error(\"Match index is undefined\");\n }\n\n // Find the boundary between the two groups\n const firstGroupLength = match[1].length;\n const boundary = match.index + firstGroupLength;\n\n return [originalString.slice(0, boundary), originalString.slice(boundary)];\n}\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine, isValidLinkFormat, slugify } from \"./utils.js\";\n\n/**\n * Enforce link convention: all lowercase and no offending characters.\n * Suggests slugified version as the proper convention.\n */\nexport const enforceLinkConvention: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Enforce that links are all lowercase and contain only valid characters (no spaces, special chars, etc.)\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n // Match markdown links: [text](link) and [text]: link\n // Use negative lookbehind to avoid matching escaped brackets like \\[text\\]\n const inlineLinksRegex = /(?<!\\\\)\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n const referenceLinkRegex = /^\\s*\\[([^\\]]+)\\]:\\s*([^<\\n]+?)(?:\\s+\"[^\"]*\")?(?:\\s*<!--.*)?$/;\n\n // Check inline links [text](link)\n let inlineMatch;\n while ((inlineMatch = inlineLinksRegex.exec(line)) !== null) {\n const link = inlineMatch[2];\n // Calculate column position: find the opening ( and start from the first char after it\n const fullMatch = inlineMatch[0];\n const openParenIndex = fullMatch.indexOf(\"(\");\n const linkColumn = inlineMatch.index + openParenIndex + 1;\n checkLink(link, i, linkColumn, context);\n }\n\n // Check reference links [text]: link\n const refMatch = line.match(referenceLinkRegex);\n if (refMatch) {\n const link = refMatch[2].trim();\n // Calculate exact column position where the link starts\n const linkPosition = line.indexOf(link);\n checkLink(link, i, linkPosition, context);\n }\n }\n\n function slugifyPath(path: string): string {\n // Split by slash and slugify each component separately to preserve directory structure\n const parts = path.split(\"/\");\n return parts\n .map((part) => {\n // Don't slugify empty parts or special directory references\n if (part === \"\" || part === \".\" || part === \"..\") {\n return part;\n }\n return slugify(part);\n })\n .join(\"/\");\n }\n\n function extractExtension(path: string): { basename: string; extension: string } {\n // Find the last slash to separate directory from filename\n const lastSlashIndex = path.lastIndexOf(\"/\");\n const filename = path.substring(lastSlashIndex + 1);\n\n // Find the last dot in the filename\n const lastDotIndex = filename.lastIndexOf(\".\");\n\n // Only extract extension if dot is not at the start (e.g., not \".gitignore\")\n if (lastDotIndex > 0) {\n const basename = filename.substring(0, lastDotIndex);\n const extension = filename.substring(lastDotIndex);\n const dirPart = lastSlashIndex >= 0 ? path.substring(0, lastSlashIndex + 1) : \"\";\n return {\n basename: dirPart + basename,\n extension: extension,\n };\n }\n\n return { basename: path, extension: \"\" };\n }\n\n function checkLink(\n link: string,\n lineIndex: number,\n columnIndex: number,\n ctx: Rule.RuleContext\n ): void {\n // Skip external links (any valid URL) and anchors\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n new ((globalThis as any).URL)(link);\n return; // Valid external URL, skip it\n } catch {\n // Not a valid URL - could be a relative path or anchor\n if (link.startsWith(\"#\")) {\n return; // It's an anchor, skip it\n }\n }\n\n // Check if link is valid format\n if (!isValidLinkFormat(link)) {\n // Separate basename and extension to preserve the extension\n const { basename, extension } = extractExtension(link);\n const suggestion = slugifyPath(basename) + extension;\n\n // Only flag if the link doesn't match the suggested slug\n if (link !== suggestion) {\n ctx.report({\n loc: {\n start: { line: lineIndex + 1, column: columnIndex },\n end: { line: lineIndex + 1, column: columnIndex + link.length },\n },\n 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}\"`,\n });\n }\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Detect inline math expressions ($...$) that exist alone on their own line.\n * This usually indicates the expression was meant to be display/block math ($$...$$).\n *\n * Bad: $x = 2$\n * Good: $$x = 2$$ OR inline text $x = 2$ more text\n */\nexport const inlineMathAloneOnLine: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n fixable: \"code\",\n docs: {\n description:\n \"Detect inline math expressions that exist alone on their own line (likely meant to be display math)\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Skip empty lines\n if (!trimmed) {\n continue;\n }\n\n // Check for display math ($$) - not what we're looking for\n if (trimmed.includes(\"$$\")) {\n continue;\n }\n\n // Check for inline math ($...$)\n const singleDollarRegex = /^\\$[^$]+\\$\\s*$/;\n const match = trimmed.match(singleDollarRegex);\n\n if (match) {\n // Found inline math alone on a line\n const indent = line.match(/^(\\s*)/)?.[1] ?? \"\";\n const expression = trimmed.slice(1, -1); // Remove the $...$\n\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n \"Inline math expression found alone on its own line. Did you mean to use display math ($$...$$) instead?\",\n fix(fixer) {\n // Convert from $...$ to $$...$$\n const replacement = `${indent}$$${expression}$$`;\n\n const lineStart = sourceCode.getIndexFromLoc({ line: i + 1, column: 1 });\n const lineEnd = sourceCode.getIndexFromLoc({ line: i + 1, column: line.length + 1 });\n\n return fixer.replaceTextRange([lineStart, lineEnd], replacement);\n },\n });\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Limit display math block lengths to 60 characters for mobile view.\n * Long math expressions should be split across multiple lines using \\\\ and & for alignment.\n *\n * Bad: $$ x = y + z + a + b + c + d + e + f + g + h + i + j $$\n * Good: $$\n * x = y + z + a + b \\\\\n * & + c + d + e + f\n * $$\n */\nexport const limitDisplayMathLength: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Limit display math ($$) block lengths for better mobile view\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n charLimit: {\n type: \"number\",\n default: 60,\n description: \"Maximum number of characters per line in display math blocks\",\n },\n },\n additionalProperties: false,\n },\n ],\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n const options = (context.options[0] as { charLimit?: number }) || {};\n const charLimit = options.charLimit ?? 60;\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track which lines we've already processed to avoid duplicate reports\n const processedLines = new Set<number>();\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n // Skip already processed lines\n if (processedLines.has(i)) {\n continue;\n }\n\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Skip if line doesn't contain $$ or is empty\n if (!trimmed.includes(\"$$\")) {\n continue;\n }\n\n // Check for single-line display math: $$ content $$\n const singleLineMatch = trimmed.match(/^(\\s*)\\$\\$(.*?)\\$\\$\\s*$/);\n if (singleLineMatch) {\n const content = singleLineMatch[2];\n if (content.length > charLimit) {\n processedLines.add(i);\n const lineContent = line.trimEnd();\n const lineStart = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: 1,\n });\n const lineEnd = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: lineContent.length + 1,\n });\n context.report({\n loc: { line: i + 1, column: 1 },\n range: [lineStart, lineEnd],\n message: `Display math block is ${content.length} characters, exceeds ${charLimit} character limit. Consider splitting into multiple lines using \\\\\\\\ as line delimiter and & for alignment.`,\n } as any);\n }\n continue;\n }\n\n // Check for multi-line display math: opening $$\n if (trimmed === \"$$\") {\n // Find matching closing $$\n let closingLine = -1;\n const contentLineIndices: number[] = [];\n\n for (let j = i + 1; j < lines.length; j++) {\n if (codeBlockTracker.isLineInFencedCodeBlock(j)) {\n continue;\n }\n\n const currentLine = lines[j];\n const currentTrimmed = currentLine.trim();\n\n if (currentTrimmed === \"$$\") {\n closingLine = j;\n break;\n }\n\n contentLineIndices.push(j);\n }\n\n if (closingLine !== -1) {\n processedLines.add(i);\n processedLines.add(closingLine);\n\n // Check each content line individually for length\n for (const lineIndex of contentLineIndices) {\n const contentLine = lines[lineIndex];\n const contentTrimmed = contentLine.trim();\n const contentLength = contentTrimmed.length;\n\n // Skip empty lines and comment lines\n if (!contentTrimmed || contentTrimmed.startsWith(\"<!--\")) {\n continue;\n }\n\n if (contentLength > charLimit) {\n const contentLineText = lines[lineIndex];\n const contentLineTrimmed = contentLineText.trimEnd();\n const contentLineStart = sourceCode.getIndexFromLoc({\n line: lineIndex + 1,\n column: 1,\n });\n const contentLineEnd = sourceCode.getIndexFromLoc({\n line: lineIndex + 1,\n column: contentLineTrimmed.length + 1,\n });\n context.report({\n loc: { line: lineIndex + 1, column: 1 },\n range: [contentLineStart, contentLineEnd],\n message: `Display math line is ${contentLength} characters, exceeds ${charLimit} character limit. Consider splitting using \\\\\\\\ as line delimiter and & for alignment.`,\n } as any);\n processedLines.add(lineIndex);\n }\n }\n }\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Detect escaped LaTeX delimiters (\\[ and \\]) that exist alone on their own line.\n * When these delimiters appear alone, use $$...$$ (double dollar) display math syntax instead.\n *\n * Bad: \\[\n * Bad: \\]\n * Good: $$x = y$$\n */\nexport const noEscapeLatexDelimiters: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n fixable: \"code\",\n docs: {\n description:\n \"Detect escaped LaTeX delimiters (\\\\[ and \\\\]) that exist alone on their own line\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track which lines we've already processed to avoid duplicate reports\n const processedLines = new Set<number>();\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip already processed lines\n if (processedLines.has(i)) {\n continue;\n }\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Skip empty lines\n if (!trimmed) {\n continue;\n }\n\n // Check for \\[ alone on the line\n if (trimmed === \"\\\\[\") {\n // Find matching \\] delimiter\n let closingDelimiterLine = -1;\n for (let j = i + 1; j < lines.length; j++) {\n if (codeBlockTracker.isLineInFencedCodeBlock(j)) {\n continue;\n }\n const jTrimmed = lines[j].trim();\n if (jTrimmed === \"\\\\]\") {\n closingDelimiterLine = j;\n break;\n }\n }\n\n if (closingDelimiterLine !== -1) {\n processedLines.add(i);\n processedLines.add(closingDelimiterLine);\n\n const openingIndent = line.match(/^(\\s*)/)?.[1] ?? \"\";\n const closingLine = lines[closingDelimiterLine];\n const closingIndent = closingLine.match(/^(\\s*)/)?.[1] ?? \"\";\n\n context.report({\n loc: { line: i + 1, column: 0 },\n message: `LaTeX delimiters \\\\[ and \\\\] found. Use $$...$$ (double dollar) syntax for display math instead.`,\n fix(fixer) {\n const openingReplacement = `${openingIndent}$$`;\n const closingReplacement = `${closingIndent}$$`;\n\n const openingStart = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: 1,\n });\n const openingEnd = sourceCode.getIndexFromLoc({\n line: i + 1,\n column: line.length + 1,\n });\n\n const closingStart = sourceCode.getIndexFromLoc({\n line: closingDelimiterLine + 1,\n column: 1,\n });\n const closingEnd = sourceCode.getIndexFromLoc({\n line: closingDelimiterLine + 1,\n column: closingLine.length + 1,\n });\n\n return [\n fixer.replaceTextRange([openingStart, openingEnd], openingReplacement),\n fixer.replaceTextRange([closingStart, closingEnd], closingReplacement),\n ];\n },\n });\n }\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\nexport const noH1Headers: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Disallow h1 headers in markdown files since the frontmatter title already serves that purpose\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // check for h1 headers\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n if (/^#\\s+/.test(line)) {\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n \"H1 headings are not allowed in Astro markdown files; use the frontmatter title field instead\",\n });\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Check if a line contains a markdown construct that requires blank line after HTML\n */\nfunction isProblematicMarkdownConstruct(line: string): boolean {\n const trimmed = line.trim();\n\n if (!trimmed) return false;\n\n // Headings (# through ###### followed by space)\n if (/^#{1,6}\\s/.test(trimmed)) return true;\n\n // Blockquote (> followed by space)\n if (/^>\\s/.test(trimmed)) return true;\n\n // List (-, *, +) followed by space\n if (/^[-*+]\\s/.test(trimmed)) return true;\n\n // Image\n if (/^!\\[/.test(trimmed)) return true;\n\n // Link (starts with [ and has matching ])\n if (/^\\[/.test(trimmed) && /\\]/.test(trimmed)) return true;\n\n // Inline code (starts with backtick)\n if (/^`/.test(trimmed)) return true;\n\n // Bold/Strong (**, __)\n if (/^\\*\\*/.test(trimmed) || /^__/.test(trimmed)) return true;\n\n // Strikethrough (~~)\n if (/^~~/.test(trimmed)) return true;\n\n // Emphasis (*, _) - but not list markers\n if (/^\\*[^\\s*]/.test(trimmed) || /^_[^\\s_]/.test(trimmed)) return true;\n\n // Table (starts with |)\n if (/^\\|/.test(trimmed)) return true;\n\n return false;\n}\n\n/**\n * Check if a line is an HTML element/tag (but not comments or blank lines)\n */\nfunction isHtmlLine(line: string): boolean {\n const trimmed = line.trim();\n // HTML tags start with < but exclude comments\n if (!trimmed.startsWith(\"<\")) return false;\n if (trimmed.startsWith(\"<!--\")) return false; // Exclude comments\n return true;\n}\n\nexport const requireBlankLineAfterHtml: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Require blank line after HTML blocks when followed by markdown constructs\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track the last HTML line seen\n let lastHtmlLine = -1;\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const line = lines[i];\n const trimmed = line.trim();\n\n // Update last HTML line if this is HTML\n if (isHtmlLine(line)) {\n lastHtmlLine = i;\n continue;\n }\n\n // If this is an empty line, reset tracking (blank line is good separator)\n if (!trimmed) {\n lastHtmlLine = -1;\n continue;\n }\n\n // We have a non-empty, non-HTML line\n // Check if it follows HTML without blank line\n if (lastHtmlLine >= 0 && i === lastHtmlLine + 1) {\n // Direct next line after HTML block\n if (isProblematicMarkdownConstruct(line)) {\n context.report({\n loc: { line: i + 1, column: 0 },\n message: \"Blank line required after HTML block when followed by markdown construct\",\n });\n }\n }\n // Reset tracking - we've seen non-HTML, non-blank content\n lastHtmlLine = -1;\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { FencedCodeBlockTracker, getFrontmatterEndLine } from \"./utils.js\";\n\n/**\n * Check if a line contains display math ($$) that's not properly formatted.\n * Display math is allowed inline with other content.\n * Only enforce the separate line pattern ($$\\n...\\n$$) when the math block is alone on its line.\n *\n * Bad: $$2+2$$ (alone on line)\n * Good: $$\n * 2+2\n * $$\n * Also Good: Some text $$2+2$$ more text (inline is allowed)\n */\nexport const requireDisplayMathFormatting: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n fixable: \"code\",\n docs: {\n description:\n \"Require display math ($$) blocks alone on a line to use the multi-line format ($$\\\\n...\\\\n$$)\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const frontmatterEndLine = getFrontmatterEndLine(text);\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n for (let i = frontmatterEndLine; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n const trimmed = line.trim();\n\n // Remove inline code (backtick-enclosed content) and HTML comments before checking for $$\n let withoutInlineCode = trimmed.replace(/`[^`]*`/g, \"\");\n withoutInlineCode = withoutInlineCode.replace(/<!--.*?-->/g, \"\").trim();\n\n // Check for display math markers\n if (!withoutInlineCode.includes(\"$$\")) {\n continue;\n }\n\n // Count $$ occurrences in the line (excluding those in inline code and comments)\n const dollarCount = (withoutInlineCode.match(/\\$\\$/g) || []).length;\n\n // If there's only one $$, it's an opening or closing delimiter (allowed)\n if (dollarCount === 1) {\n // Single $$ on a line is allowed - it's part of multi-line $$...$$\n continue;\n } else if (dollarCount === 2) {\n // Two $$ on the same line\n // Only enforce separate lines if the ENTIRE line is just the math block\n const doubleRegex = /^(\\s*)\\$\\$(.*?)\\$\\$\\s*$/;\n const match = withoutInlineCode.match(doubleRegex);\n if (match) {\n // The entire line is $$something$$, which requires separate lines\n const indent = match[1];\n const expression = match[2];\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n 'Display math delimiters ($$) must be on separate lines. Use:\\n$$\\nexpression\\n$$',\n fix(fixer) {\n // Build the replacement text - just the three lines without trailing newline\n // The line itself will have its trailing newline preserved by ESLint\n const replacement = `${indent}$$\\n${indent}${expression}\\n${indent}$$`;\n\n // Get the start of the line and the end (before the newline)\n const lineStart = sourceCode.getIndexFromLoc({ line: i + 1, column: 1 });\n const lineEnd = sourceCode.getIndexFromLoc({ line: i + 1, column: line.length + 1 });\n\n return fixer.replaceTextRange([lineStart, lineEnd], replacement);\n },\n });\n }\n // If match is null, the $$ is inline with other content, which is allowed - skip\n } else if (dollarCount > 2) {\n // Multiple $$ pairs on the same line - likely multiple math expressions or malformed\n context.report({\n loc: { line: i + 1, column: 0 },\n message:\n \"Multiple display math expressions ($$) found on same line. Each should be on separate lines\",\n });\n }\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { extractFrontmatter } from \"./utils.js\";\n\nexport const requireFrontmatter: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Require title field in frontmatter\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const frontmatter = extractFrontmatter(text);\n\n if (frontmatter) {\n if (!/^\\s*title\\s*:/m.test(frontmatter)) {\n context.report({\n loc: { line: 1, column: 0 },\n message: \"Missing required 'title' field in frontmatter\",\n });\n }\n } else {\n context.report({\n loc: { line: 1, column: 0 },\n message: \"Missing frontmatter\",\n });\n }\n },\n };\n },\n};\n","import type { Rule } from \"eslint\";\n\nimport { removeEscapedDelimiters } from \"./utils.js\";\nimport { FencedCodeBlockTracker } from \"./utils.js\";\n\n/**\n * Validate that LaTeX delimiters ($...$) and ($$...$$) are balanced.\n * Catches broken math rendering from mismatched or unclosed delimiters.\n * Works with the original source text to properly handle escaped sequences.\n */\nexport const validateLatexDelimiters: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Validate that LaTeX delimiters ($...$ and $$...$$) are balanced and properly paired\",\n },\n } as const,\n create(context: Rule.RuleContext): Rule.RuleListener {\n let alreadyProcessed = false;\n\n return {\n \"*\": (node: Rule.Node) => {\n if (alreadyProcessed || (node as unknown as { type: string }).type !== \"root\") return;\n\n alreadyProcessed = true;\n\n const sourceCode = context.sourceCode;\n if (!sourceCode) return;\n\n const text = sourceCode.getText();\n const lines = text.split(\"\\n\");\n const codeBlockTracker = new FencedCodeBlockTracker(text);\n\n // Track unclosed delimiters\n let inlineDelimiterCount = 0;\n let displayDelimiterCount = 0;\n let inlineStartLine = -1;\n let displayStartLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip lines inside fenced code blocks\n if (codeBlockTracker.isLineInFencedCodeBlock(i)) {\n continue;\n }\n\n // Remove inline code blocks (backticks) before counting\n const withoutInlineCode = line.replace(/`[^`]*`/g, \"\");\n\n // Remove HTML comments before counting\n const withoutComments = withoutInlineCode.replace(/<!--[\\s\\S]*?-->/g, \"\");\n\n // Remove escaped dollar signs before counting\n const withoutEscaped = removeEscapedDelimiters(withoutComments);\n\n // Count $$ first (to avoid counting them as two $)\n const displayMatches = withoutEscaped.match(/\\$\\$/g);\n if (displayMatches) {\n displayDelimiterCount += displayMatches.length;\n if (displayDelimiterCount % 2 === 1 && displayStartLine === -1) {\n displayStartLine = i;\n } else if (displayDelimiterCount % 2 === 0) {\n displayStartLine = -1;\n }\n }\n\n // Count remaining $ (after removing $$)\n const withoutDisplay = withoutEscaped.replace(/\\$\\$/g, \"\");\n const inlineMatches = withoutDisplay.match(/\\$/g);\n if (inlineMatches) {\n inlineDelimiterCount += inlineMatches.length;\n if (inlineDelimiterCount % 2 === 1 && inlineStartLine === -1) {\n inlineStartLine = i;\n } else if (inlineDelimiterCount % 2 === 0) {\n inlineStartLine = -1;\n }\n }\n }\n\n // Report unclosed display math\n if (displayDelimiterCount % 2 !== 0) {\n context.report({\n loc: { line: displayStartLine + 1, column: 0 },\n message: `Unclosed display math delimiter ($$). Expected closing $$ to match the opening on line ${displayStartLine + 1}.`,\n });\n }\n\n // Report unclosed inline math\n if (inlineDelimiterCount % 2 !== 0) {\n context.report({\n loc: { line: inlineStartLine + 1, column: 0 },\n message: `Unclosed inline math delimiter ($). Expected closing $ to match the opening on line ${inlineStartLine + 1}.`,\n });\n }\n },\n };\n },\n};\n","import { enforceFrontmatterSchema } from \"./plugins/markdown/enforce-frontmatter-schema.js\";\nimport { enforceLinkConvention } from \"./plugins/markdown/enforce-link-convention.js\";\nimport { inlineMathAloneOnLine } from \"./plugins/markdown/inline-math-alone-on-line.js\";\nimport { limitDisplayMathLength } from \"./plugins/markdown/limit-display-math-length.js\";\nimport { noEscapeLatexDelimiters } from \"./plugins/markdown/no-escape-latex-delimiters.js\";\nimport { noH1Headers } from \"./plugins/markdown/no-h1-headers.js\";\nimport { requireBlankLineAfterHtml } from \"./plugins/markdown/require-blank-line-after-html.js\";\nimport { requireDisplayMathFormatting } from \"./plugins/markdown/require-display-math-formatting.js\";\nimport { requireFrontmatter } from \"./plugins/markdown/require-frontmatter.js\";\nimport { validateLatexDelimiters } from \"./plugins/markdown/validate-latex-delimiters.js\";\n\nexport default {\n rules: {\n \"enforce-frontmatter-schema\": enforceFrontmatterSchema,\n \"require-frontmatter\": requireFrontmatter,\n \"no-h1-headers\": noH1Headers,\n \"require-blank-line-after-html\": requireBlankLineAfterHtml,\n \"require-display-math-formatting\": requireDisplayMathFormatting,\n \"inline-math-alone-on-line\": inlineMathAloneOnLine,\n \"validate-latex-delimiters\": validateLatexDelimiters,\n \"no-escape-latex-delimiters\": noEscapeLatexDelimiters,\n \"enforce-link-convention\": enforceLinkConvention,\n \"limit-display-math-length\": limitDisplayMathLength,\n },\n};\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-cannoli-plugins",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@eslint/js": "^10.0.1",
|
|
37
37
|
"@eslint/markdown": "^7.5.1",
|
|
38
38
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
39
|
+
"@types/js-yaml": "^4.0.9",
|
|
39
40
|
"eslint": "^10.0.3",
|
|
40
41
|
"globals": "^17.4.0",
|
|
41
42
|
"jiti": "^2.6.1",
|
|
@@ -47,5 +48,9 @@
|
|
|
47
48
|
"peerDependencies": {
|
|
48
49
|
"@eslint/markdown": "^7.0.0",
|
|
49
50
|
"eslint": "^10.0.3"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"js-yaml": "^4.1.1",
|
|
54
|
+
"minimatch": "^10.2.5"
|
|
50
55
|
}
|
|
51
56
|
}
|