diff --git a/support/src/parse.ts b/support/src/parse.ts index 4dbb9940..f1c3fb0a 100644 --- a/support/src/parse.ts +++ b/support/src/parse.ts @@ -1,6 +1,11 @@ import mit from "markdown-it"; import type { Token } from "markdown-it/index.js"; -import { sourceLocationOf, type SourceLocation } from "./sourceLocation.js"; +import { + markUpContent, + sourceLocationOf, + unMarkUpContent, + type SourceLocation, +} from "./sourceLocation.js"; export type ParsedLink = { readonly target: string; @@ -14,14 +19,22 @@ export type ParsedImage = { readonly sourceLocation: SourceLocation | null; }; +export type DeprecatedTerm = { + readonly term: string; + readonly word: string; + readonly sourceLocation: SourceLocation; +}; + export type ParseResult = { readonly links: readonly ParsedLink[]; readonly images: readonly ParsedImage[]; readonly trailingWhitespace: readonly SourceLocation[]; + readonly deprecatedTerm: readonly DeprecatedTerm[]; }; export const parse = (content: string): ParseResult => { const trailingWhitespace: SourceLocation[] = []; + const deprecatedTerm: DeprecatedTerm[] = []; content.split(/\n/).forEach((line, index) => { if (line.endsWith(" ")) { @@ -33,7 +46,7 @@ export const parse = (content: string): ParseResult => { }); const parser = mit(); - const tokens = parser.parse(content, {}); + const tokens = parser.parse(markUpContent(content), {}); const parsedLinks: ParsedLink[] = []; const parsedImages: ParsedImage[] = []; @@ -46,27 +59,45 @@ export const parse = (content: string): ParseResult => { ); if (indexOfNextClose > index) { - const target = token.attrGet("href") as string; + const markedUpTarget = token.attrGet("href") as string; parsedLinks.push({ - target, + target: unMarkUpContent(markedUpTarget), content: tokens .slice(index + 1, indexOfNextClose) - .map((t) => t.content) + .map((t) => unMarkUpContent(t.content)) .join(""), - sourceLocation: sourceLocationOf(`(${target})`, content), + sourceLocation: sourceLocationOf(markedUpTarget), }); } } if (token.type === "image") { - const src = token.attrGet("src") as string; + const markedUpSrc = token.attrGet("src") as string; parsedImages.push({ - src, - alt: token.content, - sourceLocation: sourceLocationOf(`(${src})`, content), + src: unMarkUpContent(markedUpSrc), + alt: unMarkUpContent(token.content), + sourceLocation: sourceLocationOf(markedUpSrc), }); } + if (token.type === "text") { + // console.dir(token, { depth: 2 }); + for (const match of token.content.matchAll( + /L(?\d+)C(?\d+)T(?[\p{Alpha}\p{Number}]*(?homework|student|teacher|bootcamp|classroom|class)[\p{Alpha}\p{Number}]*)/giu, + )) { + // console.dir(match, { depth: 1 }); + match.groups && + deprecatedTerm.push({ + term: match.groups.term.toLowerCase(), + word: match.groups.word, + sourceLocation: { + line0: Number(match.groups.line0), + column0: Number(match.groups.column0), + }, + }); + } + } + if (token.children) scan(token.children); }); }; @@ -77,5 +108,6 @@ export const parse = (content: string): ParseResult => { links: parsedLinks, images: parsedImages, trailingWhitespace, + deprecatedTerm, }; }; diff --git a/support/src/sourceLocation.ts b/support/src/sourceLocation.ts index 9eecdbb9..ca615c05 100644 --- a/support/src/sourceLocation.ts +++ b/support/src/sourceLocation.ts @@ -3,20 +3,38 @@ export type SourceLocation = { readonly column0: number; }; +export const markUpContent = (content: string): string => { + return content + .split(/\n/) + .map((lineText, line0) => + lineText.replaceAll( + /(? + /^\d+$/.test(matchedText) + ? matchedText + : `L${line0}C${matchedIndex}T${matchedText}`, + ), + ) + .join("\n"); +}; + +export const unMarkUpContent = (s: string) => + s.replaceAll( + /(? t, + ); + export const sourceLocationOf = ( - needle: string, - haystack: string, + markedUpText: string, ): SourceLocation | null => { - const index = haystack.indexOf(needle); - if (index < 0) return null; + const m = markedUpText.match(/L(?\d+)C(?\d+)T/); - const lines = haystack.substring(0, index).split("\n"); - const lastLine = lines.pop(); - const line0 = lines.length; - const column0 = lastLine!.length; + if (m?.groups) { + return { + line0: Number(m.groups.line0), + column0: Number(m.groups.column0) - (m.index ?? 0), + }; + } - return { - line0, - column0, - }; + return null; }; diff --git a/support/src/validateLinks.ts b/support/src/validateLinks.ts index b65a699d..d5005310 100644 --- a/support/src/validateLinks.ts +++ b/support/src/validateLinks.ts @@ -77,6 +77,16 @@ const main = async () => { ++errors; } + for (const found of parsedFile.deprecatedTerm) { + showError( + parsedFile.filename, + found.sourceLocation, + "VL004/deprecated-term", + `Deprecated term '${found.term}'`, + ); + if (found.term !== "class") ++errors; + } + for (const img of parsedFile.images) { if (!isExternalLink(img.src)) { const resolved = path.join(dirname(parsedFile.filename), img.src);