cspell
Version:
A Spelling Checker for Code!
182 lines • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const glob = require("glob");
const minimatch = require("minimatch");
const cspell = require("./index");
const fsp = require("fs-extra");
const path = require("path");
const commentJson = require("comment-json");
const util = require("./util/util");
const index_1 = require("./index");
const Validator = require("./validator");
// cspell:word nocase
const UTF8 = 'utf8';
var index_2 = require("./index");
exports.IncludeExcludeFlag = index_2.IncludeExcludeFlag;
const matchBase = { matchBase: true };
const defaultMinimatchOptions = { nocase: true };
const defaultConfigGlob = '{cspell.json,.cspell.json}';
const defaultConfigGlobOptions = defaultMinimatchOptions;
class CSpellApplicationConfiguration {
constructor(files, options, emitters) {
this.files = files;
this.options = options;
this.emitters = emitters;
this.configGlob = defaultConfigGlob;
this.configGlobOptions = defaultConfigGlobOptions;
this.info = emitters.info || this.info;
this.debug = emitters.debug || this.debug;
this.configGlob = options.config || this.configGlob;
this.configGlobOptions = options.config ? {} : this.configGlobOptions;
this.excludes = calcExcludeGlobInfo(options.exclude);
this.logIssue = emitters.issue || this.logIssue;
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) {
return run();
function run() {
header();
const configRx = globRx(cfg.configGlob, cfg.configGlobOptions).pipe(operators_1.map(util.unique), operators_1.tap(configFiles => cfg.info(`Config Files Found:\n ${configFiles.join('\n ')}\n`)), operators_1.map((filenames) => ({ filename: filenames.join(' || '), config: cspell.readSettingsFiles(filenames) })), operators_1.map(config => {
if (cfg.local) {
config.config.language = cfg.local;
}
return config;
}), operators_1.share());
// Get Exclusions from the config files.
const exclusionGlobs = configRx.pipe(operators_1.map(({ filename, config }) => extractGlobExcludesFromConfig(filename, config)), operators_1.flatMap(a => a), operators_1.toArray(), operators_1.map(a => a.concat(cfg.excludes))).toPromise();
const filesRx = filterFiles(findFiles(cfg.files), exclusionGlobs).pipe(operators_1.flatMap(filename => {
return fsp.readFile(filename).then(text => ({ text: text.toString(), filename }), error => {
return error.code === 'EISDIR'
? Promise.resolve(undefined)
: Promise.reject(Object.assign({}, error, { message: `Error reading file: "${filename}"` }));
});
}), operators_1.filter(a => !!a), operators_1.map(a => a));
const status = {
files: 0,
filesWithIssues: new Set(),
issues: 0,
};
const r = rxjs_1.combineLatest(configRx, filesRx, (configInfo, fileInfo) => ({ configInfo, text: fileInfo.text, filename: fileInfo.filename })).pipe(operators_1.map(({ configInfo, 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);
const languageIds = settings.languageId ? [settings.languageId] : cspell.getLanguagesForExt(ext);
const config = cspell.constructSettingsForText(settings, text, languageIds);
cfg.debug(`Filename: ${filename}, Extension: ${ext}, LanguageIds: ${languageIds.toString()}`);
return { configInfo: Object.assign({}, configInfo, { config }), filename, text };
}), operators_1.filter(info => info.configInfo.config.enabled !== false), operators_1.tap(() => status.files += 1), operators_1.flatMap((info) => {
const { configInfo, filename, text } = info;
const debugCfg = { config: Object.assign({}, configInfo.config, { source: null }), filename: configInfo.filename };
cfg.debug(commentJson.stringify(debugCfg, undefined, 2));
return cspell.validateText(text, configInfo.config)
.then(wordOffsets => {
return {
filename,
issues: cspell.Text.calculateTextDocumentOffsets(filename, text, wordOffsets),
config: configInfo.config,
};
});
}), operators_1.tap(info => {
const { filename, issues, config } = info;
const dictionaries = (config.dictionaries || []);
cfg.info(`Checking: ${filename}, File type: ${config.languageId}, Language: ${config.language} ... Issues: ${issues.length}`);
cfg.info(`Dictionaries Used: ${dictionaries.join(', ')}`);
issues
.filter(cfg.uniqueFilter)
.forEach((issue) => cfg.logIssue(issue));
}), operators_1.filter(info => !!info.issues.length), operators_1.tap(issue => status.filesWithIssues.add(issue.filename)), operators_1.reduce((status, info) => (Object.assign({}, status, { issues: status.issues + info.issues.length })), status)).toPromise();
return r;
}
function header() {
cfg.info(`
cspell;
Date: ${(new Date()).toUTCString()}
Options:
verbose: ${yesNo(!!cfg.options.verbose)}
config: ${cfg.configGlob}
exclude: ${cfg.excludes.map(a => a.glob).join('\n ')}
files: ${cfg.files}
wordsOnly: ${yesNo(!!cfg.options.wordsOnly)}
unique: ${yesNo(!!cfg.options.unique)}
`);
}
function isExcluded(filename, globs) {
const cwd = process.cwd();
const relFilename = (filename.slice(0, cwd.length) === cwd) ? filename.slice(cwd.length) : filename;
for (const glob of globs) {
if (glob.regex.test(relFilename)) {
cfg.info(`Excluded File: ${filename}; Excluded by ${glob.glob} from ${glob.source}`);
return true;
}
}
return false;
}
function filterFiles(files, excludeGlobs) {
excludeGlobs.then(excludeGlobs => {
const excludeInfo = excludeGlobs.map(g => `Glob: ${g.glob} from ${g.source}`);
cfg.info(`Exclusion Globs: \n ${excludeInfo.join('\n ')}\n`);
});
return rxjs_1.combineLatest(files, excludeGlobs, (filename, globs) => ({ filename, globs })).pipe(operators_1.filter(({ filename, globs }) => !isExcluded(filename, globs)), operators_1.map(({ filename }) => filename));
}
}
async function trace(words, options) {
const configGlob = options.config || defaultConfigGlob;
const configGlobOptions = options.config ? {} : defaultConfigGlobOptions;
const results = await globRx(configGlob, configGlobOptions).pipe(operators_1.map(util.unique), operators_1.map(filenames => ({ filename: filenames.join(' || '), config: cspell.readSettingsFiles(filenames) })), operators_1.map(({ filename, config }) => ({ filename, config: cspell.mergeSettings(cspell.getDefaultSettings(), cspell.getGlobalSettings(), config) })), operators_1.flatMap(config => index_1.traceWords(words, config.config)), operators_1.toArray()).toPromise();
return results;
}
exports.trace = trace;
async function checkText(filename, options) {
const configGlob = options.config || defaultConfigGlob;
const configGlobOptions = options.config ? {} : defaultConfigGlobOptions;
const pSettings = globRx(configGlob, configGlobOptions).pipe(operators_1.first(), operators_1.map(util.unique), operators_1.map(filenames => cspell.readSettingsFiles(filenames))).toPromise();
const pBuffer = fsp.readFile(filename);
const [foundSettings, buffer] = await Promise.all([pSettings, pBuffer]);
const text = buffer.toString(UTF8);
const ext = path.extname(filename);
const fileSettings = cspell.calcOverrideSettings(foundSettings, path.resolve(filename));
const settings = cspell.mergeSettings(cspell.getDefaultSettings(), cspell.getGlobalSettings(), fileSettings);
const languageIds = settings.languageId ? [settings.languageId] : cspell.getLanguagesForExt(ext);
const config = cspell.constructSettingsForText(settings, text, languageIds);
return Validator.checkText(text, config);
}
exports.checkText = checkText;
function createInit(_) {
return Promise.resolve();
}
exports.createInit = createInit;
const defaultExcludeGlobs = [
'node_modules/**'
];
function findFiles(globPatterns) {
const processed = new Set();
return rxjs_1.from(globPatterns).pipe(operators_1.flatMap(pattern => globRx(pattern)
.pipe(operators_1.catchError((error) => {
return new Promise((resolve) => resolve(Promise.reject(Object.assign({}, error, { message: 'Error with glob search.' }))));
}))), operators_1.flatMap(a => a), operators_1.filter(filename => !processed.has(filename)), operators_1.tap(filename => processed.add(filename)));
}
function calcExcludeGlobInfo(commandLineExclude) {
const excludes = commandLineExclude && commandLineExclude.split(/\s+/g).map(glob => ({ glob, source: 'arguments' }))
|| defaultExcludeGlobs.map(glob => ({ glob, source: 'default' }));
return excludes.map(({ source, glob }) => ({ source, glob, regex: minimatch.makeRe(glob, matchBase) }));
}
function extractGlobExcludesFromConfig(filename, config) {
return (config.ignorePaths || []).map(glob => ({ source: filename, glob, regex: minimatch.makeRe(glob, matchBase) }));
}
function yesNo(value) {
return value ? 'Yes' : 'No';
}
const globRx = rxjs_1.bindNodeCallback(glob);
//# sourceMappingURL=application.js.map