From 81e826497d51b664e2666e3a16572d432d1ed946 Mon Sep 17 00:00:00 2001 From: rasamhossain Date: Wed, 5 Nov 2025 14:00:04 -0500 Subject: [PATCH] Docs formatting checks using markdown linting - Charts --- .../.markdownlint/.markdownlint-cli2.yaml | 14 ++- .../.markdownlint/rules/no-four-asterisks.js | 111 ++++++++++++++++++ .../rules/no-fullwidth-asterisks.js | 86 ++++++++++++++ 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 doc-locale/.markdownlint/rules/no-four-asterisks.js create mode 100644 doc-locale/.markdownlint/rules/no-fullwidth-asterisks.js diff --git a/doc-locale/.markdownlint/.markdownlint-cli2.yaml b/doc-locale/.markdownlint/.markdownlint-cli2.yaml index 2f2bf65d93..86dd8d93f9 100644 --- a/doc-locale/.markdownlint/.markdownlint-cli2.yaml +++ b/doc-locale/.markdownlint/.markdownlint-cli2.yaml @@ -4,6 +4,10 @@ # # markdownlint-cli2 --config .markdownlint/.markdownlint-cli2.yaml '**/*.md' # See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for explanations of each rule +customRules: + - rules/no-four-asterisks.js + - rules/no-fullwidth-asterisks.js + config: # First, set the default default: true @@ -30,4 +34,12 @@ config: ul-style: # MD004 style: "dash" link-fragments: false # MD051 -fix: false + fix: false + no-space-in-emphasis: true # MD037 + + # CUSTOM JAPANESE RULES (warnings only) + MD-JA001: # Four consecutive asterisks (****) + severity: "warning" + + MD-JA002: # Fullwidth asterisks (* should be *) + severity: "warning" diff --git a/doc-locale/.markdownlint/rules/no-four-asterisks.js b/doc-locale/.markdownlint/rules/no-four-asterisks.js new file mode 100644 index 0000000000..88f293feb1 --- /dev/null +++ b/doc-locale/.markdownlint/rules/no-four-asterisks.js @@ -0,0 +1,111 @@ +// MD-JA001: Detects four consecutive asterisks (****) +// Pattern like ****text(unit)**** + +module.exports = { + names: ["MD-JA001", "no-four-asterisks"], + description: "Four consecutive asterisks (****) - likely a formatting error", + tags: ["formatting", "emphasis"], + + function: function rule(params, onError) { + const { lines, tokens } = params; + + // Build a map of lines that are in code blocks or inline code + const codeLines = new Set(); + const inlineCodeRanges = []; + + tokens.forEach(token => { + // Track fenced code blocks + if (token.type === 'fence' || token.type === 'code_block') { + const startLine = token.map[0]; + const endLine = token.map[1]; + for (let i = startLine; i < endLine; i++) { + codeLines.add(i); + } + } + + // Track inline code + if (token.type === 'inline' && token.children) { + token.children.forEach(child => { + if (child.type === 'code_inline') { + inlineCodeRanges.push({ + line: child.lineNumber - 1, // Convert to 0-indexed + content: child.content + }); + } + }); + } + }); + + lines.forEach((line, lineIndex) => { + // Skip if entire line is in a code block + if (codeLines.has(lineIndex)) { + return; + } + + // Find all occurrences of four or more asterisks + const fourAsteriskPattern = /\*{4,}/g; + let match; + + while ((match = fourAsteriskPattern.exec(line)) !== null) { + // Check if this match is inside inline code + const matchStart = match.index; + const matchEnd = match.index + match[0].length; + + const isInInlineCode = inlineCodeRanges.some(range => { + if (range.line !== lineIndex) return false; + + // Check if the asterisks appear in this inline code section + // Find all backtick pairs and check if match falls within any of them + const backtickRegex = /`([^`]+)`/g; + let codeMatch; + while ((codeMatch = backtickRegex.exec(line)) !== null) { + if (codeMatch[1] === range.content) { + const codeStart = codeMatch.index; + const codeEnd = codeMatch.index + codeMatch[0].length; + if (matchStart >= codeStart && matchEnd <= codeEnd) { + return true; + } + } + } + return false; + }); + + if (isInInlineCode) { + continue; // Skip if in inline code + } + + const asteriskCount = match[0].length; + const column = match.index + 1; + + let fixInfo = null; + let detail = ""; + + if (asteriskCount === 4) { + const before = line.substring(0, match.index); + const after = line.substring(match.index + 4); + + if (before.match(/[^\s*]$/) && after.match(/^[^\s*]/)) { + fixInfo = { + editColumn: column, + deleteCount: 4, + insertText: "** **" + }; + detail = "Should have space between closing and opening bold markers"; + } else { + detail = "Should be ** to close/open bold sections properly"; + } + } else { + detail = `Found ${asteriskCount} consecutive asterisks - check formatting`; + } + + onError({ + lineNumber: lineIndex + 1, + detail: detail, + context: line.trim(), + range: [column, asteriskCount], + fixInfo: fixInfo + }); + } + }); + } +}; diff --git a/doc-locale/.markdownlint/rules/no-fullwidth-asterisks.js b/doc-locale/.markdownlint/rules/no-fullwidth-asterisks.js new file mode 100644 index 0000000000..549aeca19e --- /dev/null +++ b/doc-locale/.markdownlint/rules/no-fullwidth-asterisks.js @@ -0,0 +1,86 @@ +// MD-JA002: Detect fullwidth asterisks and suggest halfwidth replacement +// Japanese input methods sometimes produce fullwidth * instead of * + +module.exports = { + names: ["MD-JA002", "no-fullwidth-asterisks"], + description: "Fullwidth asterisks (*) should be halfwidth (*)", + tags: ["formatting", "japanese"], + + function: function rule(params, onError) { + const { lines, tokens } = params; + + // Build a map of lines in code blocks + const codeLines = new Set(); + const inlineCodeRanges = []; + + tokens.forEach(token => { + if (token.type === 'fence' || token.type === 'code_block') { + const startLine = token.map[0]; + const endLine = token.map[1]; + for (let i = startLine; i < endLine; i++) { + codeLines.add(i); + } + } + + if (token.type === 'inline' && token.children) { + token.children.forEach(child => { + if (child.type === 'code_inline') { + inlineCodeRanges.push({ + line: child.lineNumber - 1, + content: child.content + }); + } + }); + } + }); + + lines.forEach((line, lineIndex) => { + // Skip code blocks + if (codeLines.has(lineIndex)) { + return; + } + + const fullwidthPattern = /*/g; + let match; + + while ((match = fullwidthPattern.exec(line)) !== null) { + // Check if in inline code + const matchPos = match.index; + const isInInlineCode = inlineCodeRanges.some(range => { + if (range.line !== lineIndex) return false; + // Find all backtick pairs and check if match falls within any of them + const backtickRegex = /`([^`]+)`/g; + let codeMatch; + while ((codeMatch = backtickRegex.exec(line)) !== null) { + if (codeMatch[1] === range.content) { + const codeStart = codeMatch.index; + const codeEnd = codeMatch.index + codeMatch[0].length; + if (matchPos >= codeStart && matchPos <= codeEnd) { + return true; + } + } + } + return false; + }); + + if (isInInlineCode) { + continue; + } + + const column = match.index + 1; + + onError({ + lineNumber: lineIndex + 1, + detail: "Fullwidth asterisk (*) should be halfwidth (*)", + context: line.trim(), + range: [column, 1], + fixInfo: { + editColumn: column, + deleteCount: 1, + insertText: "*" + } + }); + } + }); + } +}; -- GitLab