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
162
scripts/_lint-docs/do-lint.js
Normal file
162
scripts/_lint-docs/do-lint.js
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"use strict"
|
||||
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
const {submitTask} = require("./task-queue.js")
|
||||
const {processOne} = require("./process-file.js")
|
||||
const {root, rel, p, warnError} = require("../_utils.js")
|
||||
|
||||
const doNotVisit = /[\\/]node_modules(?:$|[\\/])|[\\/]docs[\\/](?:changelog|recent-changes|migration-[^\\/]*)\.md$/
|
||||
|
||||
function lintOne(file, callback) {
|
||||
let warnings = 0
|
||||
let errors = 0
|
||||
let files = 0
|
||||
|
||||
let pending = 1
|
||||
|
||||
function settle() {
|
||||
if (--pending === 0) {
|
||||
callback(warnings, errors, files)
|
||||
}
|
||||
}
|
||||
|
||||
function visitNext(file, contents) {
|
||||
if (contents !== undefined) {
|
||||
files++
|
||||
pending++
|
||||
processOne(file, contents, (w, e) => {
|
||||
warnings += w
|
||||
errors += e
|
||||
settle()
|
||||
})
|
||||
} else {
|
||||
pending++
|
||||
submitTask(fs.readdir.bind(null, file), (err, files) => {
|
||||
if (err) {
|
||||
if (err.code !== "ENOTDIR") {
|
||||
warnError(err)
|
||||
}
|
||||
} else {
|
||||
for (const child of files) {
|
||||
const joined = path.join(file, child)
|
||||
if (!doNotVisit.test(joined)) {
|
||||
visit(joined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function visit(file) {
|
||||
if (file.endsWith(".md")) {
|
||||
pending++
|
||||
submitTask(fs.readFile.bind(null, file, "utf8"), (err, contents) => {
|
||||
// Not found is fine. Just ignore it.
|
||||
if (!err || err.code === "EISDIR") {
|
||||
visitNext(file, err ? undefined : contents)
|
||||
} else if (err.code !== "ENOENT") {
|
||||
warnError(err)
|
||||
}
|
||||
settle()
|
||||
})
|
||||
} else {
|
||||
visitNext(file, undefined)
|
||||
}
|
||||
}
|
||||
|
||||
visit(file)
|
||||
}
|
||||
|
||||
function lintAll() {
|
||||
lintOne(root, (warnings, errors, files) => {
|
||||
let problems = ""
|
||||
|
||||
if (errors !== 0) {
|
||||
process.exitCode = 1
|
||||
problems = `${problems}\n${errors} error${errors === 1 ? "" : "s"}`
|
||||
}
|
||||
|
||||
if (warnings !== 0) {
|
||||
problems = `${problems}\n${warnings} warning${warnings === 1 ? "" : "s"}`
|
||||
}
|
||||
|
||||
if (problems !== "") {
|
||||
console.error(`${problems} found in the docs\n`)
|
||||
console.error(`Scanned ${files} file${files === 1 ? "" : "s"}\n`)
|
||||
} else {
|
||||
console.log("The docs are in good shape!\n")
|
||||
console.log(`Scanned ${files} file${files === 1 ? "" : "s"}\n`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function lintFile(file, callback) {
|
||||
if (doNotVisit.test(file)) {
|
||||
if (typeof callback === "function") process.nextTick(callback)
|
||||
} else {
|
||||
lintOne(file, (warnings, errors) => {
|
||||
const relativePath = rel(file)
|
||||
|
||||
let problems = ""
|
||||
|
||||
if (errors !== 0) {
|
||||
problems = `${problems}\n${errors} error${errors === 1 ? "" : "s"}`
|
||||
}
|
||||
|
||||
if (warnings !== 0) {
|
||||
problems = `${problems}\n${warnings} warning${warnings === 1 ? "" : "s"}`
|
||||
}
|
||||
|
||||
if (problems !== "") {
|
||||
console.error(`${problems} found in ${relativePath}\n`)
|
||||
}
|
||||
|
||||
callback?.()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function lintWatch() {
|
||||
const timers = new Map()
|
||||
|
||||
const handleFileInner = (filename) => {
|
||||
timers.delete(filename)
|
||||
lintFile(p(filename))
|
||||
}
|
||||
|
||||
const handleFile = (filename) => {
|
||||
const timer = timers.get(filename)
|
||||
if (timer !== undefined) {
|
||||
timer.refresh()
|
||||
} else {
|
||||
timers.set(filename, setTimeout(handleFileInner, 400, filename))
|
||||
}
|
||||
}
|
||||
|
||||
let fileBuffer = []
|
||||
|
||||
lintFile(root, () => {
|
||||
for (const file of fileBuffer) {
|
||||
handleFile(file)
|
||||
}
|
||||
fileBuffer = undefined
|
||||
})
|
||||
|
||||
fs.watch(root, {recursive: true}, (_, filename) => {
|
||||
if (fileBuffer !== undefined) {
|
||||
fileBuffer.push(filename)
|
||||
} else {
|
||||
handleFile(filename)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
lintAll,
|
||||
lintWatch,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue