eslint.js 26 KB

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