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 | class 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 | }
|
48 | exports.CSpellApplicationConfiguration = CSpellApplicationConfiguration;
|
49 | function lint(files, options, emitters) {
|
50 | const cfg = new CSpellApplicationConfiguration(files, options, emitters);
|
51 | return runLint(cfg);
|
52 | }
|
53 | exports.lint = lint;
|
54 | function 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 |
|
84 |
|
85 |
|
86 | function* fileLoader(fileNames) {
|
87 | for (const filename of fileNames) {
|
88 |
|
89 | const file = readFileInfo(filename);
|
90 |
|
91 | yield file;
|
92 |
|
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 |
|
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(`
|
130 | cspell;
|
131 | Date: ${(new Date()).toUTCString()}
|
132 | Options:
|
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 | }
|
161 | function 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 | }
|
170 | async 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 | }
|
178 | exports.trace = trace;
|
179 | async 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 | }
|
191 | exports.checkText = checkText;
|
192 | function createInit(_) {
|
193 | return Promise.reject();
|
194 | }
|
195 | exports.createInit = createInit;
|
196 | const defaultExcludeGlobs = [
|
197 | 'node_modules/**'
|
198 | ];
|
199 | function 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 | }
|
207 | function readFile(filename, encoding = UTF8) {
|
208 | return readFileInfo(filename, encoding).then(info => info.text);
|
209 | }
|
210 |
|
211 |
|
212 |
|
213 |
|
214 | async 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 | }
|
221 | function 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 | }
|
237 | function 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 | }
|
244 | function 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 | }
|
252 | function yesNo(value) {
|
253 | return value ? 'Yes' : 'No';
|
254 | }
|
255 | function findBaseDir(pat) {
|
256 | const globChars = /[*@()?|\[\]{},]/;
|
257 | while (globChars.test(pat)) {
|
258 | pat = path.dirname(pat);
|
259 | }
|
260 | return pat;
|
261 | }
|
262 | function exists(filename) {
|
263 | try {
|
264 | fsp.accessSync(filename);
|
265 | }
|
266 | catch (e) {
|
267 | return false;
|
268 | }
|
269 | return true;
|
270 | }
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 | function normalizePattern(pat, root) {
|
281 |
|
282 | if (path.isAbsolute(pat)) {
|
283 | const dir = findBaseDir(pat);
|
284 | if (dir.length > 1 && exists(dir)) {
|
285 |
|
286 | return {
|
287 | pattern: pat,
|
288 | root: path.sep,
|
289 | };
|
290 | }
|
291 | }
|
292 |
|
293 | if (!/^\.\./.test(pat)) {
|
294 | return {
|
295 | pattern: pat,
|
296 | root,
|
297 | };
|
298 | }
|
299 |
|
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 | }
|
312 | async 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 | }
|
327 | function _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 | }
|
341 | exports._testing_ = {
|
342 | _globP,
|
343 | findFiles,
|
344 | normalizePattern,
|
345 | };
|
346 |
|
\ | No newline at end of file |