38df9d5f3bd75cc1bf582965913299f89e478362718cb4ccf16ce23aa14c3b0292e85796cef37e810027ee43859ad756698c59e02831227a4909bad5f44aa6 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. /**
  2. * @fileoverview Main API Class
  3. * @author Kai Cataldo
  4. * @author Toru Nagashima
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const path = require("path");
  11. const fs = require("fs");
  12. const { promisify } = require("util");
  13. const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
  14. const BuiltinRules = require("../rules");
  15. const {
  16. Legacy: {
  17. ConfigOps: {
  18. getRuleSeverity
  19. }
  20. }
  21. } = require("@eslint/eslintrc");
  22. const { version } = require("../../package.json");
  23. //------------------------------------------------------------------------------
  24. // Typedefs
  25. //------------------------------------------------------------------------------
  26. /** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */
  27. /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
  28. /** @typedef {import("../shared/types").ConfigData} ConfigData */
  29. /** @typedef {import("../shared/types").LintMessage} LintMessage */
  30. /** @typedef {import("../shared/types").Plugin} Plugin */
  31. /** @typedef {import("../shared/types").Rule} Rule */
  32. /** @typedef {import("./load-formatter").Formatter} Formatter */
  33. /**
  34. * The options with which to configure the ESLint instance.
  35. * @typedef {Object} ESLintOptions
  36. * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
  37. * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
  38. * @property {boolean} [cache] Enable result caching.
  39. * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
  40. * @property {string} [cwd] The value to use for the current working directory.
  41. * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
  42. * @property {string[]} [extensions] An array of file extensions to check.
  43. * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
  44. * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
  45. * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
  46. * @property {boolean} [ignore] False disables use of .eslintignore.
  47. * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
  48. * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
  49. * @property {string} [overrideConfigFile] The configuration file to use.
  50. * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
  51. * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
  52. * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
  53. * @property {string[]} [rulePaths] An array of directories to load custom rules from.
  54. * @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files.
  55. */
  56. /**
  57. * A rules metadata object.
  58. * @typedef {Object} RulesMeta
  59. * @property {string} id The plugin ID.
  60. * @property {Object} definition The plugin definition.
  61. */
  62. /**
  63. * A linting result.
  64. * @typedef {Object} LintResult
  65. * @property {string} filePath The path to the file that was linted.
  66. * @property {LintMessage[]} messages All of the messages for the result.
  67. * @property {number} errorCount Number of errors for the result.
  68. * @property {number} warningCount Number of warnings for the result.
  69. * @property {number} fixableErrorCount Number of fixable errors for the result.
  70. * @property {number} fixableWarningCount Number of fixable warnings for the result.
  71. * @property {string} [source] The source code of the file that was linted.
  72. * @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
  73. * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
  74. */
  75. /**
  76. * Private members for the `ESLint` instance.
  77. * @typedef {Object} ESLintPrivateMembers
  78. * @property {CLIEngine} cliEngine The wrapped CLIEngine instance.
  79. * @property {ESLintOptions} options The options used to instantiate the ESLint instance.
  80. */
  81. //------------------------------------------------------------------------------
  82. // Helpers
  83. //------------------------------------------------------------------------------
  84. const writeFile = promisify(fs.writeFile);
  85. /**
  86. * The map with which to store private class members.
  87. * @type {WeakMap<ESLint, ESLintPrivateMembers>}
  88. */
  89. const privateMembersMap = new WeakMap();
  90. /**
  91. * Check if a given value is a non-empty string or not.
  92. * @param {any} x The value to check.
  93. * @returns {boolean} `true` if `x` is a non-empty string.
  94. */
  95. function isNonEmptyString(x) {
  96. return typeof x === "string" && x.trim() !== "";
  97. }
  98. /**
  99. * Check if a given value is an array of non-empty stringss or not.
  100. * @param {any} x The value to check.
  101. * @returns {boolean} `true` if `x` is an array of non-empty stringss.
  102. */
  103. function isArrayOfNonEmptyString(x) {
  104. return Array.isArray(x) && x.every(isNonEmptyString);
  105. }
  106. /**
  107. * Check if a given value is a valid fix type or not.
  108. * @param {any} x The value to check.
  109. * @returns {boolean} `true` if `x` is valid fix type.
  110. */
  111. function isFixType(x) {
  112. return x === "problem" || x === "suggestion" || x === "layout";
  113. }
  114. /**
  115. * Check if a given value is an array of fix types or not.
  116. * @param {any} x The value to check.
  117. * @returns {boolean} `true` if `x` is an array of fix types.
  118. */
  119. function isFixTypeArray(x) {
  120. return Array.isArray(x) && x.every(isFixType);
  121. }
  122. /**
  123. * The error for invalid options.
  124. */
  125. class ESLintInvalidOptionsError extends Error {
  126. constructor(messages) {
  127. super(`Invalid Options:\n- ${messages.join("\n- ")}`);
  128. this.code = "ESLINT_INVALID_OPTIONS";
  129. Error.captureStackTrace(this, ESLintInvalidOptionsError);
  130. }
  131. }
  132. /**
  133. * Validates and normalizes options for the wrapped CLIEngine instance.
  134. * @param {ESLintOptions} options The options to process.
  135. * @returns {ESLintOptions} The normalized options.
  136. */
  137. function processOptions({
  138. allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
  139. baseConfig = null,
  140. cache = false,
  141. cacheLocation = ".eslintcache",
  142. cwd = process.cwd(),
  143. errorOnUnmatchedPattern = true,
  144. extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
  145. fix = false,
  146. fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
  147. globInputPaths = true,
  148. ignore = true,
  149. ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT.
  150. overrideConfig = null,
  151. overrideConfigFile = null,
  152. plugins = {},
  153. reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that.
  154. resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature.
  155. rulePaths = [],
  156. useEslintrc = true,
  157. ...unknownOptions
  158. }) {
  159. const errors = [];
  160. const unknownOptionKeys = Object.keys(unknownOptions);
  161. if (unknownOptionKeys.length >= 1) {
  162. errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
  163. if (unknownOptionKeys.includes("cacheFile")) {
  164. errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
  165. }
  166. if (unknownOptionKeys.includes("configFile")) {
  167. errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
  168. }
  169. if (unknownOptionKeys.includes("envs")) {
  170. errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead.");
  171. }
  172. if (unknownOptionKeys.includes("globals")) {
  173. errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead.");
  174. }
  175. if (unknownOptionKeys.includes("ignorePattern")) {
  176. errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
  177. }
  178. if (unknownOptionKeys.includes("parser")) {
  179. errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead.");
  180. }
  181. if (unknownOptionKeys.includes("parserOptions")) {
  182. errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.");
  183. }
  184. if (unknownOptionKeys.includes("rules")) {
  185. errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
  186. }
  187. }
  188. if (typeof allowInlineConfig !== "boolean") {
  189. errors.push("'allowInlineConfig' must be a boolean.");
  190. }
  191. if (typeof baseConfig !== "object") {
  192. errors.push("'baseConfig' must be an object or null.");
  193. }
  194. if (typeof cache !== "boolean") {
  195. errors.push("'cache' must be a boolean.");
  196. }
  197. if (!isNonEmptyString(cacheLocation)) {
  198. errors.push("'cacheLocation' must be a non-empty string.");
  199. }
  200. if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
  201. errors.push("'cwd' must be an absolute path.");
  202. }
  203. if (typeof errorOnUnmatchedPattern !== "boolean") {
  204. errors.push("'errorOnUnmatchedPattern' must be a boolean.");
  205. }
  206. if (!isArrayOfNonEmptyString(extensions) && extensions !== null) {
  207. errors.push("'extensions' must be an array of non-empty strings or null.");
  208. }
  209. if (typeof fix !== "boolean" && typeof fix !== "function") {
  210. errors.push("'fix' must be a boolean or a function.");
  211. }
  212. if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
  213. errors.push("'fixTypes' must be an array of any of \"problem\", \"suggestion\", and \"layout\".");
  214. }
  215. if (typeof globInputPaths !== "boolean") {
  216. errors.push("'globInputPaths' must be a boolean.");
  217. }
  218. if (typeof ignore !== "boolean") {
  219. errors.push("'ignore' must be a boolean.");
  220. }
  221. if (!isNonEmptyString(ignorePath) && ignorePath !== null) {
  222. errors.push("'ignorePath' must be a non-empty string or null.");
  223. }
  224. if (typeof overrideConfig !== "object") {
  225. errors.push("'overrideConfig' must be an object or null.");
  226. }
  227. if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) {
  228. errors.push("'overrideConfigFile' must be a non-empty string or null.");
  229. }
  230. if (typeof plugins !== "object") {
  231. errors.push("'plugins' must be an object or null.");
  232. } else if (plugins !== null && Object.keys(plugins).includes("")) {
  233. errors.push("'plugins' must not include an empty string.");
  234. }
  235. if (Array.isArray(plugins)) {
  236. errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
  237. }
  238. if (
  239. reportUnusedDisableDirectives !== "error" &&
  240. reportUnusedDisableDirectives !== "warn" &&
  241. reportUnusedDisableDirectives !== "off" &&
  242. reportUnusedDisableDirectives !== null
  243. ) {
  244. errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.");
  245. }
  246. if (
  247. !isNonEmptyString(resolvePluginsRelativeTo) &&
  248. resolvePluginsRelativeTo !== null
  249. ) {
  250. errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null.");
  251. }
  252. if (!isArrayOfNonEmptyString(rulePaths)) {
  253. errors.push("'rulePaths' must be an array of non-empty strings.");
  254. }
  255. if (typeof useEslintrc !== "boolean") {
  256. errors.push("'useElintrc' must be a boolean.");
  257. }
  258. if (errors.length > 0) {
  259. throw new ESLintInvalidOptionsError(errors);
  260. }
  261. return {
  262. allowInlineConfig,
  263. baseConfig,
  264. cache,
  265. cacheLocation,
  266. configFile: overrideConfigFile,
  267. cwd,
  268. errorOnUnmatchedPattern,
  269. extensions,
  270. fix,
  271. fixTypes,
  272. globInputPaths,
  273. ignore,
  274. ignorePath,
  275. reportUnusedDisableDirectives,
  276. resolvePluginsRelativeTo,
  277. rulePaths,
  278. useEslintrc
  279. };
  280. }
  281. /**
  282. * Check if a value has one or more properties and that value is not undefined.
  283. * @param {any} obj The value to check.
  284. * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined.
  285. */
  286. function hasDefinedProperty(obj) {
  287. if (typeof obj === "object" && obj !== null) {
  288. for (const key in obj) {
  289. if (typeof obj[key] !== "undefined") {
  290. return true;
  291. }
  292. }
  293. }
  294. return false;
  295. }
  296. /**
  297. * Create rulesMeta object.
  298. * @param {Map<string,Rule>} rules a map of rules from which to generate the object.
  299. * @returns {Object} metadata for all enabled rules.
  300. */
  301. function createRulesMeta(rules) {
  302. return Array.from(rules).reduce((retVal, [id, rule]) => {
  303. retVal[id] = rule.meta;
  304. return retVal;
  305. }, {});
  306. }
  307. /** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
  308. const usedDeprecatedRulesCache = new WeakMap();
  309. /**
  310. * Create used deprecated rule list.
  311. * @param {CLIEngine} cliEngine The CLIEngine instance.
  312. * @param {string} maybeFilePath The absolute path to a lint target file or `"<text>"`.
  313. * @returns {DeprecatedRuleInfo[]} The used deprecated rule list.
  314. */
  315. function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) {
  316. const {
  317. configArrayFactory,
  318. options: { cwd }
  319. } = getCLIEngineInternalSlots(cliEngine);
  320. const filePath = path.isAbsolute(maybeFilePath)
  321. ? maybeFilePath
  322. : path.join(cwd, "__placeholder__.js");
  323. const configArray = configArrayFactory.getConfigArrayForFile(filePath);
  324. const config = configArray.extractConfig(filePath);
  325. // Most files use the same config, so cache it.
  326. if (!usedDeprecatedRulesCache.has(config)) {
  327. const pluginRules = configArray.pluginRules;
  328. const retv = [];
  329. for (const [ruleId, ruleConf] of Object.entries(config.rules)) {
  330. if (getRuleSeverity(ruleConf) === 0) {
  331. continue;
  332. }
  333. const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId);
  334. const meta = rule && rule.meta;
  335. if (meta && meta.deprecated) {
  336. retv.push({ ruleId, replacedBy: meta.replacedBy || [] });
  337. }
  338. }
  339. usedDeprecatedRulesCache.set(config, Object.freeze(retv));
  340. }
  341. return usedDeprecatedRulesCache.get(config);
  342. }
  343. /**
  344. * Processes the linting results generated by a CLIEngine linting report to
  345. * match the ESLint class's API.
  346. * @param {CLIEngine} cliEngine The CLIEngine instance.
  347. * @param {CLIEngineLintReport} report The CLIEngine linting report to process.
  348. * @returns {LintResult[]} The processed linting results.
  349. */
  350. function processCLIEngineLintReport(cliEngine, { results }) {
  351. const descriptor = {
  352. configurable: true,
  353. enumerable: true,
  354. get() {
  355. return getOrFindUsedDeprecatedRules(cliEngine, this.filePath);
  356. }
  357. };
  358. for (const result of results) {
  359. Object.defineProperty(result, "usedDeprecatedRules", descriptor);
  360. }
  361. return results;
  362. }
  363. /**
  364. * An Array.prototype.sort() compatible compare function to order results by their file path.
  365. * @param {LintResult} a The first lint result.
  366. * @param {LintResult} b The second lint result.
  367. * @returns {number} An integer representing the order in which the two results should occur.
  368. */
  369. function compareResultsByFilePath(a, b) {
  370. if (a.filePath < b.filePath) {
  371. return -1;
  372. }
  373. if (a.filePath > b.filePath) {
  374. return 1;
  375. }
  376. return 0;
  377. }
  378. class ESLint {
  379. /**
  380. * Creates a new instance of the main ESLint API.
  381. * @param {ESLintOptions} options The options for this instance.
  382. */
  383. constructor(options = {}) {
  384. const processedOptions = processOptions(options);
  385. const cliEngine = new CLIEngine(processedOptions);
  386. const {
  387. additionalPluginPool,
  388. configArrayFactory,
  389. lastConfigArrays
  390. } = getCLIEngineInternalSlots(cliEngine);
  391. let updated = false;
  392. /*
  393. * Address `plugins` to add plugin implementations.
  394. * Operate the `additionalPluginPool` internal slot directly to avoid
  395. * using `addPlugin(id, plugin)` method that resets cache everytime.
  396. */
  397. if (options.plugins) {
  398. for (const [id, plugin] of Object.entries(options.plugins)) {
  399. additionalPluginPool.set(id, plugin);
  400. updated = true;
  401. }
  402. }
  403. /*
  404. * Address `overrideConfig` to set override config.
  405. * Operate the `configArrayFactory` internal slot directly because this
  406. * functionality doesn't exist as the public API of CLIEngine.
  407. */
  408. if (hasDefinedProperty(options.overrideConfig)) {
  409. configArrayFactory.setOverrideConfig(options.overrideConfig);
  410. updated = true;
  411. }
  412. // Update caches.
  413. if (updated) {
  414. configArrayFactory.clearCache();
  415. lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
  416. }
  417. // Initialize private properties.
  418. privateMembersMap.set(this, {
  419. cliEngine,
  420. options: processedOptions
  421. });
  422. }
  423. /**
  424. * The version text.
  425. * @type {string}
  426. */
  427. static get version() {
  428. return version;
  429. }
  430. /**
  431. * Outputs fixes from the given results to files.
  432. * @param {LintResult[]} results The lint results.
  433. * @returns {Promise<void>} Returns a promise that is used to track side effects.
  434. */
  435. static async outputFixes(results) {
  436. if (!Array.isArray(results)) {
  437. throw new Error("'results' must be an array");
  438. }
  439. await Promise.all(
  440. results
  441. .filter(result => {
  442. if (typeof result !== "object" || result === null) {
  443. throw new Error("'results' must include only objects");
  444. }
  445. return (
  446. typeof result.output === "string" &&
  447. path.isAbsolute(result.filePath)
  448. );
  449. })
  450. .map(r => writeFile(r.filePath, r.output))
  451. );
  452. }
  453. /**
  454. * Returns results that only contains errors.
  455. * @param {LintResult[]} results The results to filter.
  456. * @returns {LintResult[]} The filtered results.
  457. */
  458. static getErrorResults(results) {
  459. return CLIEngine.getErrorResults(results);
  460. }
  461. /**
  462. * Executes the current configuration on an array of file and directory names.
  463. * @param {string[]} patterns An array of file and directory names.
  464. * @returns {Promise<LintResult[]>} The results of linting the file patterns given.
  465. */
  466. async lintFiles(patterns) {
  467. if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
  468. throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
  469. }
  470. const { cliEngine } = privateMembersMap.get(this);
  471. return processCLIEngineLintReport(
  472. cliEngine,
  473. cliEngine.executeOnFiles(patterns)
  474. );
  475. }
  476. /**
  477. * Executes the current configuration on text.
  478. * @param {string} code A string of JavaScript code to lint.
  479. * @param {Object} [options] The options.
  480. * @param {string} [options.filePath] The path to the file of the source code.
  481. * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
  482. * @returns {Promise<LintResult[]>} The results of linting the string of code given.
  483. */
  484. async lintText(code, options = {}) {
  485. if (typeof code !== "string") {
  486. throw new Error("'code' must be a string");
  487. }
  488. if (typeof options !== "object") {
  489. throw new Error("'options' must be an object, null, or undefined");
  490. }
  491. const {
  492. filePath,
  493. warnIgnored = false,
  494. ...unknownOptions
  495. } = options || {};
  496. for (const key of Object.keys(unknownOptions)) {
  497. throw new Error(`'options' must not include the unknown option '${key}'`);
  498. }
  499. if (filePath !== void 0 && !isNonEmptyString(filePath)) {
  500. throw new Error("'options.filePath' must be a non-empty string or undefined");
  501. }
  502. if (typeof warnIgnored !== "boolean") {
  503. throw new Error("'options.warnIgnored' must be a boolean or undefined");
  504. }
  505. const { cliEngine } = privateMembersMap.get(this);
  506. return processCLIEngineLintReport(
  507. cliEngine,
  508. cliEngine.executeOnText(code, filePath, warnIgnored)
  509. );
  510. }
  511. /**
  512. * Returns the formatter representing the given formatter name.
  513. * @param {string} [name] The name of the formattter to load.
  514. * The following values are allowed:
  515. * - `undefined` ... Load `stylish` builtin formatter.
  516. * - A builtin formatter name ... Load the builtin formatter.
  517. * - A thirdparty formatter name:
  518. * - `foo` → `eslint-formatter-foo`
  519. * - `@foo` → `@foo/eslint-formatter`
  520. * - `@foo/bar` → `@foo/eslint-formatter-bar`
  521. * - A file path ... Load the file.
  522. * @returns {Promise<Formatter>} A promise resolving to the formatter object.
  523. * This promise will be rejected if the given formatter was not found or not
  524. * a function.
  525. */
  526. async loadFormatter(name = "stylish") {
  527. if (typeof name !== "string") {
  528. throw new Error("'name' must be a string");
  529. }
  530. const { cliEngine } = privateMembersMap.get(this);
  531. const formatter = cliEngine.getFormatter(name);
  532. if (typeof formatter !== "function") {
  533. throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`);
  534. }
  535. return {
  536. /**
  537. * The main formatter method.
  538. * @param {LintResults[]} results The lint results to format.
  539. * @returns {string} The formatted lint results.
  540. */
  541. format(results) {
  542. let rulesMeta = null;
  543. results.sort(compareResultsByFilePath);
  544. return formatter(results, {
  545. get rulesMeta() {
  546. if (!rulesMeta) {
  547. rulesMeta = createRulesMeta(cliEngine.getRules());
  548. }
  549. return rulesMeta;
  550. }
  551. });
  552. }
  553. };
  554. }
  555. /**
  556. * Returns a configuration object for the given file based on the CLI options.
  557. * This is the same logic used by the ESLint CLI executable to determine
  558. * configuration for each file it processes.
  559. * @param {string} filePath The path of the file to retrieve a config object for.
  560. * @returns {Promise<ConfigData>} A configuration object for the file.
  561. */
  562. async calculateConfigForFile(filePath) {
  563. if (!isNonEmptyString(filePath)) {
  564. throw new Error("'filePath' must be a non-empty string");
  565. }
  566. const { cliEngine } = privateMembersMap.get(this);
  567. return cliEngine.getConfigForFile(filePath);
  568. }
  569. /**
  570. * Checks if a given path is ignored by ESLint.
  571. * @param {string} filePath The path of the file to check.
  572. * @returns {Promise<boolean>} Whether or not the given path is ignored.
  573. */
  574. async isPathIgnored(filePath) {
  575. if (!isNonEmptyString(filePath)) {
  576. throw new Error("'filePath' must be a non-empty string");
  577. }
  578. const { cliEngine } = privateMembersMap.get(this);
  579. return cliEngine.isPathIgnored(filePath);
  580. }
  581. }
  582. //------------------------------------------------------------------------------
  583. // Public Interface
  584. //------------------------------------------------------------------------------
  585. module.exports = {
  586. ESLint,
  587. /**
  588. * Get the private class members of a given ESLint instance for tests.
  589. * @param {ESLint} instance The ESLint instance to get.
  590. * @returns {ESLintPrivateMembers} The instance's private class members.
  591. */
  592. getESLintPrivateMembers(instance) {
  593. return privateMembersMap.get(instance);
  594. }
  595. };