UNPKG

28.5 kBJavaScriptView Raw
1/**
2 * @fileoverview Main CLI object.
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8/*
9 * The CLI object should *not* call process.exit() directly. It should only return
10 * exit codes. This allows other programs to use the CLI object and still control
11 * when the program exits.
12 */
13
14//------------------------------------------------------------------------------
15// Requirements
16//------------------------------------------------------------------------------
17
18const fs = require("fs"),
19 path = require("path"),
20 defaultOptions = require("../conf/default-cli-options"),
21 Linter = require("./linter"),
22 lodash = require("lodash"),
23 IgnoredPaths = require("./util/ignored-paths"),
24 Config = require("./config"),
25 ConfigOps = require("./config/config-ops"),
26 LintResultCache = require("./util/lint-result-cache"),
27 globUtils = require("./util/glob-utils"),
28 validator = require("./config/config-validator"),
29 hash = require("./util/hash"),
30 ModuleResolver = require("./util/module-resolver"),
31 naming = require("./util/naming"),
32 pkg = require("../package.json"),
33 loadRules = require("./load-rules");
34
35const debug = require("debug")("eslint:cli-engine");
36const resolver = new ModuleResolver();
37const validFixTypes = new Set(["problem", "suggestion", "layout"]);
38
39//------------------------------------------------------------------------------
40// Typedefs
41//------------------------------------------------------------------------------
42
43/**
44 * The options to configure a CLI engine with.
45 * @typedef {Object} CLIEngineOptions
46 * @property {boolean} allowInlineConfig Enable or disable inline configuration comments.
47 * @property {Object} baseConfig Base config object, extended by all configs used with this CLIEngine instance
48 * @property {boolean} cache Enable result caching.
49 * @property {string} cacheLocation The cache file to use instead of .eslintcache.
50 * @property {string} configFile The configuration file to use.
51 * @property {string} cwd The value to use for the current working directory.
52 * @property {string[]} envs An array of environments to load.
53 * @property {string[]} extensions An array of file extensions to check.
54 * @property {boolean|Function} fix Execute in autofix mode. If a function, should return a boolean.
55 * @property {string[]} fixTypes Array of rule types to apply fixes for.
56 * @property {string[]} globals An array of global variables to declare.
57 * @property {boolean} ignore False disables use of .eslintignore.
58 * @property {string} ignorePath The ignore file to use instead of .eslintignore.
59 * @property {string} ignorePattern A glob pattern of files to ignore.
60 * @property {boolean} useEslintrc False disables looking for .eslintrc
61 * @property {string} parser The name of the parser to use.
62 * @property {Object} parserOptions An object of parserOption settings to use.
63 * @property {string[]} plugins An array of plugins to load.
64 * @property {Object<string,*>} rules An object of rules to use.
65 * @property {string[]} rulePaths An array of directories to load custom rules from.
66 * @property {boolean} reportUnusedDisableDirectives `true` adds reports for unused eslint-disable directives
67 */
68
69/**
70 * A linting warning or error.
71 * @typedef {Object} LintMessage
72 * @property {string} message The message to display to the user.
73 */
74
75/**
76 * A linting result.
77 * @typedef {Object} LintResult
78 * @property {string} filePath The path to the file that was linted.
79 * @property {LintMessage[]} messages All of the messages for the result.
80 * @property {number} errorCount Number of errors for the result.
81 * @property {number} warningCount Number of warnings for the result.
82 * @property {number} fixableErrorCount Number of fixable errors for the result.
83 * @property {number} fixableWarningCount Number of fixable warnings for the result.
84 * @property {string=} [source] The source code of the file that was linted.
85 * @property {string=} [output] The source code of the file that was linted, with as many fixes applied as possible.
86 */
87
88//------------------------------------------------------------------------------
89// Helpers
90//------------------------------------------------------------------------------
91
92/**
93 * Determines if each fix type in an array is supported by ESLint and throws
94 * an error if not.
95 * @param {string[]} fixTypes An array of fix types to check.
96 * @returns {void}
97 * @throws {Error} If an invalid fix type is found.
98 */
99function validateFixTypes(fixTypes) {
100 for (const fixType of fixTypes) {
101 if (!validFixTypes.has(fixType)) {
102 throw new Error(`Invalid fix type "${fixType}" found.`);
103 }
104 }
105}
106
107/**
108 * It will calculate the error and warning count for collection of messages per file
109 * @param {Object[]} messages - Collection of messages
110 * @returns {Object} Contains the stats
111 * @private
112 */
113function calculateStatsPerFile(messages) {
114 return messages.reduce((stat, message) => {
115 if (message.fatal || message.severity === 2) {
116 stat.errorCount++;
117 if (message.fix) {
118 stat.fixableErrorCount++;
119 }
120 } else {
121 stat.warningCount++;
122 if (message.fix) {
123 stat.fixableWarningCount++;
124 }
125 }
126 return stat;
127 }, {
128 errorCount: 0,
129 warningCount: 0,
130 fixableErrorCount: 0,
131 fixableWarningCount: 0
132 });
133}
134
135/**
136 * It will calculate the error and warning count for collection of results from all files
137 * @param {Object[]} results - Collection of messages from all the files
138 * @returns {Object} Contains the stats
139 * @private
140 */
141function calculateStatsPerRun(results) {
142 return results.reduce((stat, result) => {
143 stat.errorCount += result.errorCount;
144 stat.warningCount += result.warningCount;
145 stat.fixableErrorCount += result.fixableErrorCount;
146 stat.fixableWarningCount += result.fixableWarningCount;
147 return stat;
148 }, {
149 errorCount: 0,
150 warningCount: 0,
151 fixableErrorCount: 0,
152 fixableWarningCount: 0
153 });
154}
155
156/**
157 * Processes an source code using ESLint.
158 * @param {string} text The source code to check.
159 * @param {Object} configHelper The configuration options for ESLint.
160 * @param {string} filename An optional string representing the texts filename.
161 * @param {boolean|Function} fix Indicates if fixes should be processed.
162 * @param {boolean} allowInlineConfig Allow/ignore comments that change config.
163 * @param {boolean} reportUnusedDisableDirectives Allow/ignore comments that change config.
164 * @param {Linter} linter Linter context
165 * @returns {{rules: LintResult, config: Object}} The results for linting on this text and the fully-resolved config for it.
166 * @private
167 */
168function processText(text, configHelper, filename, fix, allowInlineConfig, reportUnusedDisableDirectives, linter) {
169 let filePath,
170 fileExtension,
171 processor;
172
173 if (filename) {
174 filePath = path.resolve(filename);
175 fileExtension = path.extname(filename);
176 }
177
178 const effectiveFilename = filename || "<text>";
179
180 debug(`Linting ${effectiveFilename}`);
181 const config = configHelper.getConfig(filePath);
182
183 if (config.plugins) {
184 configHelper.plugins.loadAll(config.plugins);
185 }
186
187 const loadedPlugins = configHelper.plugins.getAll();
188
189 for (const plugin in loadedPlugins) {
190 if (loadedPlugins[plugin].processors && Object.keys(loadedPlugins[plugin].processors).indexOf(fileExtension) >= 0) {
191 processor = loadedPlugins[plugin].processors[fileExtension];
192 break;
193 }
194 }
195
196 const autofixingEnabled = typeof fix !== "undefined" && (!processor || processor.supportsAutofix);
197 const fixedResult = linter.verifyAndFix(text, config, {
198 filename: effectiveFilename,
199 allowInlineConfig,
200 reportUnusedDisableDirectives,
201 fix: !!autofixingEnabled && fix,
202 preprocess: processor && (rawText => processor.preprocess(rawText, effectiveFilename)),
203 postprocess: processor && (problemLists => processor.postprocess(problemLists, effectiveFilename))
204 });
205 const stats = calculateStatsPerFile(fixedResult.messages);
206
207 const result = {
208 filePath: effectiveFilename,
209 messages: fixedResult.messages,
210 errorCount: stats.errorCount,
211 warningCount: stats.warningCount,
212 fixableErrorCount: stats.fixableErrorCount,
213 fixableWarningCount: stats.fixableWarningCount
214 };
215
216 if (fixedResult.fixed) {
217 result.output = fixedResult.output;
218 }
219
220 if (result.errorCount + result.warningCount > 0 && typeof result.output === "undefined") {
221 result.source = text;
222 }
223
224 return { result, config };
225}
226
227/**
228 * Processes an individual file using ESLint. Files used here are known to
229 * exist, so no need to check that here.
230 * @param {string} filename The filename of the file being checked.
231 * @param {Object} configHelper The configuration options for ESLint.
232 * @param {Object} options The CLIEngine options object.
233 * @param {Linter} linter Linter context
234 * @returns {{rules: LintResult, config: Object}} The results for linting on this text and the fully-resolved config for it.
235 * @private
236 */
237function processFile(filename, configHelper, options, linter) {
238
239 const text = fs.readFileSync(path.resolve(filename), "utf8");
240
241 return processText(
242 text,
243 configHelper,
244 filename,
245 options.fix,
246 options.allowInlineConfig,
247 options.reportUnusedDisableDirectives,
248 linter
249 );
250}
251
252/**
253 * Returns result with warning by ignore settings
254 * @param {string} filePath - File path of checked code
255 * @param {string} baseDir - Absolute path of base directory
256 * @returns {LintResult} Result with single warning
257 * @private
258 */
259function createIgnoreResult(filePath, baseDir) {
260 let message;
261 const isHidden = /^\./u.test(path.basename(filePath));
262 const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
263 const isInBowerComponents = baseDir && path.relative(baseDir, filePath).startsWith("bower_components");
264
265 if (isHidden) {
266 message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
267 } else if (isInNodeModules) {
268 message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.";
269 } else if (isInBowerComponents) {
270 message = "File ignored by default. Use \"--ignore-pattern '!bower_components/*'\" to override.";
271 } else {
272 message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
273 }
274
275 return {
276 filePath: path.resolve(filePath),
277 messages: [
278 {
279 fatal: false,
280 severity: 1,
281 message
282 }
283 ],
284 errorCount: 0,
285 warningCount: 1,
286 fixableErrorCount: 0,
287 fixableWarningCount: 0
288 };
289}
290
291/**
292 * Produces rule warnings (i.e. deprecation) from configured rules
293 * @param {(Array<string>|Set<string>)} usedRules - Rules configured
294 * @param {Map} loadedRules - Map of loaded rules
295 * @returns {Array<Object>} Contains rule warnings
296 * @private
297 */
298function createRuleDeprecationWarnings(usedRules, loadedRules) {
299 const usedDeprecatedRules = [];
300
301 usedRules.forEach(name => {
302 const loadedRule = loadedRules.get(name);
303
304 if (loadedRule && loadedRule.meta && loadedRule.meta.deprecated) {
305 const deprecatedRule = { ruleId: name };
306 const replacedBy = lodash.get(loadedRule, "meta.replacedBy", []);
307
308 if (replacedBy.every(newRule => lodash.isString(newRule))) {
309 deprecatedRule.replacedBy = replacedBy;
310 }
311
312 usedDeprecatedRules.push(deprecatedRule);
313 }
314 });
315
316 return usedDeprecatedRules;
317}
318
319/**
320 * Checks if the given message is an error message.
321 * @param {Object} message The message to check.
322 * @returns {boolean} Whether or not the message is an error message.
323 * @private
324 */
325function isErrorMessage(message) {
326 return message.severity === 2;
327}
328
329
330/**
331 * return the cacheFile to be used by eslint, based on whether the provided parameter is
332 * a directory or looks like a directory (ends in `path.sep`), in which case the file
333 * name will be the `cacheFile/.cache_hashOfCWD`
334 *
335 * if cacheFile points to a file or looks like a file then in will just use that file
336 *
337 * @param {string} cacheFile The name of file to be used to store the cache
338 * @param {string} cwd Current working directory
339 * @returns {string} the resolved path to the cache file
340 */
341function getCacheFile(cacheFile, cwd) {
342
343 /*
344 * make sure the path separators are normalized for the environment/os
345 * keeping the trailing path separator if present
346 */
347 const normalizedCacheFile = path.normalize(cacheFile);
348
349 const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile);
350 const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep;
351
352 /**
353 * return the name for the cache file in case the provided parameter is a directory
354 * @returns {string} the resolved path to the cacheFile
355 */
356 function getCacheFileForDirectory() {
357 return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
358 }
359
360 let fileStats;
361
362 try {
363 fileStats = fs.lstatSync(resolvedCacheFile);
364 } catch (ex) {
365 fileStats = null;
366 }
367
368
369 /*
370 * in case the file exists we need to verify if the provided path
371 * is a directory or a file. If it is a directory we want to create a file
372 * inside that directory
373 */
374 if (fileStats) {
375
376 /*
377 * is a directory or is a file, but the original file the user provided
378 * looks like a directory but `path.resolve` removed the `last path.sep`
379 * so we need to still treat this like a directory
380 */
381 if (fileStats.isDirectory() || looksLikeADirectory) {
382 return getCacheFileForDirectory();
383 }
384
385 // is file so just use that file
386 return resolvedCacheFile;
387 }
388
389 /*
390 * here we known the file or directory doesn't exist,
391 * so we will try to infer if its a directory if it looks like a directory
392 * for the current operating system.
393 */
394
395 // if the last character passed is a path separator we assume is a directory
396 if (looksLikeADirectory) {
397 return getCacheFileForDirectory();
398 }
399
400 return resolvedCacheFile;
401}
402
403//------------------------------------------------------------------------------
404// Public Interface
405//------------------------------------------------------------------------------
406
407class CLIEngine {
408
409 /**
410 * Creates a new instance of the core CLI engine.
411 * @param {CLIEngineOptions} providedOptions The options for this instance.
412 * @constructor
413 */
414 constructor(providedOptions) {
415
416 const options = Object.assign(
417 Object.create(null),
418 defaultOptions,
419 { cwd: process.cwd() },
420 providedOptions
421 );
422
423 /*
424 * if an --ignore-path option is provided, ensure that the ignore
425 * file exists and is not a directory
426 */
427 if (options.ignore && options.ignorePath) {
428 try {
429 if (!fs.statSync(options.ignorePath).isFile()) {
430 throw new Error(`${options.ignorePath} is not a file`);
431 }
432 } catch (e) {
433 e.message = `Error: Could not load file ${options.ignorePath}\nError: ${e.message}`;
434 throw e;
435 }
436 }
437
438 /**
439 * Stored options for this instance
440 * @type {Object}
441 */
442 this.options = options;
443 this.linter = new Linter();
444
445 // load in additional rules
446 if (this.options.rulePaths) {
447 const cwd = this.options.cwd;
448
449 this.options.rulePaths.forEach(rulesdir => {
450 debug(`Loading rules from ${rulesdir}`);
451 this.linter.defineRules(loadRules(rulesdir, cwd));
452 });
453 }
454
455 if (this.options.rules && Object.keys(this.options.rules).length) {
456 const loadedRules = this.linter.getRules();
457
458 // Ajv validator with default schema will mutate original object, so we must clone it recursively.
459 this.options.rules = lodash.cloneDeep(this.options.rules);
460
461 Object.keys(this.options.rules).forEach(name => {
462 validator.validateRuleOptions(loadedRules.get(name), name, this.options.rules[name], "CLI");
463 });
464 }
465
466 this.config = new Config(this.options, this.linter);
467
468 if (this.options.cache) {
469 const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
470
471 /**
472 * Cache used to avoid operating on files that haven't changed since the
473 * last successful execution.
474 * @type {Object}
475 */
476 this._lintResultCache = new LintResultCache(cacheFile, this.config);
477 }
478
479 // setup special filter for fixes
480 if (this.options.fix && this.options.fixTypes && this.options.fixTypes.length > 0) {
481
482 debug(`Using fix types ${this.options.fixTypes}`);
483
484 // throw an error if any invalid fix types are found
485 validateFixTypes(this.options.fixTypes);
486
487 // convert to Set for faster lookup
488 const fixTypes = new Set(this.options.fixTypes);
489
490 // save original value of options.fix in case it's a function
491 const originalFix = (typeof this.options.fix === "function")
492 ? this.options.fix : () => this.options.fix;
493
494 // create a cache of rules (but don't populate until needed)
495 this._rulesCache = null;
496
497 this.options.fix = lintResult => {
498 const rule = this._rulesCache.get(lintResult.ruleId);
499 const matches = rule.meta && fixTypes.has(rule.meta.type);
500
501 return matches && originalFix(lintResult);
502 };
503 }
504
505 }
506
507 getRules() {
508 return this.linter.getRules();
509 }
510
511 /**
512 * Returns results that only contains errors.
513 * @param {LintResult[]} results The results to filter.
514 * @returns {LintResult[]} The filtered results.
515 */
516 static getErrorResults(results) {
517 const filtered = [];
518
519 results.forEach(result => {
520 const filteredMessages = result.messages.filter(isErrorMessage);
521
522 if (filteredMessages.length > 0) {
523 filtered.push(
524 Object.assign(result, {
525 messages: filteredMessages,
526 errorCount: filteredMessages.length,
527 warningCount: 0,
528 fixableErrorCount: result.fixableErrorCount,
529 fixableWarningCount: 0
530 })
531 );
532 }
533 });
534
535 return filtered;
536 }
537
538 /**
539 * Outputs fixes from the given results to files.
540 * @param {Object} report The report object created by CLIEngine.
541 * @returns {void}
542 */
543 static outputFixes(report) {
544 report.results.filter(result => Object.prototype.hasOwnProperty.call(result, "output")).forEach(result => {
545 fs.writeFileSync(result.filePath, result.output);
546 });
547 }
548
549
550 /**
551 * Add a plugin by passing its configuration
552 * @param {string} name Name of the plugin.
553 * @param {Object} pluginobject Plugin configuration object.
554 * @returns {void}
555 */
556 addPlugin(name, pluginobject) {
557 this.config.plugins.define(name, pluginobject);
558 }
559
560 /**
561 * Resolves the patterns passed into executeOnFiles() into glob-based patterns
562 * for easier handling.
563 * @param {string[]} patterns The file patterns passed on the command line.
564 * @returns {string[]} The equivalent glob patterns.
565 */
566 resolveFileGlobPatterns(patterns) {
567 return globUtils.resolveFileGlobPatterns(patterns.filter(Boolean), this.options);
568 }
569
570 /**
571 * Executes the current configuration on an array of file and directory names.
572 * @param {string[]} patterns An array of file and directory names.
573 * @returns {Object} The results for all files that were linted.
574 */
575 executeOnFiles(patterns) {
576 const options = this.options,
577 lintResultCache = this._lintResultCache,
578 configHelper = this.config;
579 const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
580
581 if (!options.cache && fs.existsSync(cacheFile)) {
582 fs.unlinkSync(cacheFile);
583 }
584
585 const startTime = Date.now();
586 const fileList = globUtils.listFilesToProcess(patterns, options);
587 const allUsedRules = new Set();
588 const results = fileList.map(fileInfo => {
589 if (fileInfo.ignored) {
590 return createIgnoreResult(fileInfo.filename, options.cwd);
591 }
592
593 if (options.cache) {
594 const cachedLintResults = lintResultCache.getCachedLintResults(fileInfo.filename);
595
596 if (cachedLintResults) {
597 const resultHadMessages = cachedLintResults.messages && cachedLintResults.messages.length;
598
599 if (resultHadMessages && options.fix) {
600 debug(`Reprocessing cached file to allow autofix: ${fileInfo.filename}`);
601 } else {
602 debug(`Skipping file since it hasn't changed: ${fileInfo.filename}`);
603
604 return cachedLintResults;
605 }
606 }
607 }
608
609 // if there's a cache, populate it
610 if ("_rulesCache" in this) {
611 this._rulesCache = this.getRules();
612 }
613
614 debug(`Processing ${fileInfo.filename}`);
615
616 const { result, config } = processFile(fileInfo.filename, configHelper, options, this.linter);
617
618 Object.keys(config.rules)
619 .filter(ruleId => ConfigOps.getRuleSeverity(config.rules[ruleId]))
620 .forEach(ruleId => allUsedRules.add(ruleId));
621
622 return result;
623 });
624
625 if (options.cache) {
626 results.forEach(result => {
627
628 /*
629 * Store the lint result in the LintResultCache.
630 * NOTE: The LintResultCache will remove the file source and any
631 * other properties that are difficult to serialize, and will
632 * hydrate those properties back in on future lint runs.
633 */
634 lintResultCache.setCachedLintResults(result.filePath, result);
635 });
636
637 // persist the cache to disk
638 lintResultCache.reconcile();
639 }
640
641 const stats = calculateStatsPerRun(results);
642
643 const usedDeprecatedRules = createRuleDeprecationWarnings(allUsedRules, this.getRules());
644
645 debug(`Linting complete in: ${Date.now() - startTime}ms`);
646
647 return {
648 results,
649 errorCount: stats.errorCount,
650 warningCount: stats.warningCount,
651 fixableErrorCount: stats.fixableErrorCount,
652 fixableWarningCount: stats.fixableWarningCount,
653 usedDeprecatedRules
654 };
655 }
656
657 /**
658 * Executes the current configuration on text.
659 * @param {string} text A string of JavaScript code to lint.
660 * @param {string} filename An optional string representing the texts filename.
661 * @param {boolean} warnIgnored Always warn when a file is ignored
662 * @returns {Object} The results for the linting.
663 */
664 executeOnText(text, filename, warnIgnored) {
665
666 const results = [],
667 options = this.options,
668 configHelper = this.config,
669 ignoredPaths = new IgnoredPaths(options);
670
671 // resolve filename based on options.cwd (for reporting, ignoredPaths also resolves)
672
673 const resolvedFilename = filename && !path.isAbsolute(filename)
674 ? path.resolve(options.cwd, filename)
675 : filename;
676 let usedDeprecatedRules;
677
678 if (resolvedFilename && ignoredPaths.contains(resolvedFilename)) {
679 if (warnIgnored) {
680 results.push(createIgnoreResult(resolvedFilename, options.cwd));
681 }
682 usedDeprecatedRules = [];
683 } else {
684
685 // if there's a cache, populate it
686 if ("_rulesCache" in this) {
687 this._rulesCache = this.getRules();
688 }
689
690 const { result, config } = processText(
691 text,
692 configHelper,
693 resolvedFilename,
694 options.fix,
695 options.allowInlineConfig,
696 options.reportUnusedDisableDirectives,
697 this.linter
698 );
699
700 results.push(result);
701 usedDeprecatedRules = createRuleDeprecationWarnings(
702 Object.keys(config.rules).filter(rule => ConfigOps.getRuleSeverity(config.rules[rule])),
703 this.getRules()
704 );
705 }
706
707 const stats = calculateStatsPerRun(results);
708
709 return {
710 results,
711 errorCount: stats.errorCount,
712 warningCount: stats.warningCount,
713 fixableErrorCount: stats.fixableErrorCount,
714 fixableWarningCount: stats.fixableWarningCount,
715 usedDeprecatedRules
716 };
717 }
718
719 /**
720 * Returns a configuration object for the given file based on the CLI options.
721 * This is the same logic used by the ESLint CLI executable to determine
722 * configuration for each file it processes.
723 * @param {string} filePath The path of the file to retrieve a config object for.
724 * @returns {Object} A configuration object for the file.
725 */
726 getConfigForFile(filePath) {
727 const configHelper = this.config;
728
729 return configHelper.getConfig(filePath);
730 }
731
732 /**
733 * Checks if a given path is ignored by ESLint.
734 * @param {string} filePath The path of the file to check.
735 * @returns {boolean} Whether or not the given path is ignored.
736 */
737 isPathIgnored(filePath) {
738 const resolvedPath = path.resolve(this.options.cwd, filePath);
739 const ignoredPaths = new IgnoredPaths(this.options);
740
741 return ignoredPaths.contains(resolvedPath);
742 }
743
744 /**
745 * Returns the formatter representing the given format or null if no formatter
746 * with the given name can be found.
747 * @param {string} [format] The name of the format to load or the path to a
748 * custom formatter.
749 * @returns {Function} The formatter function or null if not found.
750 */
751 getFormatter(format) {
752
753 // default is stylish
754 const resolvedFormatName = format || "stylish";
755
756 // only strings are valid formatters
757 if (typeof resolvedFormatName === "string") {
758
759 // replace \ with / for Windows compatibility
760 const normalizedFormatName = resolvedFormatName.replace(/\\/gu, "/");
761
762 const cwd = this.options ? this.options.cwd : process.cwd();
763 const namespace = naming.getNamespaceFromTerm(normalizedFormatName);
764
765 let formatterPath;
766
767 // if there's a slash, then it's a file
768 if (!namespace && normalizedFormatName.indexOf("/") > -1) {
769 formatterPath = path.resolve(cwd, normalizedFormatName);
770 } else {
771 try {
772 const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
773
774 formatterPath = resolver.resolve(npmFormat, `${cwd}/node_modules`);
775 } catch (e) {
776 formatterPath = `./formatters/${normalizedFormatName}`;
777 }
778 }
779
780 try {
781 return require(formatterPath);
782 } catch (ex) {
783 ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
784 throw ex;
785 }
786
787 } else {
788 return null;
789 }
790 }
791}
792
793CLIEngine.version = pkg.version;
794CLIEngine.getFormatter = CLIEngine.prototype.getFormatter;
795
796module.exports = CLIEngine;