cli-engine.js 28 KB

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