Rewrite docs linter
1. I want to set the stage to deal with #2898 properly. 2. `request` was deprecated years ago. Decided that it's better to just move to native Node.js APIs in its place. 3. `glob` was outdated, and it's easier to just toss it than to upgrade across a major version. 4. I switched to using Marked's "lexer" directly so I'm not fussing with the complexity of renderers. This of course necessitated a more complex file processor as its "lexer" is really an AST parser. I also decided to go a few steps further: - Drop the cache to simplify everything. I might reverse this later, but just caching URLs per-page should be enough to prevent the world from crashing down. - Drop some more dependencies, so I don't have to come back to this later nearly as quickly. - Upgrade to a more modern language version in the scripts. - Update Marked. It was super outdated. - Add line and column numbers to the warnings. That took quite a bit of work, thanks to a missing Marked feature plus a bug in Marked.
This commit is contained in:
parent
3a633ce99c
commit
0d095d1373
15 changed files with 1201 additions and 1154 deletions
145
scripts/_lint-docs/lint-code.js
Normal file
145
scripts/_lint-docs/lint-code.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
"use strict"
|
||||
|
||||
// Accept just about anything by using Babel's parser.
|
||||
|
||||
const babelParser = require("@babel/parser")
|
||||
|
||||
function getJsonError(code) {
|
||||
try {
|
||||
JSON.parse(code)
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns `undefined` or an error */
|
||||
function getBabelError(code, asTypeScript) {
|
||||
// Could be within any production.
|
||||
/** @type {babelParser.ParserPlugin[]} */
|
||||
const plugins = [
|
||||
"bigInt",
|
||||
"asyncGenerators",
|
||||
"classPrivateMethods",
|
||||
"classPrivateProperties",
|
||||
"classProperties",
|
||||
"dynamicImport",
|
||||
"logicalAssignment",
|
||||
"nullishCoalescingOperator",
|
||||
"numericSeparator",
|
||||
"objectRestSpread",
|
||||
"optionalCatchBinding",
|
||||
"optionalChaining",
|
||||
"topLevelAwait",
|
||||
"jsx",
|
||||
]
|
||||
|
||||
if (asTypeScript) {
|
||||
plugins.push("typescript")
|
||||
}
|
||||
|
||||
try {
|
||||
babelParser.parse(code, {
|
||||
sourceType: "unambiguous",
|
||||
allowReturnOutsideFunction: true,
|
||||
allowAwaitOutsideFunction: true,
|
||||
allowSuperOutsideMethod: true,
|
||||
allowUndeclaredExports: true,
|
||||
plugins,
|
||||
})
|
||||
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef LangEntry
|
||||
* @property {string} name
|
||||
* @property {undefined | RegExp} unspacedComment
|
||||
* @property {(code: string) => undefined | Error} getError
|
||||
*/
|
||||
|
||||
/** @type {Map<string, string | LangEntry>} */
|
||||
const recognizedLangTags = new Map([
|
||||
["js", {
|
||||
name: "JavaScript",
|
||||
unspacedComment: /(^|\s)\/\/\S/g,
|
||||
getError: (code) => getBabelError(code, false),
|
||||
}],
|
||||
["ts", {
|
||||
name: "TypeScript",
|
||||
unspacedComment: /(^|\s)\/\/\S/g,
|
||||
getError: (code) => getBabelError(code, true),
|
||||
}],
|
||||
["json", {
|
||||
name: "JSON",
|
||||
unspacedComment: undefined,
|
||||
getError: getJsonError,
|
||||
}],
|
||||
["javascript", "js"],
|
||||
["typescript", "ts"],
|
||||
])
|
||||
|
||||
/**
|
||||
* @param {undefined | string} lang
|
||||
* @returns {undefined | LangEntry}
|
||||
*/
|
||||
function lookupLang(lang) {
|
||||
while (typeof lang === "string") {
|
||||
lang = recognizedLangTags.get(lang)
|
||||
}
|
||||
return lang
|
||||
}
|
||||
|
||||
function lintCodeIsHighlightable(codeErrors, lang) {
|
||||
// We only care about what's not tagged here.
|
||||
if (lang === "") {
|
||||
// TODO: ensure all code blocks have tags, and check this in CI.
|
||||
const langTags = []
|
||||
|
||||
for (const [tag, getError] of recognizedLangTags) {
|
||||
if (typeof getError === "function" && !getError(tag)) {
|
||||
langTags.push(tag)
|
||||
}
|
||||
}
|
||||
|
||||
if (langTags.length === 1) {
|
||||
codeErrors.push(`Code block possibly missing \`${langTags[0]}\` language tag.`)
|
||||
} else if (langTags.length !== 0) {
|
||||
codeErrors.push([
|
||||
"Code block possibly missing a language tag. Possible tags that could apply:",
|
||||
...langTags.map((tag) => `- ${tag}`),
|
||||
].join("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lintCodeIsSyntaticallyValid(codeErrors, langEntry, error) {
|
||||
if (error) {
|
||||
codeErrors.push(`${langEntry.name} code block has invalid syntax: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
function lintCodeCommentStyle(codeErrors, langEntry, code) {
|
||||
if (langEntry?.unspacedComment?.test(code)) {
|
||||
codeErrors.push("Comment is missing a preceding space.")
|
||||
}
|
||||
}
|
||||
|
||||
function getCodeLintErrors(code, lang) {
|
||||
const langEntry = lookupLang(lang)
|
||||
const error = langEntry?.getError(code)
|
||||
const codeErrors = []
|
||||
|
||||
lintCodeIsHighlightable(codeErrors, lang)
|
||||
lintCodeIsSyntaticallyValid(codeErrors, langEntry, error)
|
||||
lintCodeCommentStyle(codeErrors, langEntry, code)
|
||||
|
||||
return codeErrors
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCodeLintErrors,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue