source: imaps-frontend/node_modules/eslint/lib/linter/linter.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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