UNPKG

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