cspell
Version:
A Spelling Checker for Code!
1,436 lines • 117 kB
JavaScript
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, createDictionaryReferenceCollection, createPerfTimer, extractDependencies, extractImportErrors, fileToDocument, getDefaultSettings, 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 { enablePerformanceMeasurements, measurePerf } from "@cspell/cspell-performance-monitor";
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 { dynamicImport } from "@cspell/dynamic-import";
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";
//#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) {
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,
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 = "…") {
if (!maxWidth || maxWidth <= 0) return str;
if (str.length <= maxWidth) return str;
if (ansiWidth(str) <= maxWidth) return str;
const padWidth = ansiWidth(pad);
const fragments = parseAnsiStr(str);
let remaining = maxWidth - padWidth;
for (const frag of fragments) {
if (frag.type !== "text") continue;
if (remaining <= 0) {
frag.text = "";
continue;
}
const pruned = pruneTextEnd(frag.text, remaining, pad);
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 = "…") {
if (!maxWidth || maxWidth <= 0) return str;
if (str.length <= maxWidth) return str;
if (ansiWidth(str) <= maxWidth) return str;
const padWidth = ansiWidth(pad);
const fragments = parseAnsiStr(str);
let remaining = maxWidth - padWidth;
for (const frag of fragments.reverse()) {
if (frag.type !== "text") continue;
if (remaining <= 0) {
frag.text = "";
continue;
}
const pruned = pruneTextStart(frag.text, remaining, pad);
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 = "…") {
if (!maxWidth || maxWidth <= 0) return str;
if (str.length <= maxWidth) return str;
if (isAnsiString(str)) return pruneAnsiTextEnd(str, maxWidth, pad);
const maxWidthWithPad = maxWidth - width(pad);
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;
}
}
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 = "…") {
if (!maxWidth || maxWidth <= 0) return str;
if (str.length <= maxWidth) return str;
const maxWidthWithPad = maxWidth - width(pad);
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 + 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) {
header.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) {
return tableIndent + decorateRowWith(header.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() {
if (table.title) yield table.title;
yield toHeaderLine(simpleHeader);
yield* rows.map(toLine);
}
function sumColumnWidths() {
return columnWidths.reduce((sum, width) => sum + width, 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()];
}
function headerDecorator(t) {
return chalk.bold(chalk.underline(t));
}
function decorateRowWith(row, ...decorators) {
return decorators.reduce((row, decorator) => row.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,
accumulatedTimeMs: 0,
startTime: performance.now(),
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, cachedFiles, filesWithIssues, errors, skippedFiles } = result;
const numFilesWithIssues = filesWithIssues.size;
const chalk = 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} in ${numFilesWithIssues === 1 ? "1 file" : `${numFilesWithIssues} files`}${withErrorsText}.`);
if (errorCollection?.length && issues > 5) {
consoleError("-------------------------------------------");
consoleError("Errors:");
errorCollection.forEach((error) => consoleError(error));
}
if (options.showPerfSummary) {
const elapsedTotal = performance.now() - perfStats.startTime;
consoleError("-------------------------------------------");
consoleError("Performance Summary:");
consoleError(` Files Processed : ${perfStats.filesProcessed.toString().padStart(11)}`);
consoleError(` Files Skipped : ${perfStats.filesSkipped.toString().padStart(11)}`);
consoleError(` Files Cached : ${perfStats.filesCached.toString().padStart(11)}`);
consoleError(` Processing Time : ${perfStats.accumulatedTimeMs.toFixed(2).padStart(9)}ms`);
consoleError(` Total Time : ${elapsedTotal.toFixed(2).padStart(9)}ms`);
const tableStats = {
title: chalk.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 = 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, m.depth, text);
const cNotable = (text) => notable.has(m) ? chalk.yellow(text) : text;
return [
chalk.dim("⋅".repeat(m.depth)) + colorByDepthGrayscale(stderr.chalk, m.depth, m.name),
cbd(m.totalTimeMs.toFixed(2) + chalk.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.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, depth, text) {
const colors = [
chalk.green,
chalk.cyan,
chalk.blue,
chalk.magenta,
chalk.red
];
const color = colors[depth % colors.length];
if (depth / colors.length >= 1) return chalk.dim(color(text));
return color(text);
}
function colorByDepthGrayscale(chalk, depth, text) {
const grayLevel = Math.max(32, 255 - depth * 20);
return chalk.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.accumulatedTimeMs += 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(t) {
return t.replace(/\s+/, " ");
}
const { uri = "", filename, row, col, text, context = issue.line, offset } = issue;
const contextLeft = clean(context.text.slice(0, offset - context.offset));
const contextRight = clean(context.text.slice(offset + text.length - context.offset));
const contextFull = clean(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 = 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,
$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 = text.indexOf(match); i >= 0; i = text.indexOf(match, i)) {
const end = i + len;
const reg = /\b/y;
reg.lastIndex = end;
if (reg.test(text)) subs.push([
i,
end,
replaceWith
]);
i = 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) {
try {
require.resolve(name, { 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 = configFile.getNode("dictionaries", []);
assert(isCfgArrayNode(dicts));
const knownDicts = new Set(dicts.value);
for (const dict of dictionaries) if (!knownDicts.has(dict)) {
dicts.push(dict);
knownDicts.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 = outputUrl.pathname;
if (path.endsWith(".json") || path.endsWith(".jsonc") || path.endsWith(".yaml") || path.endsWith(".yml")) return outputUrl;
if (/\.{m,c}?{j,t}s$/.test(path)) throw new Error(`Unsupported file extension: ${path}`);
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, value] = flag;
try {
featureFlags.setFlag(name, value);
} catch {
console.warn(`Unknown flag: "${name}"`);
}
}
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;
const npmPackage = {
name: "cspell",
version: "10.0.0",
engines: { node: ">=22.18.0" }
};
//#endregion
//#region src/reporters/reporters.ts
function filterFeatureIssues(features, issue, reportOptions) {
if (issue.issueType === IssueType.directive) return features?.issueType && reportOptions?.validateDirectives || false;
if (features?.unknownWords) return true;
if (!reportOptions) return true;
if (issue.isFlagged || !reportOptions.unknownWords || reportOptions.unknownWords === unknownWordsChoices.ReportAll) return true;
if (issue.hasPreferredSuggestions && reportOptions.unknownWords !== unknownWordsChoices.ReportFlagged) return true;
if (issue.hasSimpleSuggestions && reportOptions.unknownWords === unknownWordsChoices.ReportSimple) return true;
return false;
}
function handleIssue(reporter, issue, reportOptions) {
if (!reporter.issue) return;
if (!filterFeatureIssues(reporter.features, issue, reportOptions)) return;
if (!reporter.features?.contextGeneration && !issue.context) {
issue = { ...issue };
issue.context = issue.line;
}
return reporter.issue(issue, reportOptions);
}
/**
* Loads reporter modules configured in cspell config file
*/
async function loadReporters(reporters, defaultReporter, config) {
async function loadReporter(reporterSettings) {
if (reporterSettings === "default") return defaultReporter;
if (!Array.isArray(reporterSettings)) reporterSettings = [reporterSettings];
const [moduleName, settings] = reporterSettings;
try {
const { getReporter } = await dynamicImport(moduleName, [process.cwd(), pkgDir]);
return getReporter(settings, config);
} catch (e) {
throw new ApplicationError(`Failed to load reporter ${moduleName}: ${toError$1(e).message}`);
}
}
reporters = !reporters || !reporters.length ? ["default"] : [...reporters];
return (await Promise.all(reporters.map(loadReporter))).filter((v) => v !== void 0);
}
function finalizeReporter(reporter) {
if (!reporter) return void 0;
if (reporterIsFinalized(reporter)) return reporter;
return {
issue: (...params) => reporter.issue?.(...params),
info: (...params) => reporter.info?.(...params),
debug: (...params) => reporter.debug?.(...params),
progress: (...params) => reporter.progress?.(...params),
error: (...params) => reporter.error?.(...params),
result: (...params) => reporter.result?.(...params),
features: reporter.features
};
}
function reporterIsFinalized(reporter) {
return !!reporter && reporter.features && typeof reporter.issue === "function" && typeof reporter.info === "function" && typeof reporter.debug === "function" && typeof reporter.error === "function" && typeof reporter.progress === "function" && typeof reporter.result === "function" || false;
}
const reportIssueOptionsKeyMap = {
unknownWords: "unknownWords",
validateDirectives: "validateDirectives",
showContext: "showContext"
};
function setValue(options, key, value) {
if (value !== void 0) options[key] = value;
}
function extractReporterIssueOptions(settings) {
const src = settings;
const options = {};
for (const key in reportIssueOptionsKeyMap) {
const k = key;
setValue(options, k, src[k]);
}
return options;
}
function mergeReportIssueOptions(a, b) {
const options = extractReporterIssueOptions(a);
if (!b) return options;
for (const key in reportIssueOptionsKeyMap) {
const k = key;
setValue(options, k, b[k]);
}
return options;
}
var LintReporter = class {
#reporters = [];
#config;
#finalized = false;
constructor(defaultReporter, config) {
this.defaultReporter = defaultReporter;
this.#config = config;
if (defaultReporter) this.#reporters.push(finalizeReporter(defaultReporter));
}
get config() {
return this.#config;
}
set config(config) {
assert(!this.#finalized, "Cannot change the configuration of a finalized reporter");
this.#config = config;
}
issue(issue, reportOptions) {
for (const reporter of this.#reporters) handleIssue(reporter, issue, reportOptions);
}
info(...params) {
for (const reporter of this.#reporters) reporter.info(...params);
}
debug(...params) {
for (const reporter of this.#reporters) reporter.debug(...params);
}
error(...params) {
for (const reporter of this.#reporters) reporter.error(...params);
}
progress(...params) {
for (const reporter of this.#reporters) reporter.progress(...params);
}
async result(result) {
await Promise.all(this.#reporters.map((reporter) => reporter.result?.(result)));
}
get features() {
return {
unknownWords: true,
issueType: true
};
}
async loadReportersAndFinalize(reporters) {
assert(!this.#finalized, "Cannot change the configuration of a finalized reporter");
const loaded = await loadReporters(reporters, this.defaultReporter, this.config);
this.#reporters = [...new Set(loaded)].map((reporter) => finalizeReporter(reporter));
}
emitProgressBegin(filename, fileNum, fileCount) {
this.progress({
type: "ProgressFileBegin",
fileNum,
fileCount,
filename
});
}
emitProgressComplete(filename, fileNum, fileCount, result) {
const numIssues = result.issues.filter((issue) => filterFeatureIssues({}, issue, result.reportIssueOptions)).length;
for (const reporter of this.#reporters) {
const progress = clean({
type: "ProgressFileComplete",
fileNum,
fileCount,
filename,
elapsedTimeMs: result.elapsedTimeMs,
processed: result.processed,
skippedReason: result.skippedReason,
numErrors: numIssues || result.errors,
cached: result.cached,
perf: result.perf,
issues: reporter.features && result.issues,
reportIssueOptions: reporter.features && result.reportIssueOptions
});
reporter.progress(progress);
}
result.issues.forEach((issue) => this.issue(issue, result.reportIssueOptions));
return numIssues;
}
};
var ReportItemCollector = class {
#collection;
constructor(collection) {
this.#collection = collection;
}
get #items() {
if (!this.#collection.reportItems) this.#collection.reportItems = [];
return this.#collection.reportItems;
}
get items() {
return this.#collection.reportItems;
}
info(...params) {
this.#items.push({
type: "info",
payload: params
});
}
debug(...params) {
this.#items.push({
type: "debug",
payload: params
});
}
error(...params) {
this.#items.push({
type: "error",
payload: params
});
}
};
function replayReportItems(reportItemsCollection, reporter) {
const items = reportItemsCollection.reportItems;
if (!items) return;
for (const item of items) switch (item.type) {
case "info":
reporter.info(...item.payload);
break;
case "debug":
reporter.debug(...item.payload);
break;
case "error":
reporter.error(...item.payload);
break;
}
}
//#endregion
//#region src/util/async.ts
const asyncMap = operators.opMapAsync;
operators.opFilterAsync;
const asyncAwait = operators.opAwaitAsync;
const asyncFlatten = operators.opFlattenAsync;
//#endregion
//#region src/util/constants.ts
const UTF8 = "utf8";
const STDINProtocol = "stdin:";
const STDINUrlPrefix = "stdin://";
//#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 = new Intl.Collator("en").compare;
return (await glob$1(patterns, useOptions)).sort(compare).map((absFilename) => path$1.relative(cwd, absFilename));
}
function calcGlobs(commandLineExclude) {
const commandLineExcludes = {
globs: [...new Set((commandLineExclude || []).flatMap((glob) => glob.split(/(?<!\\)\s+/g)).map((g) => g.replaceAll("\\ ", " ")))],
source: "arguments"
};
const defaultExcludes = {
globs: defaultExcludeGlobs,
source: "def