]> git.proxmox.com Git - pve-eslint.git/blame - eslint/lib/linter/linter.js
bump version to 8.4.0-3
[pve-eslint.git] / eslint / lib / linter / linter.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Main Linter Class
3 * @author Gyandeep Singh
4 * @author aladdin-add
5 */
6
7"use strict";
8
9//------------------------------------------------------------------------------
10// Requirements
11//------------------------------------------------------------------------------
12
13const
14 path = require("path"),
15 eslintScope = require("eslint-scope"),
16 evk = require("eslint-visitor-keys"),
17 espree = require("espree"),
5422a9cc 18 merge = require("lodash.merge"),
eb39fafa
DC
19 pkg = require("../../package.json"),
20 astUtils = require("../shared/ast-utils"),
609c276f
TL
21 {
22 Legacy: {
23 ConfigOps,
24 ConfigValidator,
25 environments: BuiltInEnvironments
26 }
27 } = require("@eslint/eslintrc/universal"),
eb39fafa
DC
28 Traverser = require("../shared/traverser"),
29 { SourceCode } = require("../source-code"),
30 CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
31 applyDisableDirectives = require("./apply-disable-directives"),
32 ConfigCommentParser = require("./config-comment-parser"),
33 NodeEventGenerator = require("./node-event-generator"),
34 createReportTranslator = require("./report-translator"),
35 Rules = require("./rules"),
36 createEmitter = require("./safe-emitter"),
37 SourceCodeFixer = require("./source-code-fixer"),
38 timing = require("./timing"),
39 ruleReplacements = require("../../conf/replacements.json");
34eeec05
TL
40const { getRuleFromConfig } = require("../config/flat-config-helpers");
41const { FlatConfigArray } = require("../config/flat-config-array");
eb39fafa
DC
42
43const debug = require("debug")("eslint:linter");
44const MAX_AUTOFIX_PASSES = 10;
45const DEFAULT_PARSER_NAME = "espree";
609c276f 46const DEFAULT_ECMA_VERSION = 5;
eb39fafa
DC
47const commentParser = new ConfigCommentParser();
48const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
609c276f 49const parserSymbol = Symbol.for("eslint.RuleTester.parser");
34eeec05 50const globals = require("../../conf/globals");
eb39fafa
DC
51
52//------------------------------------------------------------------------------
53// Typedefs
54//------------------------------------------------------------------------------
55
609c276f
TL
56/** @typedef {InstanceType<import("../cli-engine/config-array").ConfigArray>} ConfigArray */
57/** @typedef {InstanceType<import("../cli-engine/config-array").ExtractedConfig>} ExtractedConfig */
eb39fafa
DC
58/** @typedef {import("../shared/types").ConfigData} ConfigData */
59/** @typedef {import("../shared/types").Environment} Environment */
60/** @typedef {import("../shared/types").GlobalConf} GlobalConf */
61/** @typedef {import("../shared/types").LintMessage} LintMessage */
62/** @typedef {import("../shared/types").ParserOptions} ParserOptions */
34eeec05 63/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
eb39fafa
DC
64/** @typedef {import("../shared/types").Processor} Processor */
65/** @typedef {import("../shared/types").Rule} Rule */
66
609c276f 67/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
eb39fafa
DC
68/**
69 * @template T
70 * @typedef {{ [P in keyof T]-?: T[P] }} Required
71 */
609c276f 72/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
eb39fafa
DC
73
74/**
75 * @typedef {Object} DisableDirective
609c276f
TL
76 * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type Type of directive
77 * @property {number} line The line number
78 * @property {number} column The column number
79 * @property {(string|null)} ruleId The rule ID
eb39fafa
DC
80 */
81
82/**
83 * The private data for `Linter` instance.
84 * @typedef {Object} LinterInternalSlots
85 * @property {ConfigArray|null} lastConfigArray The `ConfigArray` instance that the last `verify()` call used.
86 * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used.
87 * @property {Map<string, Parser>} parserMap The loaded parsers.
88 * @property {Rules} ruleMap The loaded rules.
89 */
90
91/**
92 * @typedef {Object} VerifyOptions
93 * @property {boolean} [allowInlineConfig] Allow/disallow inline comments' ability
94 * to change config once it is set. Defaults to true if not supplied.
95 * Useful if you want to validate JS without comments overriding rules.
96 * @property {boolean} [disableFixes] if `true` then the linter doesn't make `fix`
97 * properties into the lint result.
98 * @property {string} [filename] the filename of the source code.
99 * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
100 * unused `eslint-disable` directives.
101 */
102
103/**
104 * @typedef {Object} ProcessorOptions
105 * @property {(filename:string, text:string) => boolean} [filterCodeBlock] the
106 * predicate function that selects adopt code blocks.
609c276f 107 * @property {Processor.postprocess} [postprocess] postprocessor for report
eb39fafa
DC
108 * messages. If provided, this should accept an array of the message lists
109 * for each code block returned from the preprocessor, apply a mapping to
110 * the messages as appropriate, and return a one-dimensional array of
111 * messages.
609c276f 112 * @property {Processor.preprocess} [preprocess] preprocessor for source text.
eb39fafa
DC
113 * If provided, this should accept a string of source text, and return an
114 * array of code blocks to lint.
115 */
116
117/**
118 * @typedef {Object} FixOptions
119 * @property {boolean | ((message: LintMessage) => boolean)} [fix] Determines
120 * whether fixes should be applied.
121 */
122
123/**
124 * @typedef {Object} InternalOptions
125 * @property {string | null} warnInlineConfig The config name what `noInlineConfig` setting came from. If `noInlineConfig` setting didn't exist, this is null. If this is a config name, then the linter warns directive comments.
126 * @property {"off" | "warn" | "error"} reportUnusedDisableDirectives (boolean values were normalized)
127 */
128
129//------------------------------------------------------------------------------
130// Helpers
131//------------------------------------------------------------------------------
132
34eeec05
TL
133/**
134 * Determines if a given object is Espree.
135 * @param {Object} parser The parser to check.
136 * @returns {boolean} True if the parser is Espree or false if not.
137 */
138function isEspree(parser) {
139 return !!(parser === espree || parser[parserSymbol] === espree);
140}
141
142/**
143 * Retrieves globals for the given ecmaVersion.
144 * @param {number} ecmaVersion The version to retrieve globals for.
145 * @returns {Object} The globals for the given ecmaVersion.
146 */
147function getGlobalsForEcmaVersion(ecmaVersion) {
148
149 switch (ecmaVersion) {
150 case 3:
151 return globals.es3;
152
153 case 5:
154 return globals.es5;
155
156 default:
157 if (ecmaVersion < 2015) {
158 return globals[`es${ecmaVersion + 2009}`];
159 }
160
161 return globals[`es${ecmaVersion}`];
162 }
163}
164
eb39fafa
DC
165/**
166 * Ensures that variables representing built-in properties of the Global Object,
167 * and any globals declared by special block comments, are present in the global
168 * scope.
169 * @param {Scope} globalScope The global scope.
170 * @param {Object} configGlobals The globals declared in configuration
171 * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
172 * @returns {void}
173 */
174function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) {
175
176 // Define configured global variables.
177 for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) {
178
179 /*
180 * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
181 * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
182 */
183 const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]);
184 const commentValue = enabledGlobals[id] && enabledGlobals[id].value;
185 const value = commentValue || configValue;
186 const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments;
187
188 if (value === "off") {
189 continue;
190 }
191
192 let variable = globalScope.set.get(id);
193
194 if (!variable) {
195 variable = new eslintScope.Variable(id, globalScope);
196
197 globalScope.variables.push(variable);
198 globalScope.set.set(id, variable);
199 }
200
201 variable.eslintImplicitGlobalSetting = configValue;
202 variable.eslintExplicitGlobal = sourceComments !== void 0;
203 variable.eslintExplicitGlobalComments = sourceComments;
204 variable.writeable = (value === "writable");
205 }
206
207 // mark all exported variables as such
208 Object.keys(exportedVariables).forEach(name => {
209 const variable = globalScope.set.get(name);
210
211 if (variable) {
212 variable.eslintUsed = true;
213 }
214 });
215
216 /*
217 * "through" contains all references which definitions cannot be found.
218 * Since we augment the global scope using configuration, we need to update
219 * references and remove the ones that were added by configuration.
220 */
221 globalScope.through = globalScope.through.filter(reference => {
222 const name = reference.identifier.name;
223 const variable = globalScope.set.get(name);
224
225 if (variable) {
226
227 /*
228 * Links the variable and the reference.
229 * And this reference is removed from `Scope#through`.
230 */
231 reference.resolved = variable;
232 variable.references.push(reference);
233
234 return false;
235 }
236
237 return true;
238 });
239}
240
241/**
242 * creates a missing-rule message.
243 * @param {string} ruleId the ruleId to create
244 * @returns {string} created error message
245 * @private
246 */
247function createMissingRuleMessage(ruleId) {
248 return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId)
249 ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
250 : `Definition for rule '${ruleId}' was not found.`;
251}
252
253/**
254 * creates a linting problem
255 * @param {Object} options to create linting error
256 * @param {string} [options.ruleId] the ruleId to report
257 * @param {Object} [options.loc] the loc to report
258 * @param {string} [options.message] the error message to report
259 * @param {string} [options.severity] the error message to report
260 * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
261 * @private
262 */
263function createLintingProblem(options) {
264 const {
265 ruleId = null,
266 loc = DEFAULT_ERROR_LOC,
267 message = createMissingRuleMessage(options.ruleId),
268 severity = 2
269 } = options;
270
271 return {
272 ruleId,
273 message,
274 line: loc.start.line,
275 column: loc.start.column + 1,
276 endLine: loc.end.line,
277 endColumn: loc.end.column + 1,
278 severity,
279 nodeType: null
280 };
281}
282
283/**
284 * Creates a collection of disable directives from a comment
285 * @param {Object} options to create disable directives
286 * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
609c276f 287 * @param {token} options.commentToken The Comment token
eb39fafa
DC
288 * @param {string} options.value The value after the directive in the comment
289 * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
290 * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules
291 * @returns {Object} Directives and problems from the comment
292 */
293function createDisableDirectives(options) {
609c276f 294 const { commentToken, type, value, ruleMapper } = options;
eb39fafa
DC
295 const ruleIds = Object.keys(commentParser.parseListConfig(value));
296 const directiveRules = ruleIds.length ? ruleIds : [null];
297 const result = {
298 directives: [], // valid disable directives
299 directiveProblems: [] // problems in directives
300 };
301
609c276f
TL
302 const parentComment = { commentToken, ruleIds };
303
eb39fafa
DC
304 for (const ruleId of directiveRules) {
305
306 // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
34eeec05 307 if (ruleId === null || !!ruleMapper(ruleId)) {
609c276f 308 result.directives.push({ parentComment, type, line: commentToken.loc.start.line, column: commentToken.loc.start.column + 1, ruleId });
eb39fafa 309 } else {
609c276f 310 result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc }));
eb39fafa
DC
311 }
312 }
313 return result;
314}
315
316/**
317 * Remove the ignored part from a given directive comment and trim it.
318 * @param {string} value The comment text to strip.
319 * @returns {string} The stripped text.
320 */
321function stripDirectiveComment(value) {
322 return value.split(/\s-{2,}\s/u)[0].trim();
323}
324
325/**
326 * Parses comments in file to extract file-specific config of rules, globals
327 * and environments and merges them with global config; also code blocks
328 * where reporting is disabled or enabled and merges them with reporting config.
329 * @param {string} filename The file being checked.
330 * @param {ASTNode} ast The top node of the AST.
331 * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
332 * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
333 * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
334 * A collection of the directive comments that were found, along with any problems that occurred when parsing
335 */
336function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
337 const configuredRules = {};
338 const enabledGlobals = Object.create(null);
339 const exportedVariables = {};
340 const problems = [];
341 const disableDirectives = [];
6f036462
TL
342 const validator = new ConfigValidator({
343 builtInRules: Rules
344 });
eb39fafa
DC
345
346 ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
347 const trimmedCommentText = stripDirectiveComment(comment.value);
348 const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText);
349
350 if (!match) {
351 return;
352 }
353 const directiveText = match[1];
354 const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
355
356 if (comment.type === "Line" && !lineCommentSupported) {
357 return;
358 }
359
360 if (warnInlineConfig) {
361 const kind = comment.type === "Block" ? `/*${directiveText}*/` : `//${directiveText}`;
362
363 problems.push(createLintingProblem({
364 ruleId: null,
365 message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
366 loc: comment.loc,
367 severity: 1
368 }));
369 return;
370 }
371
372 if (lineCommentSupported && comment.loc.start.line !== comment.loc.end.line) {
373 const message = `${directiveText} comment should not span multiple lines.`;
374
375 problems.push(createLintingProblem({
376 ruleId: null,
377 message,
378 loc: comment.loc
379 }));
380 return;
381 }
382
383 const directiveValue = trimmedCommentText.slice(match.index + directiveText.length);
384
385 switch (directiveText) {
386 case "eslint-disable":
387 case "eslint-enable":
388 case "eslint-disable-next-line":
389 case "eslint-disable-line": {
390 const directiveType = directiveText.slice("eslint-".length);
609c276f 391 const options = { commentToken: comment, type: directiveType, value: directiveValue, ruleMapper };
eb39fafa
DC
392 const { directives, directiveProblems } = createDisableDirectives(options);
393
394 disableDirectives.push(...directives);
395 problems.push(...directiveProblems);
396 break;
397 }
398
399 case "exported":
400 Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
401 break;
402
403 case "globals":
404 case "global":
405 for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
406 let normalizedValue;
407
408 try {
409 normalizedValue = ConfigOps.normalizeConfigGlobal(value);
410 } catch (err) {
411 problems.push(createLintingProblem({
412 ruleId: null,
413 loc: comment.loc,
414 message: err.message
415 }));
416 continue;
417 }
418
419 if (enabledGlobals[id]) {
420 enabledGlobals[id].comments.push(comment);
421 enabledGlobals[id].value = normalizedValue;
422 } else {
423 enabledGlobals[id] = {
424 comments: [comment],
425 value: normalizedValue
426 };
427 }
428 }
429 break;
430
431 case "eslint": {
432 const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
433
434 if (parseResult.success) {
435 Object.keys(parseResult.config).forEach(name => {
436 const rule = ruleMapper(name);
437 const ruleValue = parseResult.config[name];
438
34eeec05 439 if (!rule) {
eb39fafa
DC
440 problems.push(createLintingProblem({ ruleId: name, loc: comment.loc }));
441 return;
442 }
443
444 try {
445 validator.validateRuleOptions(rule, name, ruleValue);
446 } catch (err) {
447 problems.push(createLintingProblem({
448 ruleId: name,
449 message: err.message,
450 loc: comment.loc
451 }));
452
453 // do not apply the config, if found invalid options.
454 return;
455 }
456
457 configuredRules[name] = ruleValue;
458 });
459 } else {
460 problems.push(parseResult.error);
461 }
462
463 break;
464 }
465
466 // no default
467 }
468 });
469
470 return {
471 configuredRules,
472 enabledGlobals,
473 exportedVariables,
474 problems,
475 disableDirectives
476 };
477}
478
479/**
480 * Normalize ECMAScript version from the initial config
609c276f
TL
481 * @param {Parser} parser The parser which uses this options.
482 * @param {number} ecmaVersion ECMAScript version from the initial config
eb39fafa
DC
483 * @returns {number} normalized ECMAScript version
484 */
609c276f 485function normalizeEcmaVersion(parser, ecmaVersion) {
34eeec05
TL
486
487 if (isEspree(parser)) {
609c276f
TL
488 if (ecmaVersion === "latest") {
489 return espree.latestEcmaVersion;
490 }
491 }
eb39fafa
DC
492
493 /*
494 * Calculate ECMAScript edition number from official year version starting with
495 * ES2015, which corresponds with ES6 (or a difference of 2009).
496 */
497 return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
498}
499
34eeec05
TL
500/**
501 * Normalize ECMAScript version from the initial config into languageOptions (year)
502 * format.
503 * @param {any} [ecmaVersion] ECMAScript version from the initial config
504 * @returns {number} normalized ECMAScript version
505 */
506function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
507
508 switch (ecmaVersion) {
509 case 3:
510 return 3;
511
512 // void 0 = no ecmaVersion specified so use the default
513 case 5:
514 case void 0:
515 return 5;
516
517 default:
518 if (typeof ecmaVersion === "number") {
519 return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
520 }
521 }
522
523 /*
524 * We default to the latest supported ecmaVersion for everything else.
525 * Remember, this is for languageOptions.ecmaVersion, which sets the version
526 * that is used for a number of processes inside of ESLint. It's normally
527 * safe to assume people want the latest unless otherwise specified.
528 */
529 return espree.latestEcmaVersion + 2009;
530}
531
609c276f 532const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
eb39fafa
DC
533
534/**
535 * Checks whether or not there is a comment which has "eslint-env *" in a given text.
536 * @param {string} text A source code text to check.
537 * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
538 */
539function findEslintEnv(text) {
540 let match, retv;
541
542 eslintEnvPattern.lastIndex = 0;
543
544 while ((match = eslintEnvPattern.exec(text)) !== null) {
609c276f
TL
545 if (match[0].endsWith("*/")) {
546 retv = Object.assign(
547 retv || {},
548 commentParser.parseListConfig(stripDirectiveComment(match[1]))
549 );
550 }
eb39fafa
DC
551 }
552
553 return retv;
554}
555
556/**
557 * Convert "/path/to/<text>" to "<text>".
558 * `CLIEngine#executeOnText()` method gives "/path/to/<text>" if the filename
559 * was omitted because `configArray.extractConfig()` requires an absolute path.
560 * But the linter should pass `<text>` to `RuleContext#getFilename()` in that
561 * case.
562 * Also, code blocks can have their virtual filename. If the parent filename was
563 * `<text>`, the virtual filename is `<text>/0_foo.js` or something like (i.e.,
564 * it's not an absolute path).
565 * @param {string} filename The filename to normalize.
566 * @returns {string} The normalized filename.
567 */
568function normalizeFilename(filename) {
569 const parts = filename.split(path.sep);
570 const index = parts.lastIndexOf("<text>");
571
572 return index === -1 ? filename : parts.slice(index).join(path.sep);
573}
574
575/**
576 * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
577 * consistent shape.
578 * @param {VerifyOptions} providedOptions Options
579 * @param {ConfigData} config Config.
580 * @returns {Required<VerifyOptions> & InternalOptions} Normalized options
581 */
582function normalizeVerifyOptions(providedOptions, config) {
34eeec05
TL
583
584 const linterOptions = config.linterOptions || config;
585
586 // .noInlineConfig for eslintrc, .linterOptions.noInlineConfig for flat
587 const disableInlineConfig = linterOptions.noInlineConfig === true;
eb39fafa
DC
588 const ignoreInlineConfig = providedOptions.allowInlineConfig === false;
589 const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig
590 ? ` (${config.configNameOfNoInlineConfig})`
591 : "";
592
593 let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives;
594
595 if (typeof reportUnusedDisableDirectives === "boolean") {
596 reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off";
597 }
598 if (typeof reportUnusedDisableDirectives !== "string") {
34eeec05
TL
599 reportUnusedDisableDirectives =
600 linterOptions.reportUnusedDisableDirectives
601 ? "warn" : "off";
eb39fafa
DC
602 }
603
604 return {
605 filename: normalizeFilename(providedOptions.filename || "<input>"),
606 allowInlineConfig: !ignoreInlineConfig,
607 warnInlineConfig: disableInlineConfig && !ignoreInlineConfig
608 ? `your config${configNameOfNoInlineConfig}`
609 : null,
610 reportUnusedDisableDirectives,
611 disableFixes: Boolean(providedOptions.disableFixes)
612 };
613}
614
615/**
616 * Combines the provided parserOptions with the options from environments
609c276f 617 * @param {Parser} parser The parser which uses this options.
eb39fafa
DC
618 * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
619 * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
620 * @returns {ParserOptions} Resulting parser options after merge
621 */
609c276f
TL
622function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
623
eb39fafa
DC
624 const parserOptionsFromEnv = enabledEnvironments
625 .filter(env => env.parserOptions)
5422a9cc
TL
626 .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
627 const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
eb39fafa
DC
628 const isModule = mergedParserOptions.sourceType === "module";
629
630 if (isModule) {
631
632 /*
633 * can't have global return inside of modules
634 * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add)
635 */
636 mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
637 }
638
609c276f 639 mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);
eb39fafa
DC
640
641 return mergedParserOptions;
642}
643
34eeec05
TL
644/**
645 * Converts parserOptions to languageOptions for backwards compatibility with eslintrc.
646 * @param {ConfigData} config Config object.
647 * @param {Object} config.globals Global variable definitions.
648 * @param {Parser} config.parser The parser to use.
649 * @param {ParserOptions} config.parserOptions The parserOptions to use.
650 * @returns {LanguageOptions} The languageOptions equivalent.
651 */
652function createLanguageOptions({ globals: configuredGlobals, parser, parserOptions }) {
653
654 const {
655 ecmaVersion,
656 sourceType
657 } = parserOptions;
658
659 return {
660 globals: configuredGlobals,
661 ecmaVersion: normalizeEcmaVersionForLanguageOptions(ecmaVersion),
662 sourceType,
663 parser,
664 parserOptions
665 };
666}
667
eb39fafa
DC
668/**
669 * Combines the provided globals object with the globals from environments
670 * @param {Record<string, GlobalConf>} providedGlobals The 'globals' key in a config
671 * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
672 * @returns {Record<string, GlobalConf>} The resolved globals object
673 */
674function resolveGlobals(providedGlobals, enabledEnvironments) {
675 return Object.assign(
676 {},
677 ...enabledEnvironments.filter(env => env.globals).map(env => env.globals),
678 providedGlobals
679 );
680}
681
682/**
683 * Strips Unicode BOM from a given text.
684 * @param {string} text A text to strip.
685 * @returns {string} The stripped text.
686 */
687function stripUnicodeBOM(text) {
688
689 /*
690 * Check Unicode BOM.
691 * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
692 * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
693 */
694 if (text.charCodeAt(0) === 0xFEFF) {
695 return text.slice(1);
696 }
697 return text;
698}
699
700/**
701 * Get the options for a rule (not including severity), if any
702 * @param {Array|number} ruleConfig rule configuration
703 * @returns {Array} of rule options, empty Array if none
704 */
705function getRuleOptions(ruleConfig) {
706 if (Array.isArray(ruleConfig)) {
707 return ruleConfig.slice(1);
708 }
709 return [];
710
711}
712
713/**
714 * Analyze scope of the given AST.
715 * @param {ASTNode} ast The `Program` node to analyze.
34eeec05 716 * @param {LanguageOptions} languageOptions The parser options.
eb39fafa
DC
717 * @param {Record<string, string[]>} visitorKeys The visitor keys.
718 * @returns {ScopeManager} The analysis result.
719 */
34eeec05
TL
720function analyzeScope(ast, languageOptions, visitorKeys) {
721 const parserOptions = languageOptions.parserOptions;
eb39fafa 722 const ecmaFeatures = parserOptions.ecmaFeatures || {};
34eeec05 723 const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
eb39fafa
DC
724
725 return eslintScope.analyze(ast, {
726 ignoreEval: true,
727 nodejsScope: ecmaFeatures.globalReturn,
728 impliedStrict: ecmaFeatures.impliedStrict,
609c276f 729 ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
34eeec05 730 sourceType: languageOptions.sourceType || "script",
eb39fafa
DC
731 childVisitorKeys: visitorKeys || evk.KEYS,
732 fallback: Traverser.getKeys
733 });
734}
735
736/**
737 * Parses text into an AST. Moved out here because the try-catch prevents
738 * optimization of functions, so it's best to keep the try-catch as isolated
739 * as possible
740 * @param {string} text The text to parse.
34eeec05 741 * @param {LanguageOptions} languageOptions Options to pass to the parser
eb39fafa
DC
742 * @param {string} filePath The path to the file being parsed.
743 * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}}
744 * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
745 * @private
746 */
34eeec05 747function parse(text, languageOptions, filePath) {
eb39fafa 748 const textToParse = stripUnicodeBOM(text).replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
34eeec05
TL
749 const { ecmaVersion, sourceType, parser } = languageOptions;
750 const parserOptions = Object.assign(
751 { ecmaVersion, sourceType },
752 languageOptions.parserOptions,
753 {
754 loc: true,
755 range: true,
756 raw: true,
757 tokens: true,
758 comment: true,
759 eslintVisitorKeys: true,
760 eslintScopeManager: true,
761 filePath
762 }
763 );
eb39fafa
DC
764
765 /*
766 * Check for parsing errors first. If there's a parsing error, nothing
767 * else can happen. However, a parsing error does not throw an error
768 * from this method - it's just considered a fatal error message, a
769 * problem that ESLint identified just like any other.
770 */
771 try {
772 const parseResult = (typeof parser.parseForESLint === "function")
773 ? parser.parseForESLint(textToParse, parserOptions)
774 : { ast: parser.parse(textToParse, parserOptions) };
775 const ast = parseResult.ast;
776 const parserServices = parseResult.services || {};
777 const visitorKeys = parseResult.visitorKeys || evk.KEYS;
34eeec05 778 const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys);
eb39fafa
DC
779
780 return {
781 success: true,
782
783 /*
784 * Save all values that `parseForESLint()` returned.
785 * If a `SourceCode` object is given as the first parameter instead of source code text,
786 * linter skips the parsing process and reuses the source code object.
787 * In that case, linter needs all the values that `parseForESLint()` returned.
788 */
789 sourceCode: new SourceCode({
790 text,
791 ast,
792 parserServices,
793 scopeManager,
794 visitorKeys
795 })
796 };
797 } catch (ex) {
798
799 // If the message includes a leading line number, strip it:
800 const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
801
802 debug("%s\n%s", message, ex.stack);
803
804 return {
805 success: false,
806 error: {
807 ruleId: null,
808 fatal: true,
809 severity: 2,
810 message,
811 line: ex.lineNumber,
812 column: ex.column
813 }
814 };
815 }
816}
817
818/**
819 * Gets the scope for the current node
820 * @param {ScopeManager} scopeManager The scope manager for this AST
821 * @param {ASTNode} currentNode The node to get the scope of
822 * @returns {eslint-scope.Scope} The scope information for this node
823 */
824function getScope(scopeManager, currentNode) {
825
826 // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
827 const inner = currentNode.type !== "Program";
828
829 for (let node = currentNode; node; node = node.parent) {
830 const scope = scopeManager.acquire(node, inner);
831
832 if (scope) {
833 if (scope.type === "function-expression-name") {
834 return scope.childScopes[0];
835 }
836 return scope;
837 }
838 }
839
840 return scopeManager.scopes[0];
841}
842
843/**
844 * Marks a variable as used in the current scope
845 * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function.
846 * @param {ASTNode} currentNode The node currently being traversed
34eeec05 847 * @param {LanguageOptions} languageOptions The options used to parse this text
eb39fafa
DC
848 * @param {string} name The name of the variable that should be marked as used.
849 * @returns {boolean} True if the variable was found and marked as used, false if not.
850 */
34eeec05
TL
851function markVariableAsUsed(scopeManager, currentNode, languageOptions, name) {
852 const parserOptions = languageOptions.parserOptions;
853 const sourceType = languageOptions.sourceType;
854 const hasGlobalReturn =
855 (parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn) ||
856 sourceType === "commonjs";
857 const specialScope = hasGlobalReturn || sourceType === "module";
eb39fafa
DC
858 const currentScope = getScope(scopeManager, currentNode);
859
860 // Special Node.js scope means we need to start one level deeper
861 const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope;
862
863 for (let scope = initialScope; scope; scope = scope.upper) {
864 const variable = scope.variables.find(scopeVar => scopeVar.name === name);
865
866 if (variable) {
867 variable.eslintUsed = true;
868 return true;
869 }
870 }
871
872 return false;
873}
874
875/**
876 * Runs a rule, and gets its listeners
877 * @param {Rule} rule A normalized rule with a `create` method
878 * @param {Context} ruleContext The context that should be passed to the rule
609c276f 879 * @throws {any} Any error during the rule's `create`
eb39fafa
DC
880 * @returns {Object} A map of selector listeners provided by the rule
881 */
882function createRuleListeners(rule, ruleContext) {
883 try {
884 return rule.create(ruleContext);
885 } catch (ex) {
886 ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`;
887 throw ex;
888 }
889}
890
891/**
892 * Gets all the ancestors of a given node
893 * @param {ASTNode} node The node
894 * @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting
895 * from the root node and going inwards to the parent node.
896 */
897function getAncestors(node) {
898 const ancestorsStartingAtParent = [];
899
900 for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
901 ancestorsStartingAtParent.push(ancestor);
902 }
903
904 return ancestorsStartingAtParent.reverse();
905}
906
907// methods that exist on SourceCode object
908const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
909 getSource: "getText",
910 getSourceLines: "getLines",
911 getAllComments: "getAllComments",
912 getNodeByRangeIndex: "getNodeByRangeIndex",
913 getComments: "getComments",
914 getCommentsBefore: "getCommentsBefore",
915 getCommentsAfter: "getCommentsAfter",
916 getCommentsInside: "getCommentsInside",
917 getJSDocComment: "getJSDocComment",
918 getFirstToken: "getFirstToken",
919 getFirstTokens: "getFirstTokens",
920 getLastToken: "getLastToken",
921 getLastTokens: "getLastTokens",
922 getTokenAfter: "getTokenAfter",
923 getTokenBefore: "getTokenBefore",
924 getTokenByRangeStart: "getTokenByRangeStart",
925 getTokens: "getTokens",
926 getTokensAfter: "getTokensAfter",
927 getTokensBefore: "getTokensBefore",
928 getTokensBetween: "getTokensBetween"
929};
930
931const BASE_TRAVERSAL_CONTEXT = Object.freeze(
932 Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
933 (contextInfo, methodName) =>
934 Object.assign(contextInfo, {
935 [methodName](...args) {
936 return this.getSourceCode()[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args);
937 }
938 }),
939 {}
940 )
941);
942
943/**
944 * Runs the given rules on the given SourceCode object
945 * @param {SourceCode} sourceCode A SourceCode object for the given text
946 * @param {Object} configuredRules The rules configuration
947 * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
34eeec05
TL
948 * @param {string | undefined} parserName The name of the parser in the config
949 * @param {LanguageOptions} languageOptions The options for parsing the code.
eb39fafa
DC
950 * @param {Object} settings The settings that were enabled in the config
951 * @param {string} filename The reported filename of the code
952 * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
953 * @param {string | undefined} cwd cwd of the cli
5422a9cc 954 * @param {string} physicalFilename The full path of the file on disk without any code block information
eb39fafa
DC
955 * @returns {Problem[]} An array of reported problems
956 */
34eeec05 957function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) {
eb39fafa
DC
958 const emitter = createEmitter();
959 const nodeQueue = [];
960 let currentNode = sourceCode.ast;
961
962 Traverser.traverse(sourceCode.ast, {
963 enter(node, parent) {
964 node.parent = parent;
965 nodeQueue.push({ isEntering: true, node });
966 },
967 leave(node) {
968 nodeQueue.push({ isEntering: false, node });
969 },
970 visitorKeys: sourceCode.visitorKeys
971 });
972
973 /*
974 * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
975 * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
976 * properties once for each rule.
977 */
978 const sharedTraversalContext = Object.freeze(
979 Object.assign(
980 Object.create(BASE_TRAVERSAL_CONTEXT),
981 {
982 getAncestors: () => getAncestors(currentNode),
983 getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
984 getCwd: () => cwd,
985 getFilename: () => filename,
5422a9cc 986 getPhysicalFilename: () => physicalFilename || filename,
eb39fafa
DC
987 getScope: () => getScope(sourceCode.scopeManager, currentNode),
988 getSourceCode: () => sourceCode,
34eeec05
TL
989 markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, languageOptions, name),
990 parserOptions: {
991 ...languageOptions.parserOptions
992 },
eb39fafa 993 parserPath: parserName,
34eeec05 994 languageOptions,
eb39fafa
DC
995 parserServices: sourceCode.parserServices,
996 settings
997 }
998 )
999 );
1000
eb39fafa
DC
1001 const lintingProblems = [];
1002
1003 Object.keys(configuredRules).forEach(ruleId => {
1004 const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
1005
1006 // not load disabled rules
1007 if (severity === 0) {
1008 return;
1009 }
1010
1011 const rule = ruleMapper(ruleId);
1012
34eeec05 1013 if (!rule) {
eb39fafa
DC
1014 lintingProblems.push(createLintingProblem({ ruleId }));
1015 return;
1016 }
1017
1018 const messageIds = rule.meta && rule.meta.messages;
1019 let reportTranslator = null;
1020 const ruleContext = Object.freeze(
1021 Object.assign(
1022 Object.create(sharedTraversalContext),
1023 {
1024 id: ruleId,
1025 options: getRuleOptions(configuredRules[ruleId]),
1026 report(...args) {
1027
1028 /*
1029 * Create a report translator lazily.
1030 * In a vast majority of cases, any given rule reports zero errors on a given
1031 * piece of code. Creating a translator lazily avoids the performance cost of
1032 * creating a new translator function for each rule that usually doesn't get
1033 * called.
1034 *
1035 * Using lazy report translators improves end-to-end performance by about 3%
1036 * with Node 8.4.0.
1037 */
1038 if (reportTranslator === null) {
1039 reportTranslator = createReportTranslator({
1040 ruleId,
1041 severity,
1042 sourceCode,
1043 messageIds,
1044 disableFixes
1045 });
1046 }
1047 const problem = reportTranslator(...args);
1048
609c276f
TL
1049 if (problem.fix && !(rule.meta && rule.meta.fixable)) {
1050 throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
1051 }
1052 if (problem.suggestions && !(rule.meta && rule.meta.hasSuggestions === true)) {
1053 if (rule.meta && rule.meta.docs && typeof rule.meta.docs.suggestion !== "undefined") {
1054
1055 // Encourage migration from the former property name.
1056 throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.");
1057 }
1058 throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
eb39fafa
DC
1059 }
1060 lintingProblems.push(problem);
1061 }
1062 }
1063 )
1064 );
1065
1066 const ruleListeners = createRuleListeners(rule, ruleContext);
1067
609c276f
TL
1068 /**
1069 * Include `ruleId` in error logs
1070 * @param {Function} ruleListener A rule method that listens for a node.
1071 * @returns {Function} ruleListener wrapped in error handler
1072 */
1073 function addRuleErrorHandler(ruleListener) {
1074 return function ruleErrorHandler(...listenerArgs) {
1075 try {
1076 return ruleListener(...listenerArgs);
1077 } catch (e) {
1078 e.ruleId = ruleId;
1079 throw e;
1080 }
1081 };
1082 }
1083
eb39fafa
DC
1084 // add all the selectors from the rule as listeners
1085 Object.keys(ruleListeners).forEach(selector => {
609c276f
TL
1086 const ruleListener = timing.enabled
1087 ? timing.time(ruleId, ruleListeners[selector])
1088 : ruleListeners[selector];
1089
eb39fafa
DC
1090 emitter.on(
1091 selector,
609c276f 1092 addRuleErrorHandler(ruleListener)
eb39fafa
DC
1093 );
1094 });
1095 });
1096
ebb53d86 1097 // only run code path analyzer if the top level node is "Program", skip otherwise
5422a9cc
TL
1098 const eventGenerator = nodeQueue[0].node.type === "Program"
1099 ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
1100 : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
eb39fafa
DC
1101
1102 nodeQueue.forEach(traversalInfo => {
1103 currentNode = traversalInfo.node;
1104
1105 try {
1106 if (traversalInfo.isEntering) {
1107 eventGenerator.enterNode(currentNode);
1108 } else {
1109 eventGenerator.leaveNode(currentNode);
1110 }
1111 } catch (err) {
1112 err.currentNode = currentNode;
1113 throw err;
1114 }
1115 });
1116
1117 return lintingProblems;
1118}
1119
1120/**
1121 * Ensure the source code to be a string.
1122 * @param {string|SourceCode} textOrSourceCode The text or source code object.
1123 * @returns {string} The source code text.
1124 */
1125function ensureText(textOrSourceCode) {
1126 if (typeof textOrSourceCode === "object") {
1127 const { hasBOM, text } = textOrSourceCode;
1128 const bom = hasBOM ? "\uFEFF" : "";
1129
1130 return bom + text;
1131 }
1132
1133 return String(textOrSourceCode);
1134}
1135
1136/**
1137 * Get an environment.
1138 * @param {LinterInternalSlots} slots The internal slots of Linter.
1139 * @param {string} envId The environment ID to get.
1140 * @returns {Environment|null} The environment.
1141 */
1142function getEnv(slots, envId) {
1143 return (
1144 (slots.lastConfigArray && slots.lastConfigArray.pluginEnvironments.get(envId)) ||
1145 BuiltInEnvironments.get(envId) ||
1146 null
1147 );
1148}
1149
1150/**
1151 * Get a rule.
1152 * @param {LinterInternalSlots} slots The internal slots of Linter.
1153 * @param {string} ruleId The rule ID to get.
1154 * @returns {Rule|null} The rule.
1155 */
1156function getRule(slots, ruleId) {
1157 return (
1158 (slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) ||
1159 slots.ruleMap.get(ruleId)
1160 );
1161}
1162
1163/**
1164 * Normalize the value of the cwd
1165 * @param {string | undefined} cwd raw value of the cwd, path to a directory that should be considered as the current working directory, can be undefined.
1166 * @returns {string | undefined} normalized cwd
1167 */
1168function normalizeCwd(cwd) {
1169 if (cwd) {
1170 return cwd;
1171 }
1172 if (typeof process === "object") {
1173 return process.cwd();
1174 }
1175
1176 // It's more explicit to assign the undefined
609c276f 1177 // eslint-disable-next-line no-undefined -- Consistently returning a value
eb39fafa
DC
1178 return undefined;
1179}
1180
1181/**
1182 * The map to store private data.
1183 * @type {WeakMap<Linter, LinterInternalSlots>}
1184 */
1185const internalSlotsMap = new WeakMap();
1186
34eeec05
TL
1187/**
1188 * Throws an error when the given linter is in flat config mode.
1189 * @param {Linter} linter The linter to check.
1190 * @returns {void}
1191 * @throws {Error} If the linter is in flat config mode.
1192 */
1193function assertEslintrcConfig(linter) {
1194 const { configType } = internalSlotsMap.get(linter);
1195
1196 if (configType === "flat") {
1197 throw new Error("This method cannot be used with flat config. Add your entries directly into the config array.");
1198 }
1199}
1200
1201
eb39fafa
DC
1202//------------------------------------------------------------------------------
1203// Public Interface
1204//------------------------------------------------------------------------------
1205
1206/**
1207 * Object that is responsible for verifying JavaScript text
34eeec05 1208 * @name Linter
eb39fafa
DC
1209 */
1210class Linter {
1211
1212 /**
1213 * Initialize the Linter.
1214 * @param {Object} [config] the config object
609c276f 1215 * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
34eeec05 1216 * @param {"flat"|"eslintrc"} [config.configType="eslintrc"] the type of config used.
eb39fafa 1217 */
34eeec05 1218 constructor({ cwd, configType } = {}) {
eb39fafa
DC
1219 internalSlotsMap.set(this, {
1220 cwd: normalizeCwd(cwd),
1221 lastConfigArray: null,
1222 lastSourceCode: null,
34eeec05 1223 configType, // TODO: Remove after flat config conversion
eb39fafa
DC
1224 parserMap: new Map([["espree", espree]]),
1225 ruleMap: new Rules()
1226 });
1227
1228 this.version = pkg.version;
1229 }
1230
1231 /**
1232 * Getter for package version.
1233 * @static
1234 * @returns {string} The version from package.json.
1235 */
1236 static get version() {
1237 return pkg.version;
1238 }
1239
1240 /**
1241 * Same as linter.verify, except without support for processors.
1242 * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
1243 * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
1244 * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
609c276f 1245 * @throws {Error} If during rule execution.
eb39fafa
DC
1246 * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
1247 */
1248 _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
1249 const slots = internalSlotsMap.get(this);
1250 const config = providedConfig || {};
1251 const options = normalizeVerifyOptions(providedOptions, config);
1252 let text;
1253
1254 // evaluate arguments
1255 if (typeof textOrSourceCode === "string") {
1256 slots.lastSourceCode = null;
1257 text = textOrSourceCode;
1258 } else {
1259 slots.lastSourceCode = textOrSourceCode;
1260 text = textOrSourceCode.text;
1261 }
1262
1263 // Resolve parser.
1264 let parserName = DEFAULT_PARSER_NAME;
1265 let parser = espree;
1266
1267 if (typeof config.parser === "object" && config.parser !== null) {
1268 parserName = config.parser.filePath;
1269 parser = config.parser.definition;
1270 } else if (typeof config.parser === "string") {
1271 if (!slots.parserMap.has(config.parser)) {
1272 return [{
1273 ruleId: null,
1274 fatal: true,
1275 severity: 2,
1276 message: `Configured parser '${config.parser}' was not found.`,
1277 line: 0,
1278 column: 0
1279 }];
1280 }
1281 parserName = config.parser;
1282 parser = slots.parserMap.get(config.parser);
1283 }
1284
1285 // search and apply "eslint-env *".
1286 const envInFile = options.allowInlineConfig && !options.warnInlineConfig
1287 ? findEslintEnv(text)
1288 : {};
1289 const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
1290 const enabledEnvs = Object.keys(resolvedEnvConfig)
1291 .filter(envName => resolvedEnvConfig[envName])
1292 .map(envName => getEnv(slots, envName))
1293 .filter(env => env);
1294
609c276f 1295 const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
eb39fafa
DC
1296 const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
1297 const settings = config.settings || {};
34eeec05
TL
1298 const languageOptions = createLanguageOptions({
1299 globals: config.globals,
1300 parser,
1301 parserOptions
1302 });
eb39fafa
DC
1303
1304 if (!slots.lastSourceCode) {
1305 const parseResult = parse(
1306 text,
34eeec05 1307 languageOptions,
eb39fafa
DC
1308 options.filename
1309 );
1310
1311 if (!parseResult.success) {
1312 return [parseResult.error];
1313 }
1314
1315 slots.lastSourceCode = parseResult.sourceCode;
1316 } else {
1317
1318 /*
1319 * If the given source code object as the first argument does not have scopeManager, analyze the scope.
1320 * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
1321 */
1322 if (!slots.lastSourceCode.scopeManager) {
1323 slots.lastSourceCode = new SourceCode({
1324 text: slots.lastSourceCode.text,
1325 ast: slots.lastSourceCode.ast,
1326 parserServices: slots.lastSourceCode.parserServices,
1327 visitorKeys: slots.lastSourceCode.visitorKeys,
34eeec05 1328 scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
eb39fafa
DC
1329 });
1330 }
1331 }
1332
1333 const sourceCode = slots.lastSourceCode;
1334 const commentDirectives = options.allowInlineConfig
1335 ? getDirectiveComments(options.filename, sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
1336 : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1337
1338 // augment global scope with declared global variables
1339 addDeclaredGlobals(
1340 sourceCode.scopeManager.scopes[0],
1341 configuredGlobals,
1342 { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
1343 );
1344
1345 const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
1346
1347 let lintingProblems;
1348
1349 try {
1350 lintingProblems = runRules(
1351 sourceCode,
1352 configuredRules,
1353 ruleId => getRule(slots, ruleId),
eb39fafa 1354 parserName,
34eeec05 1355 languageOptions,
eb39fafa
DC
1356 settings,
1357 options.filename,
1358 options.disableFixes,
5422a9cc
TL
1359 slots.cwd,
1360 providedOptions.physicalFilename
eb39fafa
DC
1361 );
1362 } catch (err) {
1363 err.message += `\nOccurred while linting ${options.filename}`;
1364 debug("An error occurred while traversing");
1365 debug("Filename:", options.filename);
1366 if (err.currentNode) {
1367 const { line } = err.currentNode.loc.start;
1368
1369 debug("Line:", line);
1370 err.message += `:${line}`;
1371 }
1372 debug("Parser Options:", parserOptions);
1373 debug("Parser Path:", parserName);
1374 debug("Settings:", settings);
609c276f
TL
1375
1376 if (err.ruleId) {
1377 err.message += `\nRule: "${err.ruleId}"`;
1378 }
1379
eb39fafa
DC
1380 throw err;
1381 }
1382
1383 return applyDisableDirectives({
1384 directives: commentDirectives.disableDirectives,
609c276f 1385 disableFixes: options.disableFixes,
eb39fafa
DC
1386 problems: lintingProblems
1387 .concat(commentDirectives.problems)
1388 .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
1389 reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
1390 });
1391 }
1392
1393 /**
1394 * Verifies the text against the rules specified by the second argument.
1395 * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
1396 * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything.
1397 * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked.
1398 * If this is not set, the filename will default to '<input>' in the rule context. If
1399 * an object, then it has "filename", "allowInlineConfig", and some properties.
1400 * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
1401 */
1402 verify(textOrSourceCode, config, filenameOrOptions) {
1403 debug("Verify");
34eeec05
TL
1404
1405 const { configType } = internalSlotsMap.get(this);
1406
eb39fafa
DC
1407 const options = typeof filenameOrOptions === "string"
1408 ? { filename: filenameOrOptions }
1409 : filenameOrOptions || {};
1410
34eeec05
TL
1411 if (config) {
1412 if (configType === "flat") {
1413
1414 /*
1415 * Because of how Webpack packages up the files, we can't
1416 * compare directly to `FlatConfigArray` using `instanceof`
1417 * because it's not the same `FlatConfigArray` as in the tests.
1418 * So, we work around it by assuming an array is, in fact, a
1419 * `FlatConfigArray` if it has a `getConfig()` method.
1420 */
1421 let configArray = config;
1422
1423 if (!Array.isArray(config) || typeof config.getConfig !== "function") {
1424 configArray = new FlatConfigArray(config);
1425 configArray.normalizeSync();
1426 }
1427
1428 return this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true);
1429 }
1430
1431 if (typeof config.extractConfig === "function") {
1432 return this._verifyWithConfigArray(textOrSourceCode, config, options);
1433 }
eb39fafa
DC
1434 }
1435
34eeec05
TL
1436 /*
1437 * If we get to here, it means `config` is just an object rather
1438 * than a config array so we can go right into linting.
1439 */
1440
eb39fafa
DC
1441 /*
1442 * `Linter` doesn't support `overrides` property in configuration.
1443 * So we cannot apply multiple processors.
1444 */
1445 if (options.preprocess || options.postprocess) {
1446 return this._verifyWithProcessor(textOrSourceCode, config, options);
1447 }
1448 return this._verifyWithoutProcessors(textOrSourceCode, config, options);
1449 }
1450
34eeec05
TL
1451 /**
1452 * Verify with a processor.
1453 * @param {string|SourceCode} textOrSourceCode The source code.
1454 * @param {FlatConfig} config The config array.
1455 * @param {VerifyOptions&ProcessorOptions} options The options.
1456 * @param {FlatConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
1457 * @returns {LintMessage[]} The found problems.
1458 */
1459 _verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) {
1460 const filename = options.filename || "<input>";
1461 const filenameToExpose = normalizeFilename(filename);
1462 const physicalFilename = options.physicalFilename || filenameToExpose;
1463 const text = ensureText(textOrSourceCode);
1464 const preprocess = options.preprocess || (rawText => [rawText]);
1465 const postprocess = options.postprocess || (messagesList => messagesList.flat());
1466 const filterCodeBlock =
1467 options.filterCodeBlock ||
1468 (blockFilename => blockFilename.endsWith(".js"));
1469 const originalExtname = path.extname(filename);
1470 const messageLists = preprocess(text, filenameToExpose).map((block, i) => {
1471 debug("A code block was found: %o", block.filename || "(unnamed)");
1472
1473 // Keep the legacy behavior.
1474 if (typeof block === "string") {
1475 return this._verifyWithFlatConfigArrayAndWithoutProcessors(block, config, options);
1476 }
1477
1478 const blockText = block.text;
1479 const blockName = path.join(filename, `${i}_${block.filename}`);
1480
1481 // Skip this block if filtered.
1482 if (!filterCodeBlock(blockName, blockText)) {
1483 debug("This code block was skipped.");
1484 return [];
1485 }
1486
1487 // Resolve configuration again if the file content or extension was changed.
1488 if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
1489 debug("Resolving configuration again because the file content or extension was changed.");
1490 return this._verifyWithFlatConfigArray(
1491 blockText,
1492 configForRecursive,
1493 { ...options, filename: blockName, physicalFilename }
1494 );
1495 }
1496
1497 // Does lint.
1498 return this._verifyWithFlatConfigArrayAndWithoutProcessors(
1499 blockText,
1500 config,
1501 { ...options, filename: blockName, physicalFilename }
1502 );
1503 });
1504
1505 return postprocess(messageLists, filenameToExpose);
1506 }
1507
1508 /**
1509 * Same as linter.verify, except without support for processors.
1510 * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
1511 * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything.
1512 * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
1513 * @throws {Error} If during rule execution.
1514 * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
1515 */
1516 _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
1517 const slots = internalSlotsMap.get(this);
1518 const config = providedConfig || {};
1519 const options = normalizeVerifyOptions(providedOptions, config);
1520 let text;
1521
1522 // evaluate arguments
1523 if (typeof textOrSourceCode === "string") {
1524 slots.lastSourceCode = null;
1525 text = textOrSourceCode;
1526 } else {
1527 slots.lastSourceCode = textOrSourceCode;
1528 text = textOrSourceCode.text;
1529 }
1530
1531 const languageOptions = config.languageOptions;
1532
1533 languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
1534 languageOptions.ecmaVersion
1535 );
1536
1537 // add configured globals and language globals
1538 const configuredGlobals = {
1539 ...(getGlobalsForEcmaVersion(languageOptions.ecmaVersion)),
1540 ...(languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0),
1541 ...languageOptions.globals
1542 };
1543
1544 // Espree expects this information to be passed in
1545 if (isEspree(languageOptions.parser)) {
1546 const parserOptions = languageOptions.parserOptions;
1547
1548 if (languageOptions.sourceType) {
1549
1550 parserOptions.sourceType = languageOptions.sourceType;
1551
1552 if (
1553 parserOptions.sourceType === "module" &&
1554 parserOptions.ecmaFeatures &&
1555 parserOptions.ecmaFeatures.globalReturn
1556 ) {
1557 parserOptions.ecmaFeatures.globalReturn = false;
1558 }
1559 }
1560 }
1561
1562 const settings = config.settings || {};
1563
1564 if (!slots.lastSourceCode) {
1565 const parseResult = parse(
1566 text,
1567 languageOptions,
1568 options.filename
1569 );
1570
1571 if (!parseResult.success) {
1572 return [parseResult.error];
1573 }
1574
1575 slots.lastSourceCode = parseResult.sourceCode;
1576 } else {
1577
1578 /*
1579 * If the given source code object as the first argument does not have scopeManager, analyze the scope.
1580 * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
1581 */
1582 if (!slots.lastSourceCode.scopeManager) {
1583 slots.lastSourceCode = new SourceCode({
1584 text: slots.lastSourceCode.text,
1585 ast: slots.lastSourceCode.ast,
1586 parserServices: slots.lastSourceCode.parserServices,
1587 visitorKeys: slots.lastSourceCode.visitorKeys,
1588 scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
1589 });
1590 }
1591 }
1592
1593 const sourceCode = slots.lastSourceCode;
1594 const commentDirectives = options.allowInlineConfig
1595 ? getDirectiveComments(
1596 options.filename,
1597 sourceCode.ast,
1598 ruleId => getRuleFromConfig(ruleId, config),
1599 options.warnInlineConfig
1600 )
1601 : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1602
1603 // augment global scope with declared global variables
1604 addDeclaredGlobals(
1605 sourceCode.scopeManager.scopes[0],
1606 configuredGlobals,
1607 { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
1608 );
1609
1610 const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
1611
1612 let lintingProblems;
1613
1614 try {
1615 lintingProblems = runRules(
1616 sourceCode,
1617 configuredRules,
1618 ruleId => getRuleFromConfig(ruleId, config),
1619 void 0,
1620 languageOptions,
1621 settings,
1622 options.filename,
1623 options.disableFixes,
1624 slots.cwd,
1625 providedOptions.physicalFilename
1626 );
1627 } catch (err) {
1628 err.message += `\nOccurred while linting ${options.filename}`;
1629 debug("An error occurred while traversing");
1630 debug("Filename:", options.filename);
1631 if (err.currentNode) {
1632 const { line } = err.currentNode.loc.start;
1633
1634 debug("Line:", line);
1635 err.message += `:${line}`;
1636 }
1637 debug("Parser Options:", languageOptions.parserOptions);
1638
1639 // debug("Parser Path:", parserName);
1640 debug("Settings:", settings);
1641
1642 if (err.ruleId) {
1643 err.message += `\nRule: "${err.ruleId}"`;
1644 }
1645
1646 throw err;
1647 }
1648
1649 return applyDisableDirectives({
1650 directives: commentDirectives.disableDirectives,
1651 disableFixes: options.disableFixes,
1652 problems: lintingProblems
1653 .concat(commentDirectives.problems)
1654 .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
1655 reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
1656 });
1657 }
1658
eb39fafa
DC
1659 /**
1660 * Verify a given code with `ConfigArray`.
1661 * @param {string|SourceCode} textOrSourceCode The source code.
1662 * @param {ConfigArray} configArray The config array.
1663 * @param {VerifyOptions&ProcessorOptions} options The options.
1664 * @returns {LintMessage[]} The found problems.
1665 */
1666 _verifyWithConfigArray(textOrSourceCode, configArray, options) {
1667 debug("With ConfigArray: %s", options.filename);
1668
1669 // Store the config array in order to get plugin envs and rules later.
1670 internalSlotsMap.get(this).lastConfigArray = configArray;
1671
1672 // Extract the final config for this file.
1673 const config = configArray.extractConfig(options.filename);
1674 const processor =
1675 config.processor &&
1676 configArray.pluginProcessors.get(config.processor);
1677
1678 // Verify.
1679 if (processor) {
1680 debug("Apply the processor: %o", config.processor);
1681 const { preprocess, postprocess, supportsAutofix } = processor;
1682 const disableFixes = options.disableFixes || !supportsAutofix;
1683
1684 return this._verifyWithProcessor(
1685 textOrSourceCode,
1686 config,
1687 { ...options, disableFixes, postprocess, preprocess },
1688 configArray
1689 );
1690 }
1691 return this._verifyWithoutProcessors(textOrSourceCode, config, options);
1692 }
1693
34eeec05
TL
1694 /**
1695 * Verify a given code with a flat config.
1696 * @param {string|SourceCode} textOrSourceCode The source code.
1697 * @param {FlatConfigArray} configArray The config array.
1698 * @param {VerifyOptions&ProcessorOptions} options The options.
1699 * @param {boolean} [firstCall=false] Indicates if this is being called directly
1700 * from verify(). (TODO: Remove once eslintrc is removed.)
1701 * @returns {LintMessage[]} The found problems.
1702 */
1703 _verifyWithFlatConfigArray(textOrSourceCode, configArray, options, firstCall = false) {
1704 debug("With flat config: %s", options.filename);
1705
1706 // we need a filename to match configs against
1707 const filename = options.filename || "<input>";
1708
1709 // Store the config array in order to get plugin envs and rules later.
1710 internalSlotsMap.get(this).lastConfigArray = configArray;
1711 const config = configArray.getConfig(filename);
1712
1713 // Verify.
1714 if (config.processor) {
1715 debug("Apply the processor: %o", config.processor);
1716 const { preprocess, postprocess, supportsAutofix } = config.processor;
1717 const disableFixes = options.disableFixes || !supportsAutofix;
1718
1719 return this._verifyWithFlatConfigArrayAndProcessor(
1720 textOrSourceCode,
1721 config,
1722 { ...options, filename, disableFixes, postprocess, preprocess },
1723 configArray
1724 );
1725 }
1726
1727 // check for options-based processing
1728 if (firstCall && (options.preprocess || options.postprocess)) {
1729 return this._verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options);
1730 }
1731
1732 return this._verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, config, options);
1733 }
1734
eb39fafa
DC
1735 /**
1736 * Verify with a processor.
1737 * @param {string|SourceCode} textOrSourceCode The source code.
1738 * @param {ConfigData|ExtractedConfig} config The config array.
1739 * @param {VerifyOptions&ProcessorOptions} options The options.
1740 * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively.
1741 * @returns {LintMessage[]} The found problems.
1742 */
1743 _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
1744 const filename = options.filename || "<input>";
1745 const filenameToExpose = normalizeFilename(filename);
5422a9cc 1746 const physicalFilename = options.physicalFilename || filenameToExpose;
eb39fafa
DC
1747 const text = ensureText(textOrSourceCode);
1748 const preprocess = options.preprocess || (rawText => [rawText]);
5422a9cc 1749
609c276f 1750 const postprocess = options.postprocess || (messagesList => messagesList.flat());
eb39fafa
DC
1751 const filterCodeBlock =
1752 options.filterCodeBlock ||
1753 (blockFilename => blockFilename.endsWith(".js"));
1754 const originalExtname = path.extname(filename);
1755 const messageLists = preprocess(text, filenameToExpose).map((block, i) => {
1756 debug("A code block was found: %o", block.filename || "(unnamed)");
1757
1758 // Keep the legacy behavior.
1759 if (typeof block === "string") {
1760 return this._verifyWithoutProcessors(block, config, options);
1761 }
1762
1763 const blockText = block.text;
1764 const blockName = path.join(filename, `${i}_${block.filename}`);
1765
1766 // Skip this block if filtered.
1767 if (!filterCodeBlock(blockName, blockText)) {
1768 debug("This code block was skipped.");
1769 return [];
1770 }
1771
5422a9cc
TL
1772 // Resolve configuration again if the file content or extension was changed.
1773 if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
1774 debug("Resolving configuration again because the file content or extension was changed.");
eb39fafa
DC
1775 return this._verifyWithConfigArray(
1776 blockText,
1777 configForRecursive,
5422a9cc 1778 { ...options, filename: blockName, physicalFilename }
eb39fafa
DC
1779 );
1780 }
1781
1782 // Does lint.
1783 return this._verifyWithoutProcessors(
1784 blockText,
1785 config,
5422a9cc 1786 { ...options, filename: blockName, physicalFilename }
eb39fafa
DC
1787 );
1788 });
1789
1790 return postprocess(messageLists, filenameToExpose);
1791 }
1792
1793 /**
1794 * Gets the SourceCode object representing the parsed source.
1795 * @returns {SourceCode} The SourceCode object.
1796 */
1797 getSourceCode() {
1798 return internalSlotsMap.get(this).lastSourceCode;
1799 }
1800
1801 /**
1802 * Defines a new linting rule.
1803 * @param {string} ruleId A unique rule identifier
1804 * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers
1805 * @returns {void}
1806 */
1807 defineRule(ruleId, ruleModule) {
34eeec05 1808 assertEslintrcConfig(this);
eb39fafa
DC
1809 internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule);
1810 }
1811
1812 /**
1813 * Defines many new linting rules.
1814 * @param {Record<string, Function | Rule>} rulesToDefine map from unique rule identifier to rule
1815 * @returns {void}
1816 */
1817 defineRules(rulesToDefine) {
34eeec05 1818 assertEslintrcConfig(this);
eb39fafa
DC
1819 Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
1820 this.defineRule(ruleId, rulesToDefine[ruleId]);
1821 });
1822 }
1823
1824 /**
1825 * Gets an object with all loaded rules.
1826 * @returns {Map<string, Rule>} All loaded rules
1827 */
1828 getRules() {
34eeec05 1829 assertEslintrcConfig(this);
eb39fafa
DC
1830 const { lastConfigArray, ruleMap } = internalSlotsMap.get(this);
1831
1832 return new Map(function *() {
1833 yield* ruleMap;
1834
1835 if (lastConfigArray) {
1836 yield* lastConfigArray.pluginRules;
1837 }
1838 }());
1839 }
1840
1841 /**
1842 * Define a new parser module
1843 * @param {string} parserId Name of the parser
1844 * @param {Parser} parserModule The parser object
1845 * @returns {void}
1846 */
1847 defineParser(parserId, parserModule) {
34eeec05 1848 assertEslintrcConfig(this);
eb39fafa
DC
1849 internalSlotsMap.get(this).parserMap.set(parserId, parserModule);
1850 }
1851
1852 /**
1853 * Performs multiple autofix passes over the text until as many fixes as possible
1854 * have been applied.
1855 * @param {string} text The source text to apply fixes to.
34eeec05 1856 * @param {ConfigData|ConfigArray|FlatConfigArray} config The ESLint config object to use.
eb39fafa
DC
1857 * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use.
1858 * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the
1859 * SourceCodeFixer.
1860 */
1861 verifyAndFix(text, config, options) {
1862 let messages = [],
1863 fixedResult,
1864 fixed = false,
1865 passNumber = 0,
1866 currentText = text;
1867 const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
1868 const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
1869
1870 /**
1871 * This loop continues until one of the following is true:
1872 *
1873 * 1. No more fixes have been applied.
1874 * 2. Ten passes have been made.
1875 *
1876 * That means anytime a fix is successfully applied, there will be another pass.
1877 * Essentially, guaranteeing a minimum of two passes.
1878 */
1879 do {
1880 passNumber++;
1881
1882 debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
1883 messages = this.verify(currentText, config, options);
1884
1885 debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
1886 fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
1887
1888 /*
1889 * stop if there are any syntax errors.
1890 * 'fixedResult.output' is a empty string.
1891 */
1892 if (messages.length === 1 && messages[0].fatal) {
1893 break;
1894 }
1895
1896 // keep track if any fixes were ever applied - important for return value
1897 fixed = fixed || fixedResult.fixed;
1898
1899 // update to use the fixed output instead of the original text
1900 currentText = fixedResult.output;
1901
1902 } while (
1903 fixedResult.fixed &&
1904 passNumber < MAX_AUTOFIX_PASSES
1905 );
1906
1907 /*
1908 * If the last result had fixes, we need to lint again to be sure we have
1909 * the most up-to-date information.
1910 */
1911 if (fixedResult.fixed) {
1912 fixedResult.messages = this.verify(currentText, config, options);
1913 }
1914
1915 // ensure the last result properly reflects if fixes were done
1916 fixedResult.fixed = fixed;
1917 fixedResult.output = currentText;
1918
1919 return fixedResult;
1920 }
1921}
1922
1923module.exports = {
1924 Linter,
1925
1926 /**
1927 * Get the internal slots of a given Linter instance for tests.
1928 * @param {Linter} instance The Linter instance to get.
1929 * @returns {LinterInternalSlots} The internal slots.
1930 */
1931 getLinterInternalSlots(instance) {
1932 return internalSlotsMap.get(instance);
1933 }
1934};