123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792 |
- /**
- * @fileoverview Main CLI object.
- * @author Nicholas C. Zakas
- */
-
- "use strict";
-
- /*
- * The CLI object should *not* call process.exit() directly. It should only return
- * exit codes. This allows other programs to use the CLI object and still control
- * when the program exits.
- */
-
- //------------------------------------------------------------------------------
- // Requirements
- //------------------------------------------------------------------------------
-
- const fs = require("fs"),
- path = require("path"),
- defaultOptions = require("../conf/default-cli-options"),
- Linter = require("./linter"),
- lodash = require("lodash"),
- IgnoredPaths = require("./ignored-paths"),
- Config = require("./config"),
- ConfigOps = require("./config/config-ops"),
- LintResultCache = require("./util/lint-result-cache"),
- globUtils = require("./util/glob-utils"),
- validator = require("./config/config-validator"),
- hash = require("./util/hash"),
- ModuleResolver = require("./util/module-resolver"),
- naming = require("./util/naming"),
- pkg = require("../package.json");
-
- const debug = require("debug")("eslint:cli-engine");
- const resolver = new ModuleResolver();
- const validFixTypes = new Set(["problem", "suggestion", "layout"]);
-
- //------------------------------------------------------------------------------
- // Typedefs
- //------------------------------------------------------------------------------
-
- /**
- * The options to configure a CLI engine with.
- * @typedef {Object} CLIEngineOptions
- * @property {boolean} allowInlineConfig Enable or disable inline configuration comments.
- * @property {Object} baseConfig Base config object, extended by all configs used with this CLIEngine instance
- * @property {boolean} cache Enable result caching.
- * @property {string} cacheLocation The cache file to use instead of .eslintcache.
- * @property {string} configFile The configuration file to use.
- * @property {string} cwd The value to use for the current working directory.
- * @property {string[]} envs An array of environments to load.
- * @property {string[]} extensions An array of file extensions to check.
- * @property {boolean|Function} fix Execute in autofix mode. If a function, should return a boolean.
- * @property {string[]} fixTypes Array of rule types to apply fixes for.
- * @property {string[]} globals An array of global variables to declare.
- * @property {boolean} ignore False disables use of .eslintignore.
- * @property {string} ignorePath The ignore file to use instead of .eslintignore.
- * @property {string} ignorePattern A glob pattern of files to ignore.
- * @property {boolean} useEslintrc False disables looking for .eslintrc
- * @property {string} parser The name of the parser to use.
- * @property {Object} parserOptions An object of parserOption settings to use.
- * @property {string[]} plugins An array of plugins to load.
- * @property {Object<string,*>} rules An object of rules to use.
- * @property {string[]} rulePaths An array of directories to load custom rules from.
- * @property {boolean} reportUnusedDisableDirectives `true` adds reports for unused eslint-disable directives
- */
-
- /**
- * A linting warning or error.
- * @typedef {Object} LintMessage
- * @property {string} message The message to display to the user.
- */
-
- /**
- * A linting result.
- * @typedef {Object} LintResult
- * @property {string} filePath The path to the file that was linted.
- * @property {LintMessage[]} messages All of the messages for the result.
- * @property {number} errorCount Number of errors for the result.
- * @property {number} warningCount Number of warnings for the result.
- * @property {number} fixableErrorCount Number of fixable errors for the result.
- * @property {number} fixableWarningCount Number of fixable warnings for the result.
- * @property {string=} [source] The source code of the file that was linted.
- * @property {string=} [output] The source code of the file that was linted, with as many fixes applied as possible.
- */
-
- //------------------------------------------------------------------------------
- // Helpers
- //------------------------------------------------------------------------------
-
- /**
- * Determines if each fix type in an array is supported by ESLint and throws
- * an error if not.
- * @param {string[]} fixTypes An array of fix types to check.
- * @returns {void}
- * @throws {Error} If an invalid fix type is found.
- */
- function validateFixTypes(fixTypes) {
- for (const fixType of fixTypes) {
- if (!validFixTypes.has(fixType)) {
- throw new Error(`Invalid fix type "${fixType}" found.`);
- }
- }
- }
-
- /**
- * It will calculate the error and warning count for collection of messages per file
- * @param {Object[]} messages - Collection of messages
- * @returns {Object} Contains the stats
- * @private
- */
- function calculateStatsPerFile(messages) {
- return messages.reduce((stat, message) => {
- if (message.fatal || message.severity === 2) {
- stat.errorCount++;
- if (message.fix) {
- stat.fixableErrorCount++;
- }
- } else {
- stat.warningCount++;
- if (message.fix) {
- stat.fixableWarningCount++;
- }
- }
- return stat;
- }, {
- errorCount: 0,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- });
- }
-
- /**
- * It will calculate the error and warning count for collection of results from all files
- * @param {Object[]} results - Collection of messages from all the files
- * @returns {Object} Contains the stats
- * @private
- */
- function calculateStatsPerRun(results) {
- return results.reduce((stat, result) => {
- stat.errorCount += result.errorCount;
- stat.warningCount += result.warningCount;
- stat.fixableErrorCount += result.fixableErrorCount;
- stat.fixableWarningCount += result.fixableWarningCount;
- return stat;
- }, {
- errorCount: 0,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- });
- }
-
- /**
- * Processes an source code using ESLint.
- * @param {string} text The source code to check.
- * @param {Object} configHelper The configuration options for ESLint.
- * @param {string} filename An optional string representing the texts filename.
- * @param {boolean|Function} fix Indicates if fixes should be processed.
- * @param {boolean} allowInlineConfig Allow/ignore comments that change config.
- * @param {boolean} reportUnusedDisableDirectives Allow/ignore comments that change config.
- * @param {Linter} linter Linter context
- * @returns {{rules: LintResult, config: Object}} The results for linting on this text and the fully-resolved config for it.
- * @private
- */
- function processText(text, configHelper, filename, fix, allowInlineConfig, reportUnusedDisableDirectives, linter) {
- let filePath,
- fileExtension,
- processor;
-
- if (filename) {
- filePath = path.resolve(filename);
- fileExtension = path.extname(filename);
- }
-
- const effectiveFilename = filename || "<text>";
-
- debug(`Linting ${effectiveFilename}`);
- const config = configHelper.getConfig(filePath);
-
- if (config.plugins) {
- configHelper.plugins.loadAll(config.plugins);
- }
-
- const loadedPlugins = configHelper.plugins.getAll();
-
- for (const plugin in loadedPlugins) {
- if (loadedPlugins[plugin].processors && Object.keys(loadedPlugins[plugin].processors).indexOf(fileExtension) >= 0) {
- processor = loadedPlugins[plugin].processors[fileExtension];
- break;
- }
- }
-
- const autofixingEnabled = typeof fix !== "undefined" && (!processor || processor.supportsAutofix);
- const fixedResult = linter.verifyAndFix(text, config, {
- filename: effectiveFilename,
- allowInlineConfig,
- reportUnusedDisableDirectives,
- fix: !!autofixingEnabled && fix,
- preprocess: processor && (rawText => processor.preprocess(rawText, effectiveFilename)),
- postprocess: processor && (problemLists => processor.postprocess(problemLists, effectiveFilename))
- });
- const stats = calculateStatsPerFile(fixedResult.messages);
-
- const result = {
- filePath: effectiveFilename,
- messages: fixedResult.messages,
- errorCount: stats.errorCount,
- warningCount: stats.warningCount,
- fixableErrorCount: stats.fixableErrorCount,
- fixableWarningCount: stats.fixableWarningCount
- };
-
- if (fixedResult.fixed) {
- result.output = fixedResult.output;
- }
-
- if (result.errorCount + result.warningCount > 0 && typeof result.output === "undefined") {
- result.source = text;
- }
-
- return { result, config };
- }
-
- /**
- * Processes an individual file using ESLint. Files used here are known to
- * exist, so no need to check that here.
- * @param {string} filename The filename of the file being checked.
- * @param {Object} configHelper The configuration options for ESLint.
- * @param {Object} options The CLIEngine options object.
- * @param {Linter} linter Linter context
- * @returns {{rules: LintResult, config: Object}} The results for linting on this text and the fully-resolved config for it.
- * @private
- */
- function processFile(filename, configHelper, options, linter) {
-
- const text = fs.readFileSync(path.resolve(filename), "utf8");
-
- return processText(
- text,
- configHelper,
- filename,
- options.fix,
- options.allowInlineConfig,
- options.reportUnusedDisableDirectives,
- linter
- );
- }
-
- /**
- * Returns result with warning by ignore settings
- * @param {string} filePath - File path of checked code
- * @param {string} baseDir - Absolute path of base directory
- * @returns {LintResult} Result with single warning
- * @private
- */
- function createIgnoreResult(filePath, baseDir) {
- let message;
- const isHidden = /^\./.test(path.basename(filePath));
- const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
- const isInBowerComponents = baseDir && path.relative(baseDir, filePath).startsWith("bower_components");
-
- if (isHidden) {
- message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
- } else if (isInNodeModules) {
- message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.";
- } else if (isInBowerComponents) {
- message = "File ignored by default. Use \"--ignore-pattern '!bower_components/*'\" to override.";
- } else {
- message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
- }
-
- return {
- filePath: path.resolve(filePath),
- messages: [
- {
- fatal: false,
- severity: 1,
- message
- }
- ],
- errorCount: 0,
- warningCount: 1,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- };
- }
-
- /**
- * Produces rule warnings (i.e. deprecation) from configured rules
- * @param {(Array<string>|Set<string>)} usedRules - Rules configured
- * @param {Map} loadedRules - Map of loaded rules
- * @returns {Array<Object>} Contains rule warnings
- * @private
- */
- function createRuleDeprecationWarnings(usedRules, loadedRules) {
- const usedDeprecatedRules = [];
-
- usedRules.forEach(name => {
- const loadedRule = loadedRules.get(name);
-
- if (loadedRule && loadedRule.meta && loadedRule.meta.deprecated) {
- const deprecatedRule = { ruleId: name };
- const replacedBy = lodash.get(loadedRule, "meta.replacedBy", []);
-
- if (replacedBy.every(newRule => lodash.isString(newRule))) {
- deprecatedRule.replacedBy = replacedBy;
- }
-
- usedDeprecatedRules.push(deprecatedRule);
- }
- });
-
- return usedDeprecatedRules;
- }
-
- /**
- * Checks if the given message is an error message.
- * @param {Object} message The message to check.
- * @returns {boolean} Whether or not the message is an error message.
- * @private
- */
- function isErrorMessage(message) {
- return message.severity === 2;
- }
-
-
- /**
- * return the cacheFile to be used by eslint, based on whether the provided parameter is
- * a directory or looks like a directory (ends in `path.sep`), in which case the file
- * name will be the `cacheFile/.cache_hashOfCWD`
- *
- * if cacheFile points to a file or looks like a file then in will just use that file
- *
- * @param {string} cacheFile The name of file to be used to store the cache
- * @param {string} cwd Current working directory
- * @returns {string} the resolved path to the cache file
- */
- function getCacheFile(cacheFile, cwd) {
-
- /*
- * make sure the path separators are normalized for the environment/os
- * keeping the trailing path separator if present
- */
- const normalizedCacheFile = path.normalize(cacheFile);
-
- const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile);
- const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep;
-
- /**
- * return the name for the cache file in case the provided parameter is a directory
- * @returns {string} the resolved path to the cacheFile
- */
- function getCacheFileForDirectory() {
- return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
- }
-
- let fileStats;
-
- try {
- fileStats = fs.lstatSync(resolvedCacheFile);
- } catch (ex) {
- fileStats = null;
- }
-
-
- /*
- * in case the file exists we need to verify if the provided path
- * is a directory or a file. If it is a directory we want to create a file
- * inside that directory
- */
- if (fileStats) {
-
- /*
- * is a directory or is a file, but the original file the user provided
- * looks like a directory but `path.resolve` removed the `last path.sep`
- * so we need to still treat this like a directory
- */
- if (fileStats.isDirectory() || looksLikeADirectory) {
- return getCacheFileForDirectory();
- }
-
- // is file so just use that file
- return resolvedCacheFile;
- }
-
- /*
- * here we known the file or directory doesn't exist,
- * so we will try to infer if its a directory if it looks like a directory
- * for the current operating system.
- */
-
- // if the last character passed is a path separator we assume is a directory
- if (looksLikeADirectory) {
- return getCacheFileForDirectory();
- }
-
- return resolvedCacheFile;
- }
-
- //------------------------------------------------------------------------------
- // Public Interface
- //------------------------------------------------------------------------------
-
- class CLIEngine {
-
- /**
- * Creates a new instance of the core CLI engine.
- * @param {CLIEngineOptions} providedOptions The options for this instance.
- * @constructor
- */
- constructor(providedOptions) {
-
- const options = Object.assign(
- Object.create(null),
- defaultOptions,
- { cwd: process.cwd() },
- providedOptions
- );
-
- /*
- * if an --ignore-path option is provided, ensure that the ignore
- * file exists and is not a directory
- */
- if (options.ignore && options.ignorePath) {
- try {
- if (!fs.statSync(options.ignorePath).isFile()) {
- throw new Error(`${options.ignorePath} is not a file`);
- }
- } catch (e) {
- e.message = `Error: Could not load file ${options.ignorePath}\nError: ${e.message}`;
- throw e;
- }
- }
-
- /**
- * Stored options for this instance
- * @type {Object}
- */
- this.options = options;
- this.linter = new Linter();
-
- // load in additional rules
- if (this.options.rulePaths) {
- const cwd = this.options.cwd;
-
- this.options.rulePaths.forEach(rulesdir => {
- debug(`Loading rules from ${rulesdir}`);
- this.linter.rules.load(rulesdir, cwd);
- });
- }
-
- if (this.options.rules && Object.keys(this.options.rules).length) {
- const loadedRules = this.linter.getRules();
-
- Object.keys(this.options.rules).forEach(name => {
- validator.validateRuleOptions(loadedRules.get(name), name, this.options.rules[name], "CLI");
- });
- }
-
- this.config = new Config(this.options, this.linter);
-
- if (this.options.cache) {
- const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
-
- /**
- * Cache used to avoid operating on files that haven't changed since the
- * last successful execution.
- * @type {Object}
- */
- this._lintResultCache = new LintResultCache(cacheFile, this.config);
- }
-
- // setup special filter for fixes
- if (this.options.fix && this.options.fixTypes && this.options.fixTypes.length > 0) {
-
- debug(`Using fix types ${this.options.fixTypes}`);
-
- // throw an error if any invalid fix types are found
- validateFixTypes(this.options.fixTypes);
-
- // convert to Set for faster lookup
- const fixTypes = new Set(this.options.fixTypes);
-
- // save original value of options.fix in case it's a function
- const originalFix = (typeof this.options.fix === "function")
- ? this.options.fix : () => this.options.fix;
-
- // create a cache of rules (but don't populate until needed)
- this._rulesCache = null;
-
- this.options.fix = lintResult => {
- const rule = this._rulesCache.get(lintResult.ruleId);
- const matches = rule.meta && fixTypes.has(rule.meta.type);
-
- return matches && originalFix(lintResult);
- };
- }
-
- }
-
- getRules() {
- return this.linter.getRules();
- }
-
- /**
- * Returns results that only contains errors.
- * @param {LintResult[]} results The results to filter.
- * @returns {LintResult[]} The filtered results.
- */
- static getErrorResults(results) {
- const filtered = [];
-
- results.forEach(result => {
- const filteredMessages = result.messages.filter(isErrorMessage);
-
- if (filteredMessages.length > 0) {
- filtered.push(
- Object.assign(result, {
- messages: filteredMessages,
- errorCount: filteredMessages.length,
- warningCount: 0,
- fixableErrorCount: result.fixableErrorCount,
- fixableWarningCount: 0
- })
- );
- }
- });
-
- return filtered;
- }
-
- /**
- * Outputs fixes from the given results to files.
- * @param {Object} report The report object created by CLIEngine.
- * @returns {void}
- */
- static outputFixes(report) {
- report.results.filter(result => Object.prototype.hasOwnProperty.call(result, "output")).forEach(result => {
- fs.writeFileSync(result.filePath, result.output);
- });
- }
-
-
- /**
- * Add a plugin by passing its configuration
- * @param {string} name Name of the plugin.
- * @param {Object} pluginobject Plugin configuration object.
- * @returns {void}
- */
- addPlugin(name, pluginobject) {
- this.config.plugins.define(name, pluginobject);
- }
-
- /**
- * Resolves the patterns passed into executeOnFiles() into glob-based patterns
- * for easier handling.
- * @param {string[]} patterns The file patterns passed on the command line.
- * @returns {string[]} The equivalent glob patterns.
- */
- resolveFileGlobPatterns(patterns) {
- return globUtils.resolveFileGlobPatterns(patterns.filter(Boolean), this.options);
- }
-
- /**
- * Executes the current configuration on an array of file and directory names.
- * @param {string[]} patterns An array of file and directory names.
- * @returns {Object} The results for all files that were linted.
- */
- executeOnFiles(patterns) {
- const options = this.options,
- lintResultCache = this._lintResultCache,
- configHelper = this.config;
- const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
-
- if (!options.cache && fs.existsSync(cacheFile)) {
- fs.unlinkSync(cacheFile);
- }
-
- const startTime = Date.now();
- const fileList = globUtils.listFilesToProcess(patterns, options);
- const allUsedRules = new Set();
- const results = fileList.map(fileInfo => {
- if (fileInfo.ignored) {
- return createIgnoreResult(fileInfo.filename, options.cwd);
- }
-
- if (options.cache) {
- const cachedLintResults = lintResultCache.getCachedLintResults(fileInfo.filename);
-
- if (cachedLintResults) {
- const resultHadMessages = cachedLintResults.messages && cachedLintResults.messages.length;
-
- if (resultHadMessages && options.fix) {
- debug(`Reprocessing cached file to allow autofix: ${fileInfo.filename}`);
- } else {
- debug(`Skipping file since it hasn't changed: ${fileInfo.filename}`);
-
- return cachedLintResults;
- }
- }
- }
-
- // if there's a cache, populate it
- if ("_rulesCache" in this) {
- this._rulesCache = this.getRules();
- }
-
- debug(`Processing ${fileInfo.filename}`);
-
- const { result, config } = processFile(fileInfo.filename, configHelper, options, this.linter);
-
- Object.keys(config.rules)
- .filter(ruleId => ConfigOps.getRuleSeverity(config.rules[ruleId]))
- .forEach(ruleId => allUsedRules.add(ruleId));
-
- return result;
- });
-
- if (options.cache) {
- results.forEach(result => {
-
- /*
- * Store the lint result in the LintResultCache.
- * NOTE: The LintResultCache will remove the file source and any
- * other properties that are difficult to serialize, and will
- * hydrate those properties back in on future lint runs.
- */
- lintResultCache.setCachedLintResults(result.filePath, result);
- });
-
- // persist the cache to disk
- lintResultCache.reconcile();
- }
-
- const stats = calculateStatsPerRun(results);
-
- const usedDeprecatedRules = createRuleDeprecationWarnings(allUsedRules, this.getRules());
-
- debug(`Linting complete in: ${Date.now() - startTime}ms`);
-
- return {
- results,
- errorCount: stats.errorCount,
- warningCount: stats.warningCount,
- fixableErrorCount: stats.fixableErrorCount,
- fixableWarningCount: stats.fixableWarningCount,
- usedDeprecatedRules
- };
- }
-
- /**
- * Executes the current configuration on text.
- * @param {string} text A string of JavaScript code to lint.
- * @param {string} filename An optional string representing the texts filename.
- * @param {boolean} warnIgnored Always warn when a file is ignored
- * @returns {Object} The results for the linting.
- */
- executeOnText(text, filename, warnIgnored) {
-
- const results = [],
- options = this.options,
- configHelper = this.config,
- ignoredPaths = new IgnoredPaths(options);
-
- // resolve filename based on options.cwd (for reporting, ignoredPaths also resolves)
-
- const resolvedFilename = filename && !path.isAbsolute(filename)
- ? path.resolve(options.cwd, filename)
- : filename;
- let usedDeprecatedRules;
-
- if (resolvedFilename && ignoredPaths.contains(resolvedFilename)) {
- if (warnIgnored) {
- results.push(createIgnoreResult(resolvedFilename, options.cwd));
- }
- usedDeprecatedRules = [];
- } else {
-
- // if there's a cache, populate it
- if ("_rulesCache" in this) {
- this._rulesCache = this.getRules();
- }
-
- const { result, config } = processText(
- text,
- configHelper,
- resolvedFilename,
- options.fix,
- options.allowInlineConfig,
- options.reportUnusedDisableDirectives,
- this.linter
- );
-
- results.push(result);
- usedDeprecatedRules = createRuleDeprecationWarnings(
- Object.keys(config.rules).filter(rule => ConfigOps.getRuleSeverity(config.rules[rule])),
- this.getRules()
- );
- }
-
- const stats = calculateStatsPerRun(results);
-
- return {
- results,
- errorCount: stats.errorCount,
- warningCount: stats.warningCount,
- fixableErrorCount: stats.fixableErrorCount,
- fixableWarningCount: stats.fixableWarningCount,
- usedDeprecatedRules
- };
- }
-
- /**
- * Returns a configuration object for the given file based on the CLI options.
- * This is the same logic used by the ESLint CLI executable to determine
- * configuration for each file it processes.
- * @param {string} filePath The path of the file to retrieve a config object for.
- * @returns {Object} A configuration object for the file.
- */
- getConfigForFile(filePath) {
- const configHelper = this.config;
-
- return configHelper.getConfig(filePath);
- }
-
- /**
- * Checks if a given path is ignored by ESLint.
- * @param {string} filePath The path of the file to check.
- * @returns {boolean} Whether or not the given path is ignored.
- */
- isPathIgnored(filePath) {
- const resolvedPath = path.resolve(this.options.cwd, filePath);
- const ignoredPaths = new IgnoredPaths(this.options);
-
- return ignoredPaths.contains(resolvedPath);
- }
-
- /**
- * Returns the formatter representing the given format or null if no formatter
- * with the given name can be found.
- * @param {string} [format] The name of the format to load or the path to a
- * custom formatter.
- * @returns {Function} The formatter function or null if not found.
- */
- getFormatter(format) {
-
- // default is stylish
- const resolvedFormatName = format || "stylish";
-
- // only strings are valid formatters
- if (typeof resolvedFormatName === "string") {
-
- // replace \ with / for Windows compatibility
- const normalizedFormatName = resolvedFormatName.replace(/\\/g, "/");
-
- const cwd = this.options ? this.options.cwd : process.cwd();
- const namespace = naming.getNamespaceFromTerm(normalizedFormatName);
-
- let formatterPath;
-
- // if there's a slash, then it's a file
- if (!namespace && normalizedFormatName.indexOf("/") > -1) {
- formatterPath = path.resolve(cwd, normalizedFormatName);
- } else {
- try {
- const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
-
- formatterPath = resolver.resolve(npmFormat, `${cwd}/node_modules`);
- } catch (e) {
- formatterPath = `./formatters/${normalizedFormatName}`;
- }
- }
-
- try {
- return require(formatterPath);
- } catch (ex) {
- ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
- throw ex;
- }
-
- } else {
- return null;
- }
- }
- }
-
- CLIEngine.version = pkg.version;
- CLIEngine.getFormatter = CLIEngine.prototype.getFormatter;
-
- module.exports = CLIEngine;
|