UNPKG

cspell

Version:

A Spelling Checker for Code!

1,449 lines (1,427 loc) 112 kB
import { createRequire } from "node:module"; import { isAsyncIterable, opFilter, opMap, opTap, operators, pipeAsync, pipeAsync as asyncPipe, toAsyncIterable, toAsyncIterable as mergeAsyncIterables } from "@cspell/cspell-pipe"; import * as cspell from "cspell-lib"; import { ENV_CSPELL_GLOB_ROOT, IncludeExcludeFlag, MessageTypes, SuggestionError, Text, checkTextDocument, combineTextAndLanguageSettings, createPerfTimer, extractDependencies, extractImportErrors, fileToDocument, getDefaultSettings, getDictionary, getGlobalSettingsAsync, getSystemFeatureFlags, isBinaryFile, isSpellingDictionaryLoadError, mergeSettings, setLogger, shouldCheckDocument, spellCheckDocument, suggestionsForWords, traceWordsAsync } from "cspell-lib"; import assert from "node:assert"; import { format, formatWithOptions, stripVTControlCharacters } from "node:util"; import { isUrlLike, toFileDirURL, toFilePathOrHref, toFileURL, urlRelative } from "@cspell/url"; import chalk, { Chalk } from "chalk"; import { makeTemplate } from "chalk-template"; import ansiRegex from "ansi-regex"; import fs, { stat } from "node:fs/promises"; import { MutableCSpellConfigFile, createReaderWriter, cspellConfigFileSchema, isCfgArrayNode } from "cspell-config-lib"; import { promises } from "node:fs"; import { fileURLToPath } from "node:url"; import * as path$1 from "node:path"; import path, { isAbsolute, posix, relative, resolve, sep } from "node:path"; import { opMap as opMap$1, pipe } from "@cspell/cspell-pipe/sync"; import { IssueType, MessageTypes as MessageTypes$1, unknownWordsChoices } from "@cspell/cspell-types"; import { dictionaryCacheEnableLogging, dictionaryCacheGetLog } from "cspell-dictionary"; import { GitIgnore, findRepoRoot } from "cspell-gitignore"; import { GlobMatcher, fileOrGlobToGlob, workaroundPicomatchBug } from "cspell-glob"; import crypto from "node:crypto"; import streamConsumers from "node:stream/consumers"; import { getStat, readFileText, toURL } from "cspell-io"; import { glob } from "tinyglobby"; import * as readline from "node:readline"; import { parse, stringify } from "flatted"; import { dynamicImport } from "@cspell/dynamic-import"; //#region src/console.ts var ImplChannel = class { constructor(stream) { this.stream = stream; } write = (msg) => this.stream.write(msg); writeLine = (msg) => this.write(msg + "\n"); clearLine = (dir, callback) => this.stream.clearLine?.(dir, callback) ?? false; printLine = (...params) => this.writeLine(params.length && formatWithOptions({ colors: this.stream.hasColors?.() }, ...params) || ""); getColorLevel = () => getColorLevel(this.stream); }; var Console = class { stderrChannel; stdoutChannel; constructor(stdout = process.stdout, stderr = process.stderr) { this.stdout = stdout; this.stderr = stderr; this.stderrChannel = new ImplChannel(this.stderr); this.stdoutChannel = new ImplChannel(this.stdout); } log = (...p) => this.stdoutChannel.printLine(...p); error = (...p) => this.stderrChannel.printLine(...p); info = this.log; warn = this.error; }; const console = new Console(); function getColorLevel(stream) { switch (stream.getColorDepth?.() || 0) { case 1: return 1; case 4: return 2; case 24: return 3; default: return 0; } } //#endregion //#region src/util/errors.ts var CheckFailed = class extends Error { constructor(message, exitCode = 1) { super(message); this.exitCode = exitCode; } }; var ApplicationError = class extends Error { constructor(message, exitCode = 1, cause) { super(message); this.exitCode = exitCode; this.cause = cause; } }; var IOError = class extends ApplicationError { constructor(message, cause) { super(message, void 0, cause); this.cause = cause; } get code() { return this.cause.code; } isNotFound() { return this.cause.code === "ENOENT"; } }; function toError$1(e) { if (isError(e)) return e; if (isErrorLike(e)) { const ex = new Error(e.message, { cause: e }); if (e.code !== void 0) ex.code = e.code; return ex; } const message = format(e); return new Error(message); } function isError(e) { return e instanceof Error; } function isErrorLike(e) { if (e instanceof Error) return true; if (!e || typeof e !== "object") return false; return typeof e.message === "string"; } function toApplicationError(e, message) { if (e instanceof ApplicationError && !message) return e; const err = toError$1(e); return new ApplicationError(message ?? err.message, void 0, err); } //#endregion //#region src/util/perfMeasurements.ts function getPerfMeasurements() { const measurements = performance.getEntriesByType("measure"); const root = { depth: -1, totalTimeMs: 0, nestedTimeMs: 0, children: /* @__PURE__ */ new Map() }; if (!measurements.length) return []; const stack = []; let depth = 0; for (let i = 0; i < measurements.length; i++) { const m = measurements[i]; rollUpStack(m.startTime); const s = { m, p: addToParent(depth === 0 ? root : stack[depth - 1].p, m) }; stack[depth++] = s; } sortChildren(root); return [...root.children.values()].flatMap((r) => [...flattenChildren(r)]); function contains(m, t) { const stop = m.startTime + m.duration; return t >= m.startTime && t < stop; } function rollUpStack(t) { for (; depth > 0 && !contains(stack[depth - 1].m, t); --depth); } function addToParent(p, m) { p.children ??= /* @__PURE__ */ new Map(); p.nestedTimeMs += m.duration; return updateChild(p.children, m, p.depth + 1); } function updateChild(children, m, depth$1) { const p = children.get(m.name); if (p) { p.totalTimeMs += m.duration; p.count += 1; p.minTimeMs = Math.min(p.minTimeMs, m.duration); p.maxTimeMs = Math.max(p.maxTimeMs, m.duration); return p; } const n = { name: m.name, depth: depth$1, totalTimeMs: m.duration, nestedTimeMs: 0, count: 1, minTimeMs: m.duration, maxTimeMs: m.duration }; children.set(m.name, n); return n; } function* flattenChildren(m) { yield m; if (!m.children) return; for (const child of m.children.values()) yield* flattenChildren(child); } function sortChildren(m) { if (!m.children) return; m.children = new Map([...m.children.entries()].sort((a, b) => b[1].totalTimeMs - a[1].totalTimeMs)); m.children.forEach(sortChildren); } } //#endregion //#region src/util/ansi.ts function isAnsiString(s) { return s.includes("\x1B") || s.includes("›"); } /** * * @param s - the string to measure - should NOT contains ANSI codes * @param tabWidth - * @returns */ function width(s, tabWidth = 1) { return s.replaceAll("…", ".").replaceAll(" ", " ".repeat(tabWidth)).replaceAll(/\p{M}/gu, "").replaceAll(/\p{L}/gu, ".").replaceAll(/[\u0000-\u001F\u0300-\u036F]/g, "").replaceAll(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, ".").length; } /** * Measure the width of a string containing ANSI control characters. * @param s - string to measure with width in characters. * @returns the approximate number of screen characters. */ function ansiWidth(s) { return width(stripVTControlCharacters(s)); } function fragmentString(str, splitOnRegex, sType) { const fragments = []; let lastIndex = 0; for (const match of str.matchAll(new RegExp(splitOnRegex))) { if (match.index > lastIndex) fragments.push({ type: "text", text: str.slice(lastIndex, match.index) }); fragments.push({ type: sType, text: match[0] }); lastIndex = match.index + match[0].length; } if (lastIndex < str.length) fragments.push({ type: "text", text: str.slice(lastIndex) }); return fragments; } const ansi = ansiRegex(); function parseAnsiStr(str) { return fragmentString(str, ansi, "ansi"); } /** * Prune the end of a string to fit within a specified width, adding an ellipsis if necessary. * @param str - the text to prune - ANSI is supported * @param maxWidth - the maximum width of the text * @param pad - the string to use for padding, default is '…' * @returns the pruned text */ function pruneAnsiTextEnd(str, maxWidth, pad$1 = "…") { if (!maxWidth || maxWidth <= 0) return str; if (str.length <= maxWidth) return str; if (ansiWidth(str) <= maxWidth) return str; const padWidth$1 = ansiWidth(pad$1); const fragments = parseAnsiStr(str); let remaining = maxWidth - padWidth$1; for (const frag of fragments) { if (frag.type !== "text") continue; if (remaining <= 0) { frag.text = ""; continue; } const pruned = pruneTextEnd(frag.text, remaining, pad$1); if (pruned !== frag.text) { frag.text = pruned; remaining = 0; continue; } remaining -= width(frag.text); } return fragments.map((frag) => frag.text).join(""); } /** * Prune the start of a string to fit within a specified width, adding an ellipsis if necessary. * @param str - the text to prune - ANSI is supported * @param maxWidth - the maximum width of the text * @param pad - the string to use for padding, default is '…' * @returns the pruned text */ function pruneAnsiTextStart(str, maxWidth, pad$1 = "…") { if (!maxWidth || maxWidth <= 0) return str; if (str.length <= maxWidth) return str; if (ansiWidth(str) <= maxWidth) return str; const padWidth$1 = ansiWidth(pad$1); const fragments = parseAnsiStr(str); let remaining = maxWidth - padWidth$1; for (const frag of fragments.reverse()) { if (frag.type !== "text") continue; if (remaining <= 0) { frag.text = ""; continue; } const pruned = pruneTextStart(frag.text, remaining, pad$1); if (pruned !== frag.text) { frag.text = pruned; remaining = 0; continue; } remaining -= width(frag.text); } return fragments.reverse().map((frag) => frag.text).join(""); } /** * Prune the end of a string to fit within a specified width, adding an ellipsis if necessary. * @param str - the text to prune - ANSI is not supported * @param maxWidth - the maximum width of the text * @param pad - the string to use for padding, default is '…' * @returns the pruned text */ function pruneTextEnd(str, maxWidth, pad$1 = "…") { if (!maxWidth || maxWidth <= 0) return str; if (str.length <= maxWidth) return str; if (isAnsiString(str)) return pruneAnsiTextEnd(str, maxWidth, pad$1); const maxWidthWithPad = maxWidth - width(pad$1); const letters = [...str]; let len = 0; for (let i = 0; i < letters.length; i++) { const c = letters[i]; len += width(c); if (len > maxWidthWithPad) { let j = i + 1; while (j < letters.length && width(letters[j]) === 0) ++j; return j === letters.length ? str : letters.slice(0, i).join("") + pad$1; } } return str; } /** * Prune the start of a string to fit within a specified width, adding an ellipsis if necessary. * @param str - the text to prune - ANSI is not supported * @param maxWidth - the maximum width of the text * @param pad - the string to use for padding, default is '…' * @returns the pruned text */ function pruneTextStart(str, maxWidth, pad$1 = "…") { if (!maxWidth || maxWidth <= 0) return str; if (str.length <= maxWidth) return str; const maxWidthWithPad = maxWidth - width(pad$1); const letters = [...str]; let len = 0; for (let i = letters.length - 1; i >= 1; i--) { const c = letters[i]; len += width(c); if (len > maxWidthWithPad) { i += 1; while (i < letters.length && width(letters[i]) === 0) ++i; return pad$1 + letters.slice(i).join(""); } } return str; } //#endregion //#region src/util/pad.ts function pad(s, w) { const p = padWidth(s, w); if (!p) return s; return s.padEnd(p + s.length); } function padWidth(s, target) { const sWidth = ansiWidth(s); return Math.max(target - sWidth, 0); } function padLeft(s, w) { const p = padWidth(s, w); if (!p) return s; return s.padStart(p + s.length); } //#endregion //#region src/util/table.ts function tableToLines(table, deliminator) { const del = deliminator || table.deliminator || " | "; const columnWidths = []; const columnAlignments = table.columnAlignments || []; const maxColumnWidthsMap = table.maxColumnWidths || {}; const tableIndent = table.indent ? typeof table.indent === "number" ? " ".repeat(table.indent) : table.indent : ""; const { header, rows } = table; const simpleHeader = header.map((col) => Array.isArray(col) ? col[1] : col); const columnFieldNames = header.map((col) => Array.isArray(col) ? col[0] : col); const maxColumnWidths = columnFieldNames.map((field, idx) => maxColumnWidthsMap[field] ?? maxColumnWidthsMap[idx]); function getCell(row, col) { return getCellFromRow(rows[row], col); } function getCellFromRow(row, col) { if (!row) return void 0; if (Array.isArray(row)) return row[col]; return row[columnFieldNames[col]]; } function rowToCells(row) { if (Array.isArray(row)) return row; return columnFieldNames.map((fieldName) => row[fieldName]); } function getText(col, maxWidth) { return !col ? "" : typeof col === "string" ? pruneTextEnd(col, maxWidth) : col(maxWidth); } function getRCText(row, col, maxWidth) { return getText(getCell(row, col), maxWidth); } function recordHeaderWidths(header$1) { header$1.forEach((col, idx) => { columnWidths[idx] = Math.max(ansiWidth(col), columnWidths[idx] || 0); }); } function recordColWidths() { for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) for (let colIndex = 0; colIndex < columnFieldNames.length; colIndex++) columnWidths[colIndex] = Math.max(ansiWidth(getRCText(rowIndex, colIndex, void 0)), columnWidths[colIndex] || 0); } function justifyRow(c, i) { return columnAlignments[i] === "R" ? padLeft(c, columnWidths[i]) : pad(c, columnWidths[i]); } function toHeaderLine(header$1) { return tableIndent + decorateRowWith(header$1.map((c, i) => getText(c, columnWidths[i])), justifyRow, headerDecorator).join(del); } function toLine(row) { return tableIndent + decorateRowWith(rowToCells(row).map((c, i) => getText(c, columnWidths[i])), justifyRow).join(del); } function* process$1() { if (table.title) yield table.title; yield toHeaderLine(simpleHeader); yield* rows.map(toLine); } function sumColumnWidths() { return columnWidths.reduce((sum, width$1) => sum + width$1, 0); } function adjustColWidths() { for (let i = 0; i < columnWidths.length; i++) { const mw = maxColumnWidths[i]; if (!mw) continue; columnWidths[i] = Math.min(columnWidths[i], mw); } if (!table.terminalWidth) return; const dWidth = (columnWidths.length - 1) * ansiWidth(del); const lineWidth = table.terminalWidth - dWidth; if (lineWidth <= columnWidths.length * 2) { const fixedWidth = Math.max(Math.min(...columnWidths), 5); for (let i = 0; i < columnWidths.length; i++) columnWidths[i] = fixedWidth; return; } if (columnWidths.length === 1) { columnWidths[0] = lineWidth; return; } function trimWidestColumn(neededToTrim) { let first = 0; let second = 0; for (let i = 0; i < columnWidths.length; i++) if (columnWidths[i] > columnWidths[first]) { second = first; first = i; } else if (columnWidths[i] > columnWidths[second]) second = i; const diff = Math.max(columnWidths[first] - columnWidths[second], 1); columnWidths[first] -= Math.min(diff, neededToTrim); } for (let sum = sumColumnWidths(); sum > lineWidth; sum = sumColumnWidths()) trimWidestColumn(sum - lineWidth); } recordHeaderWidths(simpleHeader); recordColWidths(); adjustColWidths(); return [...process$1()]; } function headerDecorator(t) { return chalk.bold(chalk.underline(t)); } function decorateRowWith(row, ...decorators) { return decorators.reduce((row$1, decorator) => row$1.map(decorator), row); } //#endregion //#region src/util/util.ts const uniqueFn = uniqueFilterFnGenerator; function uniqueFilterFnGenerator(extractFn) { const values = /* @__PURE__ */ new Set(); const extractor = extractFn || ((a) => a); return (v) => { const vv = extractor(v); const ret = !values.has(vv); values.add(vv); return ret; }; } /** * Removed all properties with a value of `undefined` from the object. * @param src - the object to clean. * @returns the same object with all properties with a value of `undefined` removed. */ function clean(src) { const r = src; for (const key of Object.keys(r)) if (r[key] === void 0) delete r[key]; return r; } //#endregion //#region src/cli-reporter.ts const templateIssue = `{green $filename}:{yellow $row:$col} - $message ({red $text}) $quickFix`; const templateIssueNoFix = `{green $filename}:{yellow $row:$col} - $message ({red $text})`; const templateIssueWithSuggestions = `{green $filename}:{yellow $row:$col} - $message ({red $text}) Suggestions: {yellow [$suggestions]}`; const templateIssueWithContext = `{green $filename}:{yellow $row:$col} $padRowCol- $message ({red $text})$padContext -- {gray $contextLeft}{red {underline $text}}{gray $contextRight}`; const templateIssueWithContextWithSuggestions = `{green $filename}:{yellow $row:$col} $padRowCol- $message ({red $text})$padContext -- {gray $contextLeft}{red {underline $text}}{gray $contextRight}\n\t Suggestions: {yellow [$suggestions]}`; const templateIssueLegacy = `{green $filename}[$row, $col]: $message: {red $text}`; const templateIssueWordsOnly = "$text"; assert(true); /** * * @param template - The template to use for the issue. * @param uniqueIssues - If true, only unique issues will be reported. * @param reportedIssuesCollection - optional collection to store reported issues. * @returns issueEmitter function */ function genIssueEmitter(stdIO, errIO, template, uniqueIssues, reportedIssuesCollection) { const uniqueFilter = uniqueIssues ? uniqueFilterFnGenerator((issue) => issue.text) : () => true; const defaultWidth = 10; let maxWidth = defaultWidth; let uri; return function issueEmitter(issue) { if (!uniqueFilter(issue)) return; if (uri !== issue.uri) { maxWidth = defaultWidth; uri = issue.uri; } maxWidth = Math.max(maxWidth * .999, issue.text.length, 10); const issueText = formatIssue(stdIO, template, issue, Math.ceil(maxWidth)); reportedIssuesCollection?.push(formatIssue(errIO, template, issue, Math.ceil(maxWidth))); stdIO.writeLine(issueText); }; } function nullEmitter() {} function relativeUriFilename(uri, rootURL) { const url = toFileURL(uri); const rel = urlRelative(rootURL, url); if (rel.startsWith("..")) return toFilePathOrHref(url); return rel; } function reportProgress(io, p, cwdURL, options) { if (p.type === "ProgressFileComplete") return reportProgressFileComplete(io, p, cwdURL, options); if (p.type === "ProgressFileBegin") return reportProgressFileBegin(io, p, cwdURL); } function determineFilename(io, p, cwd) { const fc = "" + p.fileCount; return { idx: (" ".repeat(fc.length) + p.fileNum).slice(-fc.length) + "/" + fc, filename: io.chalk.gray(relativeUriFilename(p.filename, cwd)) }; } function reportProgressFileBegin(io, p, cwdURL) { const { idx, filename } = determineFilename(io, p, cwdURL); if (io.getColorLevel() > 0) { io.clearLine?.(0); io.write(`${idx} ${filename}\r`); } } function reportProgressFileComplete(io, p, cwd, options) { const { idx, filename } = determineFilename(io, p, cwd); const { verbose, debug } = options; const time = reportTime(io, p.elapsedTimeMs, !!p.cached); const skippedReason = p.skippedReason ? ` (${p.skippedReason})` : ""; const skipped = p.processed === false ? ` skipped${skippedReason}` : ""; const hasErrors = p.numErrors ? io.chalk.red` X` : ""; const msg = `${idx} ${filename} ${time}${skipped}${hasErrors}${(verbose || debug || hasErrors || isSlow(p.elapsedTimeMs) || io.getColorLevel() < 1 ? "\n" : "") || "\r"}`; io.write(msg); } function reportTime(io, elapsedTimeMs, cached) { if (cached) return io.chalk.green("cached"); if (elapsedTimeMs === void 0) return "-"; const slow = isSlow(elapsedTimeMs); return (!slow ? io.chalk.white : slow === 1 ? io.chalk.yellow : io.chalk.redBright)(elapsedTimeMs.toFixed(2) + "ms"); } function isSlow(elapsedTmeMs) { if (!elapsedTmeMs || elapsedTmeMs < 1e3) return 0; if (elapsedTmeMs < 2e3) return 1; return 2; } function getReporter(options, config) { const perfStats = { filesProcessed: 0, filesSkipped: 0, filesCached: 0, elapsedTimeMs: 0, perf: Object.create(null) }; const noColor = options.color === false; const forceColor = options.color === true; const uniqueIssues = config?.unique || false; const defaultIssueTemplate = options.wordsOnly ? templateIssueWordsOnly : options.legacy ? templateIssueLegacy : options.showContext ? options.showSuggestions ? templateIssueWithContextWithSuggestions : templateIssueWithContext : options.showSuggestions ? templateIssueWithSuggestions : options.showSuggestions === false ? templateIssueNoFix : templateIssue; const { fileGlobs, silent, summary, issues, progress: showProgress, verbose, debug } = options; const issueTemplate = config?.issueTemplate || defaultIssueTemplate; assertCheckTemplate(issueTemplate); const console$1 = config?.console || console; const colorLevel = noColor ? 0 : forceColor ? 2 : console$1.stdoutChannel.getColorLevel(); const stdio = { ...console$1.stdoutChannel, chalk: new Chalk({ level: colorLevel }) }; const stderr = { ...console$1.stderrChannel, chalk: new Chalk({ level: colorLevel }) }; const consoleError = (msg) => stderr.writeLine(msg); function createInfoLog(wrap) { return (msg) => console$1.info(wrap(msg)); } const emitters = { Debug: !silent && debug ? createInfoLog(stdio.chalk.cyan) : nullEmitter, Info: !silent && verbose ? createInfoLog(stdio.chalk.yellow) : nullEmitter, Warning: createInfoLog(stdio.chalk.yellow) }; function infoEmitter(message, msgType) { emitters[msgType]?.(message); } const rootURL = toFileDirURL(options.root || process.cwd()); function relativeIssue(fn) { const fnFilename = options.relative ? (uri) => relativeUriFilename(uri, rootURL) : (uri) => toFilePathOrHref(toFileURL(uri, rootURL)); return (i) => { const fullFilename = i.uri ? toFilePathOrHref(toFileURL(i.uri, rootURL)) : ""; const filename = i.uri ? fnFilename(i.uri) : ""; fn({ ...i, filename, fullFilename }); }; } const issuesCollection = void 0; const errorCollection = []; function errorEmitter(message, error) { if (isSpellingDictionaryLoadError(error)) error = error.cause; const errorText = formatWithOptions({ colors: stderr.stream.hasColors?.() }, stderr.chalk.red(message), debug ? error : error.toString()); errorCollection?.push(errorText); consoleError(errorText); } const resultEmitter = (result) => { if (!fileGlobs.length && !result.files) return; const { files, issues: issues$1, cachedFiles, filesWithIssues, errors, skippedFiles } = result; const numFilesWithIssues = filesWithIssues.size; const chalk$1 = stderr.chalk; if (stderr.getColorLevel() > 0) { stderr.write("\r"); stderr.clearLine(0); } if (issuesCollection?.length || errorCollection?.length) consoleError("-------------------------------------------"); if (issuesCollection?.length) { consoleError("Issues found:"); issuesCollection.forEach((issue) => consoleError(issue)); } const filesChecked = files - (skippedFiles || 0); const cachedFilesText = cachedFiles ? ` (${cachedFiles} from cache)` : ""; const skippedFilesText = skippedFiles ? `, skipped: ${skippedFiles}` : ""; const withErrorsText = errors ? ` with ${errors} error${errors === 1 ? "" : "s"}` : ""; consoleError(`CSpell\u003A Files checked: ${filesChecked}${cachedFilesText}${skippedFilesText}, Issues found: ${issues$1} in ${numFilesWithIssues === 1 ? "1 file" : `${numFilesWithIssues} files`}${withErrorsText}.`); if (errorCollection?.length && issues$1 > 5) { consoleError("-------------------------------------------"); consoleError("Errors:"); errorCollection.forEach((error) => consoleError(error)); } if (options.showPerfSummary) { consoleError("-------------------------------------------"); consoleError("Performance Summary:"); consoleError(` Files Processed: ${perfStats.filesProcessed.toString().padStart(6)}`); consoleError(` Files Skipped : ${perfStats.filesSkipped.toString().padStart(6)}`); consoleError(` Files Cached : ${perfStats.filesCached.toString().padStart(6)}`); consoleError(` Processing Time: ${perfStats.elapsedTimeMs.toFixed(2).padStart(9)}ms`); const tableStats = { title: chalk$1.bold("Perf Stats:"), header: ["Name", "Time (ms)"], columnAlignments: ["L", "R"], indent: 2, rows: Object.entries(perfStats.perf).filter((p) => !!p[1]).map(([key, value]) => [key, value.toFixed(2)]) }; consoleError(""); for (const line of tableToLines(tableStats)) consoleError(line); if (options.verboseLevel) verbosePerfReport(); } }; function verbosePerfReport() { const perfMeasurements = getPerfMeasurements(); if (!perfMeasurements.length) return; const notable = extractNotableBySelfTimeInGroup(perfMeasurements); const chalk$1 = stderr.chalk; const maxDepth = Math.max(...perfMeasurements.map((m) => m.depth)); const depthIndicator = (d) => "⋅".repeat(d) + " ".repeat(maxDepth - d); const rows = perfMeasurements.map((m) => { const cbd = (text) => colorByDepth(chalk$1, m.depth, text); const cNotable = (text) => notable.has(m) ? chalk$1.yellow(text) : text; return [ chalk$1.dim("⋅".repeat(m.depth)) + colorByDepthGrayscale(stderr.chalk, m.depth, m.name), cbd(m.totalTimeMs.toFixed(2) + chalk$1.dim(depthIndicator(m.depth))), cbd(cNotable((m.totalTimeMs - m.nestedTimeMs).toFixed(2))), cbd(m.count.toString()), cbd(m.minTimeMs.toFixed(2)), cbd(m.maxTimeMs.toFixed(2)), cbd((m.totalTimeMs / m.count).toFixed(2)) ]; }); const table = tableToLines({ title: chalk$1.bold("Detailed Measurements:"), header: [ "Name", "Total Time (ms)", "Self (ms)", "Count", "Min (ms)", "Max (ms)", "Avg (ms)" ], rows, columnAlignments: [ "L", "R", "R", "R", "R", "R", "R" ], indent: 2 }); consoleError("\n-------------------------------------------\n"); for (const line of table) consoleError(line); } function colorByDepth(chalk$1, depth, text) { const colors = [ chalk$1.green, chalk$1.cyan, chalk$1.blue, chalk$1.magenta, chalk$1.red ]; const color = colors[depth % colors.length]; if (depth / colors.length >= 1) return chalk$1.dim(color(text)); return color(text); } function colorByDepthGrayscale(chalk$1, depth, text) { const grayLevel = Math.max(32, 255 - depth * 20); return chalk$1.rgb(grayLevel, grayLevel, grayLevel)(text); } function collectPerfStats(p) { if (p.cached) { perfStats.filesCached++; return; } perfStats.filesProcessed += p.processed ? 1 : 0; perfStats.filesSkipped += !p.processed ? 1 : 0; perfStats.elapsedTimeMs += p.elapsedTimeMs || 0; if (!p.perf) return; for (const [key, value] of Object.entries(p.perf)) if (typeof value === "number") perfStats.perf[key] = (perfStats.perf[key] || 0) + value; } function progress(p) { if (!silent && showProgress) reportProgress(stderr, p, rootURL, options); if (p.type === "ProgressFileComplete") collectPerfStats(p); } return { issue: relativeIssue(silent || !issues ? nullEmitter : genIssueEmitter(stdio, stderr, issueTemplate, uniqueIssues, issuesCollection)), error: silent ? nullEmitter : errorEmitter, info: infoEmitter, debug: emitters.Debug, progress, result: !silent && summary ? resultEmitter : nullEmitter, features: void 0 }; } function extractNotableBySelfTimeInGroup(measurements) { const notable = /* @__PURE__ */ new Set(); if (!measurements.length) return notable; let highest; let highestSelfTime = 0; for (const m of measurements) { if (m.depth === 0 || !highest) { if (highest) notable.add(highest); highest = m; highestSelfTime = m.totalTimeMs - m.nestedTimeMs; continue; } const selfTime = m.totalTimeMs - m.nestedTimeMs; if (selfTime > highestSelfTime) { highest = m; highestSelfTime = selfTime; } } if (highest) notable.add(highest); return notable; } function formatIssue(io, templateStr, issue, maxIssueTextWidth) { function clean$1(t$1) { return t$1.replace(/\s+/, " "); } const { uri = "", filename, row, col, text, context = issue.line, offset } = issue; const contextLeft = clean$1(context.text.slice(0, offset - context.offset)); const contextRight = clean$1(context.text.slice(offset + text.length - context.offset)); const contextFull = clean$1(context.text); const padContext = " ".repeat(Math.max(maxIssueTextWidth - text.length, 0)); const rowText = row.toString(); const colText = col.toString(); const padRowCol = " ".repeat(Math.max(1, 8 - (rowText.length + colText.length))); const suggestions$1 = formatSuggestions(io, issue); const msg = issue.message || (issue.isFlagged ? "Forbidden word" : "Unknown word"); const messageColored = issue.isFlagged ? `{yellow ${msg}}` : msg; const substitutions = { $col: colText, $contextFull: contextFull, $contextLeft: contextLeft, $contextRight: contextRight, $filename: filename, $padContext: padContext, $padRowCol: padRowCol, $row: rowText, $suggestions: suggestions$1, $text: text, $uri: uri, $quickFix: formatQuickFix(io, issue), $message: msg, $messageColored: messageColored }; const t = templateStr.replaceAll("$messageColored", messageColored); return substitute(makeTemplate(io.chalk)(t), substitutions).trimEnd(); } function formatSuggestions(io, issue) { if (issue.suggestionsEx) return issue.suggestionsEx.map((sug) => sug.isPreferred ? io.chalk.italic(io.chalk.bold(sug.wordAdjustedToMatchCase || sug.word)) + "*" : sug.wordAdjustedToMatchCase || sug.word).join(", "); if (issue.suggestions) return issue.suggestions.join(", "); return ""; } function formatQuickFix(io, issue) { if (!issue.suggestionsEx?.length) return ""; const preferred = issue.suggestionsEx.filter((sug) => sug.isPreferred).map((sug) => sug.wordAdjustedToMatchCase || sug.word); if (!preferred.length) return ""; return `fix: (${preferred.map((w) => io.chalk.italic(io.chalk.yellow(w))).join(", ")})`; } function substitute(text, substitutions) { const subs = []; for (const [match, replaceWith] of Object.entries(substitutions)) { const len = match.length; for (let i$1 = text.indexOf(match); i$1 >= 0; i$1 = text.indexOf(match, i$1)) { const end = i$1 + len; const reg = /\b/y; reg.lastIndex = end; if (reg.test(text)) subs.push([ i$1, end, replaceWith ]); i$1 = end; } } subs.sort((a, b) => a[0] - b[0]); let i = 0; function sub(r) { const [a, b, t] = r; const prefix = text.slice(i, a); i = b; return prefix + t; } return subs.map(sub).join("") + text.slice(i); } function assertCheckTemplate(template) { const r = checkTemplate(template); if (r instanceof Error) throw r; } function checkTemplate(template) { const chalkTemplate = makeTemplate(new Chalk()); const substitutions = { $col: "<col>", $contextFull: "<contextFull>", $contextLeft: "<contextLeft>", $contextRight: "<contextRight>", $filename: "<filename>", $padContext: "<padContext>", $padRowCol: "<padRowCol>", $row: "<row>", $suggestions: "<suggestions>", $text: "<text>", $uri: "<uri>", $quickFix: "<quickFix>", $message: "<message>", $messageColored: "<messageColored>" }; try { const problems = [...substitute(chalkTemplate(template), substitutions).matchAll(/\$[a-z]+/gi)].map((m) => m[0]); if (problems.length) throw new Error(`Unresolved template variable${problems.length > 1 ? "s" : ""}: ${problems.map((v) => `'${v}'`).join(", ")}`); return true; } catch (e) { return new ApplicationError(e instanceof Error ? e.message : `${e}`); } } //#endregion //#region src/config/adjustConfig.ts async function fileExists(url) { if (url.protocol !== "file:") return false; try { return (await promises.stat(url)).isFile(); } catch (e) { if (toError$1(e).code === "ENOENT") return false; throw e; } } async function resolveImports(configFile, imports) { const fromConfigDir = new URL("./", configFile.url); const fromCurrentDir = toFileDirURL("./"); const require = createRequire(fromConfigDir); function isPackageName(name$1) { try { require.resolve(name$1, { paths: [fileURLToPath(fromConfigDir)] }); return true; } catch { return false; } } const _imports = []; for (const imp of imports) { const url = new URL(imp, fromCurrentDir); if (url.protocol !== "file:") { _imports.push(imp); continue; } if (await fileExists(url)) { let rel = urlRelative(fromConfigDir, url); if (!(rel.startsWith("./") || rel.startsWith("../"))) rel = "./" + rel; _imports.push(rel); continue; } if (url.protocol !== "file:") { _imports.push(url.href); continue; } if (isPackageName(imp)) { _imports.push(imp); continue; } throw new Error(`Cannot resolve import: ${imp}`); } return _imports; } function addImportsToMutableConfigFile(configFile, resolvedImports, comment) { let importNode = configFile.getNode("import", []); if (importNode.type === "scalar") { configFile.setValue("import", [importNode.value]); importNode = configFile.getNode("import", []); } assert(isCfgArrayNode(importNode)); const knownImports = new Set(importNode.value); for (const imp of resolvedImports) { if (knownImports.has(imp)) continue; importNode.push(imp); } if (comment) configFile.setComment("import", comment); } async function addImportsToConfigFile(configFile, imports, comment) { const resolvedImports = await resolveImports(configFile, imports); if (configFile instanceof MutableCSpellConfigFile) return addImportsToMutableConfigFile(configFile, resolvedImports, comment); const settings = configFile.settings; let importNode = settings.import; if (!Array.isArray(importNode)) { importNode = typeof importNode === "string" ? [importNode] : []; settings.import = importNode; if (comment) configFile.setComment("import", comment); } assert(Array.isArray(importNode)); const knownImports = new Set(importNode); for (const imp of resolvedImports) { if (knownImports.has(imp)) continue; importNode.push(imp); } } function setConfigFieldValue(configFile, key, value, comment) { configFile.setValue(key, value); if (comment !== void 0) configFile.setComment(key, comment); } function addDictionariesToConfigFile(configFile, dictionaries, comment) { if (configFile instanceof MutableCSpellConfigFile) { const found = configFile.getValue("dictionaries"); const dicts$1 = configFile.getNode("dictionaries", []); assert(isCfgArrayNode(dicts$1)); const knownDicts$1 = new Set(dicts$1.value); for (const dict of dictionaries) if (!knownDicts$1.has(dict)) { dicts$1.push(dict); knownDicts$1.add(dict); } if (!found && comment) configFile.setComment("dictionaries", comment); return; } const dicts = configFile.settings.dictionaries || []; const knownDicts = new Set(dicts); for (const dict of dictionaries) if (!knownDicts.has(dict)) { dicts.push(dict); knownDicts.add(dict); } setConfigFieldValue(configFile, "dictionaries", dicts, comment); } //#endregion //#region src/config/config.ts function applyValuesToConfigFile(config, settings, defaultValues, addComments) { const currentSettings = config.settings || {}; for (const [k, entry] of Object.entries(defaultValues)) { const { value: defaultValue, comment } = entry; const key = k; const newValue = settings[key]; const oldValue = currentSettings[key]; const value = newValue ?? oldValue ?? defaultValue; if (newValue === void 0 && oldValue !== void 0 || value === void 0) continue; setConfigFieldValue(config, key, value, addComments && oldValue === void 0 && comment || void 0); } return config; } //#endregion //#region src/config/constants.ts const defaultConfig = { $schema: { value: void 0, comment: " The schema for the configuration file." }, version: { value: "0.2", comment: " The version of the configuration file format." }, name: { value: void 0, comment: " The name of the configuration. Use for display purposes only." }, description: { value: void 0, comment: " A description of the configuration." }, language: { value: "en", comment: " The locale to use when spell checking. (e.g., en, en-GB, de-DE" }, import: { value: void 0, comment: " Configuration or packages to import." }, dictionaryDefinitions: { value: void 0, comment: " Define user dictionaries." }, dictionaries: { value: void 0, comment: " Enable the dictionaries." }, ignorePaths: { value: void 0, comment: " Glob patterns of files to be skipped." }, files: { value: void 0, comment: " Glob patterns of files to be included." }, words: { value: void 0, comment: " Words to be considered correct." }, ignoreWords: { value: void 0, comment: " Words to be ignored." }, flagWords: { value: void 0, comment: " Words to be flagged as incorrect." }, overrides: { value: void 0, comment: " Set configuration based upon file globs." }, languageSettings: { value: void 0, comment: " Define language specific settings." }, enabledFileTypes: { value: void 0, comment: " Enable for specific file types." }, caseSensitive: { value: void 0, comment: " Enable case sensitive spell checking." }, patterns: { value: void 0, comment: " Regular expression patterns." }, ignoreRegExpList: { value: void 0, comment: " Regular expressions / patterns of text to be ignored." }, includeRegExpList: { value: void 0, comment: " Regular expressions / patterns of text to be included." } }; //#endregion //#region src/config/configInit.ts const schemaRef = cspellConfigFileSchema; const defaultConfigJson = `\ { } `; const defaultConfigYaml = ` `; async function configInit(options) { const rw = createReaderWriter(); const configFile = await createConfigFile(rw, determineFileNameURL(options), options); await applyOptionsToConfigFile(configFile, options); await fs.mkdir(new URL("./", configFile.url), { recursive: true }); if (options.stdout) console.stdoutChannel.write(rw.serialize(configFile)); else await rw.writeConfig(configFile); } async function applyOptionsToConfigFile(configFile, options) { const settings = {}; const addComments = options.comments || options.comments === void 0 && !options.removeComments && !configFile.url.pathname.endsWith(".json"); if (options.comments === false) configFile.removeAllComments(); if (options.schema ?? true) configFile.setSchema(schemaRef); if (options.locale) settings.language = options.locale; applyValuesToConfigFile(configFile, settings, defaultConfig, addComments); if (options.import) await addImportsToConfigFile(configFile, options.import, addComments && defaultConfig.import?.comment || void 0); if (options.dictionary) addDictionariesToConfigFile(configFile, options.dictionary, addComments && defaultConfig.dictionaries?.comment || void 0); return configFile; } function determineFileNameURL(options) { if (options.config) return toFileURL(options.config); const defaultFileName = determineDefaultFileName(options); const outputUrl = toFileURL(options.output || defaultFileName); const path$2 = outputUrl.pathname; if (path$2.endsWith(".json") || path$2.endsWith(".jsonc") || path$2.endsWith(".yaml") || path$2.endsWith(".yml")) return outputUrl; if (/\.{m,c}?{j,t}s$/.test(path$2)) throw new Error(`Unsupported file extension: ${path$2}`); return new URL(defaultFileName, toFileDirURL(outputUrl)); } function determineDefaultFileName(options) { switch (options.format || "yaml") { case "json": return "cspell.json"; case "jsonc": return "cspell.jsonc"; case "yaml": return "cspell.config.yaml"; case "yml": return "cspell.config.yml"; } throw new Error(`Unsupported format: ${options.format}`); } function getDefaultContent(options) { switch (options.format) { case void 0: case "yaml": return defaultConfigYaml; case "json": case "jsonc": return defaultConfigJson; default: throw new Error(`Unsupported format: ${options.format}`); } } async function createConfigFile(rw, url, options) { if (url.pathname.endsWith("package.json")) return rw.readConfig(url); const content = await fs.readFile(url, "utf8").catch(() => getDefaultContent(options)); return rw.parse({ url, content }); } //#endregion //#region src/featureFlags/featureFlags.ts function getFeatureFlags() { return getSystemFeatureFlags(); } function parseFeatureFlags(flags, featureFlags = getFeatureFlags()) { if (!flags) return featureFlags; const flagsKvP = flags.map((f) => f.split(":", 2)); for (const flag of flagsKvP) { const [name$1, value] = flag; try { featureFlags.setFlag(name$1, value); } catch { console.warn(`Unknown flag: "${name$1}"`); } } return featureFlags; } //#endregion //#region src/environment.ts const environmentKeys = { CSPELL_ENABLE_DICTIONARY_LOGGING: "CSPELL_ENABLE_DICTIONARY_LOGGING", CSPELL_ENABLE_DICTIONARY_LOG_FILE: "CSPELL_ENABLE_DICTIONARY_LOG_FILE", CSPELL_ENABLE_DICTIONARY_LOG_FIELDS: "CSPELL_ENABLE_DICTIONARY_LOG_FIELDS", CSPELL_GLOB_ROOT: "CSPELL_GLOB_ROOT", CSPELL_CONFIG_PATH: "CSPELL_CONFIG_PATH", CSPELL_DEFAULT_CONFIG_PATH: "CSPELL_DEFAULT_CONFIG_PATH" }; function setEnvironmentVariable(key, value) { process.env[key] = value; } function getEnvironmentVariable(key) { return process.env[key]; } function truthy(value) { switch (value?.toLowerCase().trim()) { case "t": case "true": case "on": case "yes": case "1": return true; } return false; } //#endregion //#region src/dirname.ts let _dirname; try { if (typeof import.meta.url !== "string") throw new Error("assert"); _dirname = fileURLToPath(new URL(".", import.meta.url)); } catch { _dirname = __dirname; } const pkgDir = _dirname; //#endregion //#region src/pkgInfo.ts const name = "cspell"; const version$1 = "9.6.0"; const engines = { node: ">=20" }; const npmPackage = { name, version: version$1, engines }; //#endregion //#region src/util/async.ts const asyncMap = operators.opMapAsync; const asyncFilter = operators.opFilterAsync; const asyncAwait = operators.opAwaitAsync; const asyncFlatten = operators.opFlattenAsync; //#endregion //#region src/util/constants.ts const UTF8 = "utf8"; const STDIN = "stdin"; const STDINProtocol = "stdin:"; const STDINUrlPrefix = "stdin://"; const FileUrlPrefix = "file://"; const FileUrlAbsPrefix = "file:///"; //#endregion //#region src/util/glob.ts const defaultExcludeGlobs = ["node_modules/**"]; /** * * @param pattern - glob patterns and NOT file paths. It can be a file path turned into a glob. * @param options - search options. */ async function globP(pattern, options) { const cwd = options?.root || options?.cwd || process.cwd(); const ignore = (typeof options?.ignore === "string" ? [options.ignore] : options?.ignore)?.filter((g) => !g.startsWith("../")); const onlyFiles = options?.nodir; const dot = options?.dot; const patterns = typeof pattern === "string" ? [pattern] : pattern; const useOptions = clean({ cwd, onlyFiles, dot, ignore, absolute: true, followSymbolicLinks: false, expandDirectories: false }); const compare$1 = new Intl.Collator("en").compare; return (await glob$1(patterns, useOptions)).sort(compare$1).map((absFilename) => path$1.relative(cwd, absFilename)); } function calcGlobs(commandLineExclude) { const commandLineExcludes = { globs: [...new Set((commandLineExclude || []).flatMap((glob$2) => glob$2.split(/(?<!\\)\s+/g)).map((g) => g.replaceAll("\\ ", " ")))], source: "arguments" }; const defaultExcludes = { globs: defaultExcludeGlobs, source: "default" }; return commandLineExcludes.globs.length ? commandLineExcludes : defaultExcludes; } function extractPatterns(globs) { return globs.reduce((info, g) => { const source = g.source; const patterns = g.matcher.patternsNormalizedToRoot; return [...info, ...patterns.map((glob$2) => ({ glob: glob$2, source }))]; }, []); } function calcExcludeGlobInfo(root, commandLineExclude) { commandLineExclude = typeof commandLineExclude === "string" ? [commandLineExclude] : commandLineExclude; const choice = calcGlobs(commandLineExclude); return [{ matcher: new GlobMatcher(choice.globs, { root, dot: true }), source: choice.source }]; } /** * Build GlobMatcher from command line or config file globs. * @param globs Glob patterns or file paths * @param root - directory to use as the root */ function buildGlobMatcher(globs, root, isExclude) { return new GlobMatcher(globs.map((g) => { return { source: typeof g === "string" ? "command line" : void 0, ...fileOrGlobToGlob(g, root) }; }), { root, mode: isExclude ? "exclude" : "include" }); } function extractGlobsFromMatcher(globMatcher) { return globMatcher.patternsNormalizedToRoot.map((g) => g.glob); } function normalizeGlobsToRoot(globs, root, isExclude) { return [globs.filter((g) => typeof g === "string" && isPossibleUrlRegExp.test(g)), extractGlobsFromMatcher(buildGlobMatcher(globs.filter((g) => typeof g !== "string" || !isPossibleUrlRegExp.test(g)), root, isExclude))].flat(); } const isPossibleGlobRegExp = /[()*?[{}]/; const isPossibleUrlRegExp = /^[\d_a-z-]{3,}:\/\//; /** * If a 'glob' is a path to a directory, then append `**` so that * directory searches work. * @param glob - a glob, file, or directory * @param root - root to use. * @returns `**` is appended directories. */ async function adjustPossibleDirectory(glob$2, root) { const g = typeof glob$2 === "string" ? { glob: glob$2, root } : { glob: glob$2.glob, root: glob$2.root ?? root }; if (isPossibleGlobRegExp.test(g.glob)) return glob$2; if (isPossibleUrlRegExp.test(g.glob)) return glob$2; const dirPath = path$1.resolve(g.root, g.glob); try { if ((await promises.stat(dirPath)).isDirectory()) { const useGlob = posix.join(posixPath(g.glob), "**"); return typeof glob$2 === "string" ? useGlob : { ...glob$2, glob: useGlob }; } } catch { return glob$2; } return glob$2; } function posixPath(p) { return path$1.sep === "\\" ? p.replaceAll("\\", "/") : p; } async function normalizeFileOrGlobsToRoot(globs, root) { return normalizeGlobsToRoot(await Promise.all(globs.map((g) => adjustPossibleDirectory(g, root))), root, false); } function glob$1(patterns, options) { patterns = typeof patterns === "string" ? workaroundPicomatchBug(patterns) : patterns.map((g) => workaroundPicomatchBug(g)); return glob(patterns, options); } //#endregion //#region src/util/stdin.ts function readStdin() { return readline.createInterface(process.stdin); } //#endregion //#region src/util/stdinUrl.ts function isStdinUrl(url) { if (url instanceof URL) return url.protocol === STDINProtocol; return url.startsWith(STDINProtocol); } /** * Normalize and resolve a stdin url. * @param url - stdin url to resolve. * @param cwd - file path to resolve relative paths against. * @returns */ function resolveStdinUrl(url, cwd) { assert(url.startsWith(STDINProtocol), `Expected url to start with ${STDINProtocol}`); const path$2 = decodeURIComponent(url).slice(STDINProtocol.length).replace(/^\/\//, "").replace(/^\/([a-z]:)/i, "$1"); const fileUrl = toFileURL(path$2, cwd); return new URL(fileUrl.toString().replace(/^file:/, STDINProtocol) + (path$2 ? "" : "/")); } //#endregion //#region src/util/fileHelper.ts function fileInfoToDocument(fileInfo, languageId, locale) { const { filename, text } = fileInfo; languageId = languageId || void 0; locale = locale || void 0; const uri = filenameToUrl(filename); if (uri.href.startsWith(STDINProtocol)) return clean({ uri: uri.href, text, languageId, locale }); return fileToDocument(uri.href, text, languageId, locale); } function filenameToUrl(filename, cwd = ".") { if (filename instanceof URL) return filename; const cwdURL = toFileDirURL(cwd); if (filename === STDIN) return new URL("stdin:///"); if (isStdinUrl(filename)) return new URL(resolveStdinUrl(filename, cwd)); return toFileURL(filename, cwdURL); } function filenameToUri(filename, cwd) { return toURL(filenameToUrl(filename, cwd)); } function isBinaryFile$1(filename, cwd) { const uri = filenameToUri(filename, cwd); if (uri.protocol.startsWith("stdin")) return false; return isBinaryFile(uri); } function resolveFilenameToUrl(filename, cwd) { if (filename instanceof URL) return filename; if (filename === STDIN) return new URL(STDINUrlPrefix); if (filename.startsWith(FileUrlAbsPrefix)) return new URL(filename); const cwdUrl = toFileDirURL(cwd || process.cwd()); if (filename.startsWith(FileUrlPrefix)) return new URL(filename.slice(FileUrlPrefix.length), cwdUrl); if (isStdinUrl(filename)) return resolveStdinUrl(filename, cwdUrl); return toFileURL(filename, cwdUrl); } function resolveFilename(filename, cwd) { return toFilePathOrHref(resolveFilenameToUrl(filename, cwd)); } function readFileInfo(filename, encoding = UTF8, handleNotFound = false) { filename = resolveFilename(filename); return (filename.startsWith(STDINProtocol) ? streamConsumers.text(process.stdin) : readFileText(filename, encoding)).then((text) => ({ text, filename }), (e) => { const error = toError$1(e); return handleNotFound && error.code === "EISDIR" ? Promise.resolve({ text: "", filename, errorCode: error.code }) : handleNotFound && error.code === "ENOENT" ? Promise.resolve({ text: "", filename, errorCode: error.code }) : Promise.reject(new IOError(`Error reading file: "${filename}"`, error)); }); } async function getFileSize(filename) { const s = await getStat(filename); if (!(s instanceof Error)) return s.size; throw s; } function readFile(filename, encoding = UTF8) { return readFileInfo(filename, encoding).then((info) => info.text); } /** * Looks for matching glob patterns or stdin * @param globPatterns patterns or stdin */ async function findFiles(globPatterns, options) { const stdin = []; const globPats = globPatterns.filter((filename) => !isStdin(filename) && !filename.startsWith(FileUrlPrefix) ? true : (stdin.push(filename), false)); const globResults = globPats.length ? await globP(globPats, options) : []; const cwd = options.cw