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