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