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