UNPKG

16.2 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports._testing_ = exports.createInit = exports.checkText = exports.trace = exports.lint = exports.CSpellApplicationConfiguration = exports.MessageTypes = exports.IncludeExcludeFlag = void 0;
4const glob = require("glob");
5const cspell = require("cspell-lib");
6const fsp = require("fs-extra");
7const path = require("path");
8const commentJson = require("comment-json");
9const util = require("./util/util");
10const cspell_lib_1 = require("cspell-lib");
11const Validator = require("cspell-lib");
12const getStdin = require("get-stdin");
13var cspell_lib_2 = require("cspell-lib");
14Object.defineProperty(exports, "IncludeExcludeFlag", { enumerable: true, get: function () { return cspell_lib_2.IncludeExcludeFlag; } });
15const cspell_glob_1 = require("cspell-glob");
16// cspell:word nocase
17const UTF8 = 'utf8';
18const STDIN = 'stdin';
19exports.MessageTypes = {
20 Debug: 'Debug',
21 Info: 'Info',
22 Progress: 'Progress',
23};
24const defaultMinimatchOptions = { nocase: true };
25const defaultConfigGlob = '{cspell.json,.cspell.json}';
26const defaultConfigGlobOptions = defaultMinimatchOptions;
27const nullEmitter = () => {
28 /* empty */
29};
30class CSpellApplicationConfiguration {
31 constructor(files, options, emitters) {
32 this.files = files;
33 this.options = options;
34 this.emitters = emitters;
35 this.configGlob = defaultConfigGlob;
36 this.configGlobOptions = defaultConfigGlobOptions;
37 this.root = path.resolve(options.root || process.cwd());
38 this.info = emitters.info || nullEmitter;
39 this.debug = emitters.debug || ((msg) => this.info(msg, exports.MessageTypes.Debug));
40 this.configFile = options.config;
41 this.excludes = calcExcludeGlobInfo(this.root, options.exclude);
42 this.logIssue = emitters.issue || nullEmitter;
43 this.local = options.local || '';
44 this.uniqueFilter = options.unique ? util.uniqueFilterFnGenerator((issue) => issue.text) : () => true;
45 }
46}
47exports.CSpellApplicationConfiguration = CSpellApplicationConfiguration;
48function lint(files, options, emitters) {
49 const cfg = new CSpellApplicationConfiguration(files, options, emitters);
50 return runLint(cfg);
51}
52exports.lint = lint;
53function runLint(cfg) {
54 const configErrors = new Set();
55 return run();
56 async function processFile(fileInfo, configInfo) {
57 const settingsFromCommandLine = util.clean({
58 languageId: cfg.options.languageId || undefined,
59 language: cfg.local || undefined,
60 });
61 const result = {
62 fileInfo,
63 issues: [],
64 processed: false,
65 errors: 0,
66 configErrors: 0,
67 elapsedTimeMs: 0,
68 };
69 const { filename, text } = fileInfo;
70 const info = calcFinalConfigInfo(configInfo, settingsFromCommandLine, filename, text);
71 const config = info.configInfo.config;
72 const source = info.configInfo.source;
73 cfg.debug(`Filename: ${filename}, Extension: ${path.extname(filename)}, LanguageIds: ${info.languageIds.toString()}`);
74 if (!info.configInfo.config.enabled)
75 return result;
76 result.configErrors += reportConfigurationErrors(info.configInfo.config);
77 const debugCfg = { config: { ...config, source: null }, source };
78 cfg.debug(commentJson.stringify(debugCfg, undefined, 2));
79 const startTime = Date.now();
80 try {
81 const wordOffsets = await cspell.validateText(text, info.configInfo.config);
82 result.processed = true;
83 result.issues = cspell.Text.calculateTextDocumentOffsets(filename, text, wordOffsets);
84 }
85 catch (e) {
86 cfg.emitters.error(`Failed to process "${filename}"`, e);
87 result.errors += 1;
88 }
89 result.elapsedTimeMs = Date.now() - startTime;
90 const elapsed = result.elapsedTimeMs / 1000.0;
91 const dictionaries = config.dictionaries || [];
92 cfg.info(`Checking: ${filename}, File type: ${config.languageId}, Language: ${config.language} ... Issues: ${result.issues.length} ${elapsed}S`, exports.MessageTypes.Info);
93 cfg.info(`Dictionaries Used: ${dictionaries.join(', ')}`, exports.MessageTypes.Info);
94 result.issues.filter(cfg.uniqueFilter).forEach((issue) => cfg.logIssue(issue));
95 return result;
96 }
97 /**
98 * The file loader is written this way to cause files to be loaded in parallel while the previous one is being processed.
99 * @param fileNames names of files to load one at a time.
100 */
101 function* fileLoader(fileNames) {
102 for (const filename of fileNames) {
103 // console.log(`${Date.now()} Start reading ${filename}`);
104 const file = readFileInfo(filename);
105 // .then(f => (console.log(`${Date.now()} Loaded ${filename} (${f.text.length / 1024}K)`), f))
106 // console.log(`${Date.now()} Waiting for request ${filename}`);
107 yield file;
108 // console.log(`${Date.now()} Yield ${filename}`);
109 }
110 }
111 async function processFiles(files, configInfo) {
112 const status = runResult();
113 for (const fileP of files) {
114 const file = await fileP;
115 if (!file || !file.text) {
116 continue;
117 }
118 const r = await processFile(file, configInfo);
119 status.files += 1;
120 if (r.issues.length || r.errors) {
121 status.filesWithIssues.add(file.filename);
122 status.issues += r.issues.length;
123 status.errors += r.errors;
124 }
125 status.errors += r.configErrors;
126 }
127 return status;
128 }
129 function reportConfigurationErrors(config) {
130 const errors = cspell.extractImportErrors(config);
131 let count = 0;
132 errors.forEach((ref) => {
133 const key = ref.error.toString();
134 if (configErrors.has(key))
135 return;
136 configErrors.add(key);
137 count += 1;
138 cfg.emitters.error('Configuration', ref.error);
139 });
140 return count;
141 }
142 async function readConfig() {
143 if (cfg.configFile) {
144 const config = cspell.readSettings(cfg.configFile);
145 return { source: cfg.configFile, config };
146 }
147 const configFiles = (await globP(cfg.configGlob, cfg.configGlobOptions)).filter(util.uniqueFn());
148 cfg.info(`Config Files Found:\n ${configFiles.join('\n ')}\n`, exports.MessageTypes.Info);
149 const config = cspell.readSettingsFiles(configFiles);
150 return { source: configFiles.join(' || '), config };
151 }
152 function countConfigErrors(configInfo) {
153 return reportConfigurationErrors(configInfo.config);
154 }
155 async function run() {
156 header();
157 const configInfo = await readConfig();
158 const configErrors = countConfigErrors(configInfo);
159 if (configErrors)
160 return runResult({ errors: configErrors });
161 // Get Exclusions from the config files.
162 const { root } = cfg;
163 const globOptions = { root, cwd: root };
164 const exclusionGlobs = extractGlobExcludesFromConfig(root, configInfo.source, configInfo.config).concat(cfg.excludes);
165 const files = filterFiles(await findFiles(cfg.files, globOptions), exclusionGlobs);
166 return processFiles(fileLoader(files), configInfo);
167 }
168 function header() {
169 cfg.info(`
170cspell;
171Date: ${new Date().toUTCString()}
172Options:
173 verbose: ${yesNo(!!cfg.options.verbose)}
174 config: ${cfg.configGlob}
175 exclude: ${extractPatterns(cfg.excludes)
176 .map((a) => a.glob)
177 .join('\n ')}
178 files: ${cfg.files}
179 wordsOnly: ${yesNo(!!cfg.options.wordsOnly)}
180 unique: ${yesNo(!!cfg.options.unique)}
181`, exports.MessageTypes.Info);
182 }
183 function isExcluded(filename, globs) {
184 const { root } = cfg;
185 const absFilename = path.resolve(root, filename);
186 for (const glob of globs) {
187 const m = glob.matcher.matchEx(absFilename);
188 if (m.matched) {
189 cfg.info(`Excluded File: ${path.relative(root, absFilename)}; Excluded by ${m.glob} from ${glob.source}`, exports.MessageTypes.Info);
190 return true;
191 }
192 }
193 return false;
194 }
195 function filterFiles(files, excludeGlobs) {
196 const excludeInfo = extractPatterns(excludeGlobs).map((g) => `Glob: ${g.glob} from ${g.source}`);
197 cfg.info(`Exclusion Globs: \n ${excludeInfo.join('\n ')}\n`, exports.MessageTypes.Info);
198 const result = files.filter((filename) => !isExcluded(filename, excludeGlobs));
199 return result;
200 }
201}
202function runResult(init = {}) {
203 const { files = 0, filesWithIssues = new Set(), issues = 0, errors = 0 } = init;
204 return { files, filesWithIssues, issues, errors };
205}
206function extractPatterns(globs) {
207 const r = globs.reduce((info, g) => {
208 const source = g.source;
209 const patterns = typeof g.matcher.patterns === 'string' ? [g.matcher.patterns] : g.matcher.patterns;
210 return info.concat(patterns.map((glob) => ({ glob, source })));
211 }, []);
212 return r;
213}
214async function trace(words, options) {
215 const configGlob = options.config || defaultConfigGlob;
216 const configGlobOptions = options.config ? {} : defaultConfigGlobOptions;
217 const configFiles = (await globP(configGlob, configGlobOptions)).filter(util.uniqueFn());
218 const config = cspell.mergeSettings(cspell.getDefaultSettings(), cspell.getGlobalSettings(), cspell.readSettingsFiles(configFiles));
219 const results = await cspell_lib_1.traceWords(words, config);
220 return results;
221}
222exports.trace = trace;
223async function checkText(filename, options) {
224 const configGlob = options.config || defaultConfigGlob;
225 const configGlobOptions = options.config ? {} : defaultConfigGlobOptions;
226 const pSettings = globP(configGlob, configGlobOptions).then((filenames) => ({
227 source: filenames[0],
228 config: cspell.readSettingsFiles(filenames),
229 }));
230 const [foundSettings, text] = await Promise.all([pSettings, readFile(filename)]);
231 const settingsFromCommandLine = util.clean({
232 languageId: options.languageId || undefined,
233 local: options.local || undefined,
234 });
235 const info = calcFinalConfigInfo(foundSettings, settingsFromCommandLine, filename, text);
236 return Validator.checkText(text, info.configInfo.config);
237}
238exports.checkText = checkText;
239function createInit(_) {
240 return Promise.reject();
241}
242exports.createInit = createInit;
243const defaultExcludeGlobs = ['node_modules/**'];
244function readFileInfo(filename, encoding = UTF8) {
245 const pText = filename === STDIN ? getStdin() : fsp.readFile(filename, encoding);
246 return pText.then((text) => ({ text, filename }), (error) => {
247 return error.code === 'EISDIR'
248 ? Promise.resolve({ text: '', filename })
249 : Promise.reject({ ...error, message: `Error reading file: "${filename}"` });
250 });
251}
252function readFile(filename, encoding = UTF8) {
253 return readFileInfo(filename, encoding).then((info) => info.text);
254}
255/**
256 * Looks for matching glob patterns or stdin
257 * @param globPatterns patterns or stdin
258 */
259async function findFiles(globPatterns, options) {
260 const globPats = globPatterns.filter((filename) => filename !== STDIN);
261 const stdin = globPats.length < globPatterns.length ? [STDIN] : [];
262 const globResults = globPats.length ? await globP(globPats, options) : [];
263 const cwd = options.cwd || process.cwd();
264 return stdin.concat(globResults.map((filename) => path.resolve(cwd, filename)));
265}
266function calcExcludeGlobInfo(root, commandLineExclude) {
267 const commandLineExcludes = {
268 globs: commandLineExclude ? commandLineExclude.split(/\s+/g) : [],
269 source: 'arguments',
270 };
271 const defaultExcludes = {
272 globs: defaultExcludeGlobs,
273 source: 'default',
274 };
275 const choice = commandLineExcludes.globs.length ? commandLineExcludes : defaultExcludes;
276 const matcher = new cspell_glob_1.GlobMatcher(choice.globs, root);
277 return [
278 {
279 matcher,
280 source: choice.source,
281 },
282 ];
283}
284function extractGlobExcludesFromConfig(root, source, config) {
285 if (!config.ignorePaths || !config.ignorePaths.length) {
286 return [];
287 }
288 const matcher = new cspell_glob_1.GlobMatcher(config.ignorePaths, root);
289 return [{ source, matcher }];
290}
291function calcFinalConfigInfo(configInfo, settingsFromCommandLine, filename, text) {
292 const ext = path.extname(filename);
293 const fileSettings = cspell.calcOverrideSettings(configInfo.config, path.resolve(filename));
294 const settings = cspell.mergeSettings(cspell.getDefaultSettings(), cspell.getGlobalSettings(), fileSettings, settingsFromCommandLine);
295 const languageIds = settings.languageId ? [settings.languageId] : cspell.getLanguagesForExt(ext);
296 const config = cspell.constructSettingsForText(settings, text, languageIds);
297 return { configInfo: { ...configInfo, config }, filename, text, languageIds };
298}
299function yesNo(value) {
300 return value ? 'Yes' : 'No';
301}
302function findBaseDir(pat) {
303 const globChars = /[*@()?|[\]{},]/;
304 while (globChars.test(pat)) {
305 pat = path.dirname(pat);
306 }
307 return pat;
308}
309function exists(filename) {
310 try {
311 fsp.accessSync(filename);
312 }
313 catch (e) {
314 return false;
315 }
316 return true;
317}
318/**
319 * Attempt to normalize a pattern based upon the root.
320 * 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.
321 * If the pattern starts with a relative path, adjust the root to match.
322 * The challenge is with the patterns that begin with `/`. Is is an absolute path or relative pattern?
323 * @param pat glob pattern
324 * @param root absolute path | empty
325 * @returns the adjusted root and pattern.
326 */
327function normalizePattern(pat, root) {
328 // Absolute pattern
329 if (path.isAbsolute(pat)) {
330 const dir = findBaseDir(pat);
331 if (dir.length > 1 && exists(dir)) {
332 // Assume it is an absolute path
333 return {
334 pattern: pat,
335 root: path.sep,
336 };
337 }
338 }
339 // normal pattern
340 if (!/^\.\./.test(pat)) {
341 return {
342 pattern: pat,
343 root,
344 };
345 }
346 // relative pattern
347 pat = path.sep === '\\' ? pat.replace(/\\/g, '/') : pat;
348 const patParts = pat.split('/');
349 const rootParts = root.split(path.sep);
350 let i = 0;
351 for (; i < patParts.length && patParts[i] === '..'; ++i) {
352 rootParts.pop();
353 }
354 return {
355 pattern: patParts.slice(i).join('/'),
356 root: rootParts.join(path.sep),
357 };
358}
359async function globP(pattern, options) {
360 const root = (options === null || options === void 0 ? void 0 : options.root) || process.cwd();
361 const opts = options || {};
362 const rawPatterns = typeof pattern === 'string' ? [pattern] : pattern;
363 const normPatterns = rawPatterns.map((pat) => normalizePattern(pat, root));
364 const globResults = normPatterns.map(async (pat) => {
365 const opt = { ...opts, root: pat.root, cwd: pat.root };
366 const absolutePaths = (await _globP(pat.pattern, opt)).map((filename) => path.resolve(pat.root, filename));
367 const relativeToRoot = absolutePaths.map((absFilename) => path.relative(root, absFilename));
368 return relativeToRoot;
369 });
370 const results = (await Promise.all(globResults)).reduce((prev, next) => prev.concat(next), []);
371 return results;
372}
373function _globP(pattern, options) {
374 if (!pattern) {
375 return Promise.resolve([]);
376 }
377 return new Promise((resolve, reject) => {
378 const cb = (err, matches) => {
379 if (err) {
380 reject(err);
381 }
382 resolve(matches);
383 };
384 glob(pattern, options, cb);
385 });
386}
387exports._testing_ = {
388 _globP,
389 findFiles,
390 normalizePattern,
391};
392//# sourceMappingURL=application.js.map
\No newline at end of file