1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports._testing_ = exports.createInit = exports.checkText = exports.trace = exports.lint = exports.CSpellApplicationConfiguration = exports.MessageTypes = exports.IncludeExcludeFlag = void 0;
|
4 | const glob = require("glob");
|
5 | const cspell = require("cspell-lib");
|
6 | const fsp = require("fs-extra");
|
7 | const path = require("path");
|
8 | const commentJson = require("comment-json");
|
9 | const util = require("./util/util");
|
10 | const cspell_lib_1 = require("cspell-lib");
|
11 | const Validator = require("cspell-lib");
|
12 | const getStdin = require("get-stdin");
|
13 | var cspell_lib_2 = require("cspell-lib");
|
14 | Object.defineProperty(exports, "IncludeExcludeFlag", { enumerable: true, get: function () { return cspell_lib_2.IncludeExcludeFlag; } });
|
15 | const cspell_glob_1 = require("cspell-glob");
|
16 |
|
17 | const UTF8 = 'utf8';
|
18 | const STDIN = 'stdin';
|
19 | exports.MessageTypes = {
|
20 | Debug: 'Debug',
|
21 | Info: 'Info',
|
22 | Progress: 'Progress',
|
23 | };
|
24 | const defaultMinimatchOptions = { nocase: true };
|
25 | const defaultConfigGlob = '{cspell.json,.cspell.json}';
|
26 | const defaultConfigGlobOptions = defaultMinimatchOptions;
|
27 | const nullEmitter = () => {
|
28 |
|
29 | };
|
30 | class 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 | }
|
47 | exports.CSpellApplicationConfiguration = CSpellApplicationConfiguration;
|
48 | function lint(files, options, emitters) {
|
49 | const cfg = new CSpellApplicationConfiguration(files, options, emitters);
|
50 | return runLint(cfg);
|
51 | }
|
52 | exports.lint = lint;
|
53 | function 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 |
|
99 |
|
100 |
|
101 | function* fileLoader(fileNames) {
|
102 | for (const filename of fileNames) {
|
103 |
|
104 | const file = readFileInfo(filename);
|
105 |
|
106 |
|
107 | yield file;
|
108 |
|
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 |
|
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(`
|
170 | cspell;
|
171 | Date: ${new Date().toUTCString()}
|
172 | Options:
|
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 | }
|
202 | function runResult(init = {}) {
|
203 | const { files = 0, filesWithIssues = new Set(), issues = 0, errors = 0 } = init;
|
204 | return { files, filesWithIssues, issues, errors };
|
205 | }
|
206 | function 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 | }
|
214 | async 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 | }
|
222 | exports.trace = trace;
|
223 | async 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 | }
|
238 | exports.checkText = checkText;
|
239 | function createInit(_) {
|
240 | return Promise.reject();
|
241 | }
|
242 | exports.createInit = createInit;
|
243 | const defaultExcludeGlobs = ['node_modules/**'];
|
244 | function 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 | }
|
252 | function readFile(filename, encoding = UTF8) {
|
253 | return readFileInfo(filename, encoding).then((info) => info.text);
|
254 | }
|
255 |
|
256 |
|
257 |
|
258 |
|
259 | async 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 | }
|
266 | function 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 | }
|
284 | function 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 | }
|
291 | function 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 | }
|
299 | function yesNo(value) {
|
300 | return value ? 'Yes' : 'No';
|
301 | }
|
302 | function findBaseDir(pat) {
|
303 | const globChars = /[*@()?|[\]{},]/;
|
304 | while (globChars.test(pat)) {
|
305 | pat = path.dirname(pat);
|
306 | }
|
307 | return pat;
|
308 | }
|
309 | function exists(filename) {
|
310 | try {
|
311 | fsp.accessSync(filename);
|
312 | }
|
313 | catch (e) {
|
314 | return false;
|
315 | }
|
316 | return true;
|
317 | }
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | function normalizePattern(pat, root) {
|
328 |
|
329 | if (path.isAbsolute(pat)) {
|
330 | const dir = findBaseDir(pat);
|
331 | if (dir.length > 1 && exists(dir)) {
|
332 |
|
333 | return {
|
334 | pattern: pat,
|
335 | root: path.sep,
|
336 | };
|
337 | }
|
338 | }
|
339 |
|
340 | if (!/^\.\./.test(pat)) {
|
341 | return {
|
342 | pattern: pat,
|
343 | root,
|
344 | };
|
345 | }
|
346 |
|
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 | }
|
359 | async 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 | }
|
373 | function _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 | }
|
387 | exports._testing_ = {
|
388 | _globP,
|
389 | findFiles,
|
390 | normalizePattern,
|
391 | };
|
392 |
|
\ | No newline at end of file |