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.
162 lines
3.3 KiB
JavaScript
162 lines
3.3 KiB
JavaScript
"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,
|
|
}
|