d11 theme
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

392 lines
16 KiB

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports._testing_ = exports.createInit = exports.checkText = exports.trace = exports.lint = exports.CSpellApplicationConfiguration = exports.MessageTypes = exports.IncludeExcludeFlag = void 0;
const glob = require("glob");
const cspell = require("cspell-lib");
const fsp = require("fs-extra");
const path = require("path");
const commentJson = require("comment-json");
const util = require("./util/util");
const cspell_lib_1 = require("cspell-lib");
const Validator = require("cspell-lib");
const getStdin = require("get-stdin");
var cspell_lib_2 = require("cspell-lib");
Object.defineProperty(exports, "IncludeExcludeFlag", { enumerable: true, get: function () { return cspell_lib_2.IncludeExcludeFlag; } });
const cspell_glob_1 = require("cspell-glob");
// cspell:word nocase
const UTF8 = 'utf8';
const STDIN = 'stdin';
exports.MessageTypes = {
Debug: 'Debug',
Info: 'Info',
Progress: 'Progress',
};
const defaultMinimatchOptions = { nocase: true };
const defaultConfigGlob = '{cspell.json,.cspell.json}';
const defaultConfigGlobOptions = defaultMinimatchOptions;
const nullEmitter = () => {
/* empty */
};
class CSpellApplicationConfiguration {
constructor(files, options, emitters) {
this.files = files;
this.options = options;
this.emitters = emitters;
this.configGlob = defaultConfigGlob;
this.configGlobOptions = defaultConfigGlobOptions;
this.root = path.resolve(options.root || process.cwd());
this.info = emitters.info || nullEmitter;
this.debug = emitters.debug || ((msg) => this.info(msg, exports.MessageTypes.Debug));
this.configFile = options.config;
this.excludes = calcExcludeGlobInfo(this.root, options.exclude);
this.logIssue = emitters.issue || nullEmitter;
this.local = options.local || '';
this.uniqueFilter = options.unique ? util.uniqueFilterFnGenerator((issue) => issue.text) : () => true;
}
}
exports.CSpellApplicationConfiguration = CSpellApplicationConfiguration;
function lint(files, options, emitters) {
const cfg = new CSpellApplicationConfiguration(files, options, emitters);
return runLint(cfg);
}
exports.lint = lint;
function runLint(cfg) {
const configErrors = new Set();
return run();
async function processFile(fileInfo, configInfo) {
const settingsFromCommandLine = util.clean({
languageId: cfg.options.languageId || undefined,
language: cfg.local || undefined,
});
const result = {
fileInfo,
issues: [],
processed: false,
errors: 0,
configErrors: 0,
elapsedTimeMs: 0,
};
const { filename, text } = fileInfo;
const info = calcFinalConfigInfo(configInfo, settingsFromCommandLine, filename, text);
const config = info.configInfo.config;
const source = info.configInfo.source;
cfg.debug(`Filename: ${filename}, Extension: ${path.extname(filename)}, LanguageIds: ${info.languageIds.toString()}`);
if (!info.configInfo.config.enabled)
return result;
result.configErrors += reportConfigurationErrors(info.configInfo.config);
const debugCfg = { config: { ...config, source: null }, source };
cfg.debug(commentJson.stringify(debugCfg, undefined, 2));
const startTime = Date.now();
try {
const wordOffsets = await cspell.validateText(text, info.configInfo.config);
result.processed = true;
result.issues = cspell.Text.calculateTextDocumentOffsets(filename, text, wordOffsets);
}
catch (e) {
cfg.emitters.error(`Failed to process "${filename}"`, e);
result.errors += 1;
}
result.elapsedTimeMs = Date.now() - startTime;
const elapsed = result.elapsedTimeMs / 1000.0;
const dictionaries = config.dictionaries || [];
cfg.info(`Checking: ${filename}, File type: ${config.languageId}, Language: ${config.language} ... Issues: ${result.issues.length} ${elapsed}S`, exports.MessageTypes.Info);
cfg.info(`Dictionaries Used: ${dictionaries.join(', ')}`, exports.MessageTypes.Info);
result.issues.filter(cfg.uniqueFilter).forEach((issue) => cfg.logIssue(issue));
return result;
}
/**
* The file loader is written this way to cause files to be loaded in parallel while the previous one is being processed.
* @param fileNames names of files to load one at a time.
*/
function* fileLoader(fileNames) {
for (const filename of fileNames) {
// console.log(`${Date.now()} Start reading ${filename}`);
const file = readFileInfo(filename);
// .then(f => (console.log(`${Date.now()} Loaded ${filename} (${f.text.length / 1024}K)`), f))
// console.log(`${Date.now()} Waiting for request ${filename}`);
yield file;
// console.log(`${Date.now()} Yield ${filename}`);
}
}
async function processFiles(files, configInfo) {
const status = runResult();
for (const fileP of files) {
const file = await fileP;
if (!file || !file.text) {
continue;
}
const r = await processFile(file, configInfo);
status.files += 1;
if (r.issues.length || r.errors) {
status.filesWithIssues.add(file.filename);
status.issues += r.issues.length;
status.errors += r.errors;
}
status.errors += r.configErrors;
}
return status;
}
function reportConfigurationErrors(config) {
const errors = cspell.extractImportErrors(config);
let count = 0;
errors.forEach((ref) => {
const key = ref.error.toString();
if (configErrors.has(key))
return;
configErrors.add(key);
count += 1;
cfg.emitters.error('Configuration', ref.error);
});
return count;
}
async function readConfig() {
if (cfg.configFile) {
const config = cspell.readSettings(cfg.configFile);
return { source: cfg.configFile, config };
}
const configFiles = (await globP(cfg.configGlob, cfg.configGlobOptions)).filter(util.uniqueFn());
cfg.info(`Config Files Found:\n ${configFiles.join('\n ')}\n`, exports.MessageTypes.Info);
const config = cspell.readSettingsFiles(configFiles);
return { source: configFiles.join(' || '), config };
}
function countConfigErrors(configInfo) {
return reportConfigurationErrors(configInfo.config);
}
async function run() {
header();
const configInfo = await readConfig();
const configErrors = countConfigErrors(configInfo);
if (configErrors)
return runResult({ errors: configErrors });
// Get Exclusions from the config files.
const { root } = cfg;
const globOptions = { root, cwd: root };
const exclusionGlobs = extractGlobExcludesFromConfig(root, configInfo.source, configInfo.config).concat(cfg.excludes);
const files = filterFiles(await findFiles(cfg.files, globOptions), exclusionGlobs);
return processFiles(fileLoader(files), configInfo);
}
function header() {
cfg.info(`
cspell;
Date: ${new Date().toUTCString()}
Options:
verbose: ${yesNo(!!cfg.options.verbose)}
config: ${cfg.configGlob}
exclude: ${extractPatterns(cfg.excludes)
.map((a) => a.glob)
.join('\n ')}
files: ${cfg.files}
wordsOnly: ${yesNo(!!cfg.options.wordsOnly)}
unique: ${yesNo(!!cfg.options.unique)}
`, exports.MessageTypes.Info);
}
function isExcluded(filename, globs) {
const { root } = cfg;
const absFilename = path.resolve(root, filename);
for (const glob of globs) {
const m = glob.matcher.matchEx(absFilename);
if (m.matched) {
cfg.info(`Excluded File: ${path.relative(root, absFilename)}; Excluded by ${m.glob} from ${glob.source}`, exports.MessageTypes.Info);
return true;
}
}
return false;
}
function filterFiles(files, excludeGlobs) {
const excludeInfo = extractPatterns(excludeGlobs).map((g) => `Glob: ${g.glob} from ${g.source}`);
cfg.info(`Exclusion Globs: \n ${excludeInfo.join('\n ')}\n`, exports.MessageTypes.Info);
const result = files.filter((filename) => !isExcluded(filename, excludeGlobs));
return result;
}
}
function runResult(init = {}) {
const { files = 0, filesWithIssues = new Set(), issues = 0, errors = 0 } = init;
return { files, filesWithIssues, issues, errors };
}
function extractPatterns(globs) {
const r = globs.reduce((info, g) => {
const source = g.source;
const patterns = typeof g.matcher.patterns === 'string' ? [g.matcher.patterns] : g.matcher.patterns;
return info.concat(patterns.map((glob) => ({ glob, source })));
}, []);
return r;
}
async function trace(words, options) {
const configGlob = options.config || defaultConfigGlob;
const configGlobOptions = options.config ? {} : defaultConfigGlobOptions;
const configFiles = (await globP(configGlob, configGlobOptions)).filter(util.uniqueFn());
const config = cspell.mergeSettings(cspell.getDefaultSettings(), cspell.getGlobalSettings(), cspell.readSettingsFiles(configFiles));
const results = await cspell_lib_1.traceWords(words, config);
return results;
}
exports.trace = trace;
async function checkText(filename, options) {
const configGlob = options.config || defaultConfigGlob;
const configGlobOptions = options.config ? {} : defaultConfigGlobOptions;
const pSettings = globP(configGlob, configGlobOptions).then((filenames) => ({
source: filenames[0],
config: cspell.readSettingsFiles(filenames),
}));
const [foundSettings, text] = await Promise.all([pSettings, readFile(filename)]);
const settingsFromCommandLine = util.clean({
languageId: options.languageId || undefined,
local: options.local || undefined,
});
const info = calcFinalConfigInfo(foundSettings, settingsFromCommandLine, filename, text);
return Validator.checkText(text, info.configInfo.config);
}
exports.checkText = checkText;
function createInit(_) {
return Promise.reject();
}
exports.createInit = createInit;
const defaultExcludeGlobs = ['node_modules/**'];
function readFileInfo(filename, encoding = UTF8) {
const pText = filename === STDIN ? getStdin() : fsp.readFile(filename, encoding);
return pText.then((text) => ({ text, filename }), (error) => {
return error.code === 'EISDIR'
? Promise.resolve({ text: '', filename })
: Promise.reject({ ...error, message: `Error reading file: "${filename}"` });
});
}
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 globPats = globPatterns.filter((filename) => filename !== STDIN);
const stdin = globPats.length < globPatterns.length ? [STDIN] : [];
const globResults = globPats.length ? await globP(globPats, options) : [];
const cwd = options.cwd || process.cwd();
return stdin.concat(globResults.map((filename) => path.resolve(cwd, filename)));
}
function calcExcludeGlobInfo(root, commandLineExclude) {
const commandLineExcludes = {
globs: commandLineExclude ? commandLineExclude.split(/\s+/g) : [],
source: 'arguments',
};
const defaultExcludes = {
globs: defaultExcludeGlobs,
source: 'default',
};
const choice = commandLineExcludes.globs.length ? commandLineExcludes : defaultExcludes;
const matcher = new cspell_glob_1.GlobMatcher(choice.globs, root);
return [
{
matcher,
source: choice.source,
},
];
}
function extractGlobExcludesFromConfig(root, source, config) {
if (!config.ignorePaths || !config.ignorePaths.length) {
return [];
}
const matcher = new cspell_glob_1.GlobMatcher(config.ignorePaths, root);
return [{ source, matcher }];
}
function calcFinalConfigInfo(configInfo, settingsFromCommandLine, filename, text) {
const ext = path.extname(filename);
const fileSettings = cspell.calcOverrideSettings(configInfo.config, path.resolve(filename));
const settings = cspell.mergeSettings(cspell.getDefaultSettings(), cspell.getGlobalSettings(), fileSettings, settingsFromCommandLine);
const languageIds = settings.languageId ? [settings.languageId] : cspell.getLanguagesForExt(ext);
const config = cspell.constructSettingsForText(settings, text, languageIds);
return { configInfo: { ...configInfo, config }, filename, text, languageIds };
}
function yesNo(value) {
return value ? 'Yes' : 'No';
}
function findBaseDir(pat) {
const globChars = /[*@()?|[\]{},]/;
while (globChars.test(pat)) {
pat = path.dirname(pat);
}
return pat;
}
function exists(filename) {
try {
fsp.accessSync(filename);
}
catch (e) {
return false;
}
return true;
}
/**
* Attempt to normalize a pattern based upon the root.
* If the pattern is absolute, check to see if it exists and adjust the root, otherwise it is assumed to be based upon the current root.
* If the pattern starts with a relative path, adjust the root to match.
* The challenge is with the patterns that begin with `/`. Is is an absolute path or relative pattern?
* @param pat glob pattern
* @param root absolute path | empty
* @returns the adjusted root and pattern.
*/
function normalizePattern(pat, root) {
// Absolute pattern
if (path.isAbsolute(pat)) {
const dir = findBaseDir(pat);
if (dir.length > 1 && exists(dir)) {
// Assume it is an absolute path
return {
pattern: pat,
root: path.sep,
};
}
}
// normal pattern
if (!/^\.\./.test(pat)) {
return {
pattern: pat,
root,
};
}
// relative pattern
pat = path.sep === '\\' ? pat.replace(/\\/g, '/') : pat;
const patParts = pat.split('/');
const rootParts = root.split(path.sep);
let i = 0;
for (; i < patParts.length && patParts[i] === '..'; ++i) {
rootParts.pop();
}
return {
pattern: patParts.slice(i).join('/'),
root: rootParts.join(path.sep),
};
}
async function globP(pattern, options) {
const root = (options === null || options === void 0 ? void 0 : options.root) || process.cwd();
const opts = options || {};
const rawPatterns = typeof pattern === 'string' ? [pattern] : pattern;
const normPatterns = rawPatterns.map((pat) => normalizePattern(pat, root));
const globResults = normPatterns.map(async (pat) => {
const opt = { ...opts, root: pat.root, cwd: pat.root };
const absolutePaths = (await _globP(pat.pattern, opt)).map((filename) => path.resolve(pat.root, filename));
const relativeToRoot = absolutePaths.map((absFilename) => path.relative(root, absFilename));
return relativeToRoot;
});
const results = (await Promise.all(globResults)).reduce((prev, next) => prev.concat(next), []);
return results;
}
function _globP(pattern, options) {
if (!pattern) {
return Promise.resolve([]);
}
return new Promise((resolve, reject) => {
const cb = (err, matches) => {
if (err) {
reject(err);
}
resolve(matches);
};
glob(pattern, options, cb);
});
}
exports._testing_ = {
_globP,
findFiles,
normalizePattern,
};
//# sourceMappingURL=application.js.map