eslint 9.4.0 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +3 -3
  2. package/conf/ecma-version.js +1 -1
  3. package/conf/globals.js +6 -1
  4. package/lib/api.js +1 -1
  5. package/lib/cli.js +23 -2
  6. package/lib/config/default-config.js +5 -0
  7. package/lib/config/flat-config-array.js +71 -8
  8. package/lib/config/flat-config-schema.js +46 -62
  9. package/lib/eslint/eslint-helpers.js +32 -17
  10. package/lib/eslint/eslint.js +30 -12
  11. package/lib/eslint/legacy-eslint.js +14 -0
  12. package/lib/languages/js/index.js +247 -0
  13. package/lib/{source-code → languages/js/source-code}/source-code.js +46 -22
  14. package/lib/languages/js/validate-language-options.js +181 -0
  15. package/lib/linter/apply-disable-directives.js +8 -3
  16. package/lib/linter/config-comment-parser.js +3 -16
  17. package/lib/linter/linter.js +291 -249
  18. package/lib/linter/report-translator.js +14 -7
  19. package/lib/linter/vfile.js +104 -0
  20. package/lib/options.js +13 -1
  21. package/lib/rule-tester/rule-tester.js +5 -2
  22. package/lib/rules/no-sparse-arrays.js +26 -3
  23. package/lib/rules/no-unused-vars.js +33 -31
  24. package/lib/shared/flags.js +26 -0
  25. package/lib/shared/logging.js +10 -1
  26. package/lib/shared/types.js +1 -1
  27. package/messages/all-matched-files-ignored.js +21 -0
  28. package/package.json +13 -18
  29. /package/lib/{source-code → languages/js/source-code}/index.js +0 -0
  30. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-comment-cursor.js +0 -0
  31. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-cursor.js +0 -0
  32. /package/lib/{source-code → languages/js/source-code}/token-store/cursor.js +0 -0
  33. /package/lib/{source-code → languages/js/source-code}/token-store/cursors.js +0 -0
  34. /package/lib/{source-code → languages/js/source-code}/token-store/decorative-cursor.js +0 -0
  35. /package/lib/{source-code → languages/js/source-code}/token-store/filter-cursor.js +0 -0
  36. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-comment-cursor.js +0 -0
  37. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-cursor.js +0 -0
  38. /package/lib/{source-code → languages/js/source-code}/token-store/index.js +0 -0
  39. /package/lib/{source-code → languages/js/source-code}/token-store/limit-cursor.js +0 -0
  40. /package/lib/{source-code → languages/js/source-code}/token-store/padded-token-cursor.js +0 -0
  41. /package/lib/{source-code → languages/js/source-code}/token-store/skip-cursor.js +0 -0
  42. /package/lib/{source-code → languages/js/source-code}/token-store/utils.js +0 -0
@@ -17,7 +17,6 @@ const
17
17
  espree = require("espree"),
18
18
  merge = require("lodash.merge"),
19
19
  pkg = require("../../package.json"),
20
- astUtils = require("../shared/ast-utils"),
21
20
  {
22
21
  directivesPattern
23
22
  } = require("../shared/directives"),
@@ -29,7 +28,7 @@ const
29
28
  }
30
29
  } = require("@eslint/eslintrc/universal"),
31
30
  Traverser = require("../shared/traverser"),
32
- { SourceCode } = require("../source-code"),
31
+ { SourceCode } = require("../languages/js/source-code"),
33
32
  applyDisableDirectives = require("./apply-disable-directives"),
34
33
  ConfigCommentParser = require("./config-comment-parser"),
35
34
  NodeEventGenerator = require("./node-event-generator"),
@@ -45,6 +44,8 @@ const { startTime, endTime } = require("../shared/stats");
45
44
  const { RuleValidator } = require("../config/rule-validator");
46
45
  const { assertIsRuleSeverity } = require("../config/flat-config-schema");
47
46
  const { normalizeSeverityToString } = require("../shared/severity");
47
+ const jslang = require("../languages/js");
48
+ const { activeFlags } = require("../shared/flags");
48
49
  const debug = require("debug")("eslint:linter");
49
50
  const MAX_AUTOFIX_PASSES = 10;
50
51
  const DEFAULT_PARSER_NAME = "espree";
@@ -53,6 +54,7 @@ const commentParser = new ConfigCommentParser();
53
54
  const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
54
55
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
55
56
  const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
57
+ const { VFile } = require("./vfile");
56
58
  const STEP_KIND_VISIT = 1;
57
59
  const STEP_KIND_CALL = 2;
58
60
 
@@ -239,6 +241,35 @@ function createMissingRuleMessage(ruleId) {
239
241
  : `Definition for rule '${ruleId}' was not found.`;
240
242
  }
241
243
 
244
+ /**
245
+ * Updates a given location based on the language offsets. This allows us to
246
+ * change 0-based locations to 1-based locations. We always want ESLint
247
+ * reporting lines and columns starting from 1.
248
+ * @param {Object} location The location to update.
249
+ * @param {number} location.line The starting line number.
250
+ * @param {number} location.column The starting column number.
251
+ * @param {number} [location.endLine] The ending line number.
252
+ * @param {number} [location.endColumn] The ending column number.
253
+ * @param {Language} language The language to use to adjust the location information.
254
+ * @returns {Object} The updated location.
255
+ */
256
+ function updateLocationInformation({ line, column, endLine, endColumn }, language) {
257
+
258
+ const columnOffset = language.columnStart === 1 ? 0 : 1;
259
+ const lineOffset = language.lineStart === 1 ? 0 : 1;
260
+
261
+ // calculate separately to account for undefined
262
+ const finalEndLine = endLine === void 0 ? endLine : endLine + lineOffset;
263
+ const finalEndColumn = endColumn === void 0 ? endColumn : endColumn + columnOffset;
264
+
265
+ return {
266
+ line: line + lineOffset,
267
+ column: column + columnOffset,
268
+ endLine: finalEndLine,
269
+ endColumn: finalEndColumn
270
+ };
271
+ }
272
+
242
273
  /**
243
274
  * creates a linting problem
244
275
  * @param {Object} options to create linting error
@@ -246,6 +277,7 @@ function createMissingRuleMessage(ruleId) {
246
277
  * @param {Object} [options.loc] the loc to report
247
278
  * @param {string} [options.message] the error message to report
248
279
  * @param {string} [options.severity] the error message to report
280
+ * @param {Language} [options.language] the language to use to adjust the location information
249
281
  * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
250
282
  * @private
251
283
  */
@@ -254,16 +286,24 @@ function createLintingProblem(options) {
254
286
  ruleId = null,
255
287
  loc = DEFAULT_ERROR_LOC,
256
288
  message = createMissingRuleMessage(options.ruleId),
257
- severity = 2
289
+ severity = 2,
290
+
291
+ // fallback for eslintrc mode
292
+ language = {
293
+ columnStart: 0,
294
+ lineStart: 1
295
+ }
258
296
  } = options;
259
297
 
260
298
  return {
261
299
  ruleId,
262
300
  message,
263
- line: loc.start.line,
264
- column: loc.start.column + 1,
265
- endLine: loc.end.line,
266
- endColumn: loc.end.column + 1,
301
+ ...updateLocationInformation({
302
+ line: loc.start.line,
303
+ column: loc.start.column,
304
+ endLine: loc.end.line,
305
+ endColumn: loc.end.column
306
+ }, language),
267
307
  severity,
268
308
  nodeType: null
269
309
  };
@@ -278,9 +318,10 @@ function createLintingProblem(options) {
278
318
  * @param {string} options.justification The justification of the directive
279
319
  * @param {ASTNode|token} options.node The Comment node/token.
280
320
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
321
+ * @param {Language} language The language to use to adjust the location information.
281
322
  * @returns {Object} Directives and problems from the comment
282
323
  */
283
- function createDisableDirectives({ type, value, justification, node }, ruleMapper) {
324
+ function createDisableDirectives({ type, value, justification, node }, ruleMapper, language) {
284
325
  const ruleIds = Object.keys(commentParser.parseListConfig(value));
285
326
  const directiveRules = ruleIds.length ? ruleIds : [null];
286
327
  const result = {
@@ -294,26 +335,36 @@ function createDisableDirectives({ type, value, justification, node }, ruleMappe
294
335
  // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
295
336
  if (ruleId === null || !!ruleMapper(ruleId)) {
296
337
  if (type === "disable-next-line") {
338
+ const { line, column } = updateLocationInformation(
339
+ node.loc.end,
340
+ language
341
+ );
342
+
297
343
  result.directives.push({
298
344
  parentDirective,
299
345
  type,
300
- line: node.loc.end.line,
301
- column: node.loc.end.column + 1,
346
+ line,
347
+ column,
302
348
  ruleId,
303
349
  justification
304
350
  });
305
351
  } else {
352
+ const { line, column } = updateLocationInformation(
353
+ node.loc.start,
354
+ language
355
+ );
356
+
306
357
  result.directives.push({
307
358
  parentDirective,
308
359
  type,
309
- line: node.loc.start.line,
310
- column: node.loc.start.column + 1,
360
+ line,
361
+ column,
311
362
  ruleId,
312
363
  justification
313
364
  });
314
365
  }
315
366
  } else {
316
- result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
367
+ result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc, language }));
317
368
  }
318
369
  }
319
370
  return result;
@@ -391,7 +442,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
391
442
  value: directiveValue,
392
443
  justification: justificationPart,
393
444
  node: comment
394
- }, ruleMapper);
445
+ }, ruleMapper, jslang);
395
446
 
396
447
  disableDirectives.push(...directives);
397
448
  problems.push(...directiveProblems);
@@ -431,7 +482,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
431
482
  break;
432
483
 
433
484
  case "eslint": {
434
- const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
485
+ const parseResult = commentParser.parseJsonConfig(directiveValue);
435
486
 
436
487
  if (parseResult.success) {
437
488
  Object.keys(parseResult.config).forEach(name => {
@@ -518,7 +569,14 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
518
569
  configuredRules[name] = ruleOptions;
519
570
  });
520
571
  } else {
521
- problems.push(parseResult.error);
572
+ const problem = createLintingProblem({
573
+ ruleId: null,
574
+ loc: comment.loc,
575
+ message: parseResult.error.message
576
+ });
577
+
578
+ problem.fatal = true;
579
+ problems.push(problem);
522
580
  }
523
581
 
524
582
  break;
@@ -541,26 +599,32 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
541
599
  * Parses comments in file to extract disable directives.
542
600
  * @param {SourceCode} sourceCode The SourceCode object to get comments from.
543
601
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
602
+ * @param {Language} language The language to use to adjust the location information
544
603
  * @returns {{problems: LintMessage[], disableDirectives: DisableDirective[]}}
545
604
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
546
605
  */
547
- function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
606
+ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) {
548
607
  const disableDirectives = [];
549
608
  const problems = [];
550
609
 
551
- const {
552
- directives: directivesSources,
553
- problems: directivesProblems
554
- } = sourceCode.getDisableDirectives();
610
+ if (sourceCode.getDisableDirectives) {
611
+ const {
612
+ directives: directivesSources,
613
+ problems: directivesProblems
614
+ } = sourceCode.getDisableDirectives();
555
615
 
556
- problems.push(...directivesProblems.map(createLintingProblem));
616
+ problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({
617
+ ...directiveProblem,
618
+ language
619
+ })));
557
620
 
558
- directivesSources.forEach(directive => {
559
- const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);
621
+ directivesSources.forEach(directive => {
622
+ const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper, language);
560
623
 
561
- disableDirectives.push(...directives);
562
- problems.push(...directiveProblems);
563
- });
624
+ disableDirectives.push(...directives);
625
+ problems.push(...directiveProblems);
626
+ });
627
+ }
564
628
 
565
629
  return {
566
630
  problems,
@@ -781,24 +845,6 @@ function resolveGlobals(providedGlobals, enabledEnvironments) {
781
845
  );
782
846
  }
783
847
 
784
- /**
785
- * Strips Unicode BOM from a given text.
786
- * @param {string} text A text to strip.
787
- * @returns {string} The stripped text.
788
- */
789
- function stripUnicodeBOM(text) {
790
-
791
- /*
792
- * Check Unicode BOM.
793
- * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
794
- * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
795
- */
796
- if (text.charCodeAt(0) === 0xFEFF) {
797
- return text.slice(1);
798
- }
799
- return text;
800
- }
801
-
802
848
  /**
803
849
  * Store time measurements in map
804
850
  * @param {number} time Time measurement
@@ -866,93 +912,40 @@ function analyzeScope(ast, languageOptions, visitorKeys) {
866
912
  }
867
913
 
868
914
  /**
869
- * Parses text into an AST. Moved out here because the try-catch prevents
915
+ * Parses file into an AST. Moved out here because the try-catch prevents
870
916
  * optimization of functions, so it's best to keep the try-catch as isolated
871
917
  * as possible
872
- * @param {string} text The text to parse.
918
+ * @param {VFile} file The file to parse.
919
+ * @param {Language} language The language to use.
873
920
  * @param {LanguageOptions} languageOptions Options to pass to the parser
874
- * @param {string} filePath The path to the file being parsed.
875
921
  * @returns {{success: false, error: LintMessage}|{success: true, sourceCode: SourceCode}}
876
922
  * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
877
923
  * @private
878
924
  */
879
- function parse(text, languageOptions, filePath) {
880
- const textToParse = stripUnicodeBOM(text).replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
881
- const { ecmaVersion, sourceType, parser } = languageOptions;
882
- const parserOptions = Object.assign(
883
- { ecmaVersion, sourceType },
884
- languageOptions.parserOptions,
885
- {
886
- loc: true,
887
- range: true,
888
- raw: true,
889
- tokens: true,
890
- comment: true,
891
- eslintVisitorKeys: true,
892
- eslintScopeManager: true,
893
- filePath
894
- }
895
- );
896
-
897
- /*
898
- * Check for parsing errors first. If there's a parsing error, nothing
899
- * else can happen. However, a parsing error does not throw an error
900
- * from this method - it's just considered a fatal error message, a
901
- * problem that ESLint identified just like any other.
902
- */
903
- try {
904
- debug("Parsing:", filePath);
905
- const parseResult = (typeof parser.parseForESLint === "function")
906
- ? parser.parseForESLint(textToParse, parserOptions)
907
- : { ast: parser.parse(textToParse, parserOptions) };
908
-
909
- debug("Parsing successful:", filePath);
910
- const ast = parseResult.ast;
911
- const parserServices = parseResult.services || {};
912
- const visitorKeys = parseResult.visitorKeys || evk.KEYS;
913
-
914
- debug("Scope analysis:", filePath);
915
- const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys);
925
+ function parse(file, language, languageOptions) {
916
926
 
917
- debug("Scope analysis successful:", filePath);
927
+ const result = language.parse(file, { languageOptions });
918
928
 
929
+ if (result.ok) {
919
930
  return {
920
931
  success: true,
921
-
922
- /*
923
- * Save all values that `parseForESLint()` returned.
924
- * If a `SourceCode` object is given as the first parameter instead of source code text,
925
- * linter skips the parsing process and reuses the source code object.
926
- * In that case, linter needs all the values that `parseForESLint()` returned.
927
- */
928
- sourceCode: new SourceCode({
929
- text,
930
- ast,
931
- parserServices,
932
- scopeManager,
933
- visitorKeys
934
- })
935
- };
936
- } catch (ex) {
937
-
938
- // If the message includes a leading line number, strip it:
939
- const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
940
-
941
- debug("%s\n%s", message, ex.stack);
942
-
943
- return {
944
- success: false,
945
- error: {
946
- ruleId: null,
947
- fatal: true,
948
- severity: 2,
949
- message,
950
- line: ex.lineNumber,
951
- column: ex.column,
952
- nodeType: null
953
- }
932
+ sourceCode: language.createSourceCode(file, result, { languageOptions })
954
933
  };
955
934
  }
935
+
936
+ // if we made it to here there was an error
937
+ return {
938
+ success: false,
939
+ errors: result.errors.map(error => ({
940
+ ruleId: null,
941
+ nodeType: null,
942
+ fatal: true,
943
+ severity: 2,
944
+ message: error.message,
945
+ line: error.line,
946
+ column: error.column
947
+ }))
948
+ };
956
949
  }
957
950
 
958
951
  /**
@@ -983,6 +976,7 @@ function createRuleListeners(rule, ruleContext) {
983
976
  * @param {Object} configuredRules The rules configuration
984
977
  * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
985
978
  * @param {string | undefined} parserName The name of the parser in the config
979
+ * @param {Language} language The language object used for parsing.
986
980
  * @param {LanguageOptions} languageOptions The options for parsing the code.
987
981
  * @param {Object} settings The settings that were enabled in the config
988
982
  * @param {string} filename The reported filename of the code
@@ -995,8 +989,11 @@ function createRuleListeners(rule, ruleContext) {
995
989
  * @returns {LintMessage[]} An array of reported problems
996
990
  * @throws {Error} If traversal into a node fails.
997
991
  */
998
- function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
999
- stats, slots) {
992
+ function runRules(
993
+ sourceCode, configuredRules, ruleMapper, parserName, language, languageOptions,
994
+ settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
995
+ stats, slots
996
+ ) {
1000
997
  const emitter = createEmitter();
1001
998
 
1002
999
  // must happen first to assign all node.parent properties
@@ -1043,7 +1040,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1043
1040
  const rule = ruleMapper(ruleId);
1044
1041
 
1045
1042
  if (!rule) {
1046
- lintingProblems.push(createLintingProblem({ ruleId }));
1043
+ lintingProblems.push(createLintingProblem({ ruleId, language }));
1047
1044
  return;
1048
1045
  }
1049
1046
 
@@ -1073,7 +1070,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1073
1070
  severity,
1074
1071
  sourceCode,
1075
1072
  messageIds,
1076
- disableFixes
1073
+ disableFixes,
1074
+ language
1077
1075
  });
1078
1076
  }
1079
1077
  const problem = reportTranslator(...args);
@@ -1144,7 +1142,12 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1144
1142
  });
1145
1143
  });
1146
1144
 
1147
- const eventGenerator = new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
1145
+ const eventGenerator = new NodeEventGenerator(emitter, {
1146
+ visitorKeys: sourceCode.visitorKeys ?? language.visitorKeys,
1147
+ fallback: Traverser.getKeys,
1148
+ matchClass: language.matchesSelectorClass ?? (() => false),
1149
+ nodeTypeKey: language.nodeTypeKey
1150
+ });
1148
1151
 
1149
1152
  for (const step of eventQueue) {
1150
1153
  switch (step.kind) {
@@ -1271,11 +1274,13 @@ class Linter {
1271
1274
  * Initialize the Linter.
1272
1275
  * @param {Object} [config] the config object
1273
1276
  * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
1277
+ * @param {Array<string>} [config.flags] the feature flags to enable.
1274
1278
  * @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used.
1275
1279
  */
1276
- constructor({ cwd, configType = "flat" } = {}) {
1280
+ constructor({ cwd, configType = "flat", flags = [] } = {}) {
1277
1281
  internalSlotsMap.set(this, {
1278
1282
  cwd: normalizeCwd(cwd),
1283
+ flags: flags.filter(flag => activeFlags.has(flag)),
1279
1284
  lastConfigArray: null,
1280
1285
  lastSourceCode: null,
1281
1286
  lastSuppressedMessages: [],
@@ -1296,6 +1301,15 @@ class Linter {
1296
1301
  return pkg.version;
1297
1302
  }
1298
1303
 
1304
+ /**
1305
+ * Indicates if the given feature flag is enabled for this instance.
1306
+ * @param {string} flag The feature flag to check.
1307
+ * @returns {boolean} `true` if the feature flag is enabled, `false` if not.
1308
+ */
1309
+ hasFlag(flag) {
1310
+ return internalSlotsMap.get(this).flags.includes(flag);
1311
+ }
1312
+
1299
1313
  /**
1300
1314
  * Same as linter.verify, except without support for processors.
1301
1315
  * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
@@ -1360,6 +1374,9 @@ class Linter {
1360
1374
  parser,
1361
1375
  parserOptions
1362
1376
  });
1377
+ const file = new VFile(options.filename, text, {
1378
+ physicalPath: providedOptions.physicalFilename
1379
+ });
1363
1380
 
1364
1381
  if (!slots.lastSourceCode) {
1365
1382
  let t;
@@ -1369,9 +1386,9 @@ class Linter {
1369
1386
  }
1370
1387
 
1371
1388
  const parseResult = parse(
1372
- text,
1373
- languageOptions,
1374
- options.filename
1389
+ file,
1390
+ jslang,
1391
+ languageOptions
1375
1392
  );
1376
1393
 
1377
1394
  if (options.stats) {
@@ -1382,7 +1399,7 @@ class Linter {
1382
1399
  }
1383
1400
 
1384
1401
  if (!parseResult.success) {
1385
- return [parseResult.error];
1402
+ return parseResult.errors;
1386
1403
  }
1387
1404
 
1388
1405
  slots.lastSourceCode = parseResult.sourceCode;
@@ -1396,6 +1413,7 @@ class Linter {
1396
1413
  slots.lastSourceCode = new SourceCode({
1397
1414
  text: slots.lastSourceCode.text,
1398
1415
  ast: slots.lastSourceCode.ast,
1416
+ hasBOM: slots.lastSourceCode.hasBOM,
1399
1417
  parserServices: slots.lastSourceCode.parserServices,
1400
1418
  visitorKeys: slots.lastSourceCode.visitorKeys,
1401
1419
  scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
@@ -1408,7 +1426,6 @@ class Linter {
1408
1426
  ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config)
1409
1427
  : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1410
1428
 
1411
- // augment global scope with declared global variables
1412
1429
  addDeclaredGlobals(
1413
1430
  sourceCode.scopeManager.scopes[0],
1414
1431
  configuredGlobals,
@@ -1425,6 +1442,7 @@ class Linter {
1425
1442
  configuredRules,
1426
1443
  ruleId => getRule(slots, ruleId),
1427
1444
  parserName,
1445
+ jslang,
1428
1446
  languageOptions,
1429
1447
  settings,
1430
1448
  options.filename,
@@ -1457,6 +1475,7 @@ class Linter {
1457
1475
  }
1458
1476
 
1459
1477
  return applyDisableDirectives({
1478
+ language: jslang,
1460
1479
  directives: commentDirectives.disableDirectives,
1461
1480
  disableFixes: options.disableFixes,
1462
1481
  problems: lintingProblems
@@ -1659,6 +1678,9 @@ class Linter {
1659
1678
  }
1660
1679
 
1661
1680
  const settings = config.settings || {};
1681
+ const file = new VFile(options.filename, text, {
1682
+ physicalPath: providedOptions.physicalFilename
1683
+ });
1662
1684
 
1663
1685
  if (!slots.lastSourceCode) {
1664
1686
  let t;
@@ -1668,9 +1690,9 @@ class Linter {
1668
1690
  }
1669
1691
 
1670
1692
  const parseResult = parse(
1671
- text,
1672
- languageOptions,
1673
- options.filename
1693
+ file,
1694
+ config.language,
1695
+ languageOptions
1674
1696
  );
1675
1697
 
1676
1698
  if (options.stats) {
@@ -1680,7 +1702,7 @@ class Linter {
1680
1702
  }
1681
1703
 
1682
1704
  if (!parseResult.success) {
1683
- return [parseResult.error];
1705
+ return parseResult.errors;
1684
1706
  }
1685
1707
 
1686
1708
  slots.lastSourceCode = parseResult.sourceCode;
@@ -1689,11 +1711,17 @@ class Linter {
1689
1711
  /*
1690
1712
  * If the given source code object as the first argument does not have scopeManager, analyze the scope.
1691
1713
  * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
1714
+ *
1715
+ * We check explicitly for `null` to ensure that this is a JS-flavored language.
1716
+ * For non-JS languages we don't want to do this.
1717
+ *
1718
+ * TODO: Remove this check when we stop exporting the `SourceCode` object.
1692
1719
  */
1693
- if (!slots.lastSourceCode.scopeManager) {
1720
+ if (slots.lastSourceCode.scopeManager === null) {
1694
1721
  slots.lastSourceCode = new SourceCode({
1695
1722
  text: slots.lastSourceCode.text,
1696
1723
  ast: slots.lastSourceCode.ast,
1724
+ hasBOM: slots.lastSourceCode.hasBOM,
1697
1725
  parserServices: slots.lastSourceCode.parserServices,
1698
1726
  visitorKeys: slots.lastSourceCode.visitorKeys,
1699
1727
  scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
@@ -1708,7 +1736,7 @@ class Linter {
1708
1736
  * this is primarily about adding variables into the global scope
1709
1737
  * to account for ecmaVersion and configured globals.
1710
1738
  */
1711
- sourceCode.applyLanguageOptions(languageOptions);
1739
+ sourceCode.applyLanguageOptions?.(languageOptions);
1712
1740
 
1713
1741
  const mergedInlineConfig = {
1714
1742
  rules: {}
@@ -1725,140 +1753,151 @@ class Linter {
1725
1753
 
1726
1754
  // if inline config should warn then add the warnings
1727
1755
  if (options.warnInlineConfig) {
1728
- sourceCode.getInlineConfigNodes().forEach(node => {
1729
- inlineConfigProblems.push(createLintingProblem({
1730
- ruleId: null,
1731
- message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
1732
- loc: node.loc,
1733
- severity: 1
1734
- }));
1756
+ if (sourceCode.getInlineConfigNodes) {
1757
+ sourceCode.getInlineConfigNodes().forEach(node => {
1758
+ inlineConfigProblems.push(createLintingProblem({
1759
+ ruleId: null,
1760
+ message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
1761
+ loc: node.loc,
1762
+ severity: 1,
1763
+ language: config.language
1764
+ }));
1735
1765
 
1736
- });
1766
+ });
1767
+ }
1737
1768
  } else {
1738
- const inlineConfigResult = sourceCode.applyInlineConfig();
1739
-
1740
- inlineConfigProblems.push(
1741
- ...inlineConfigResult.problems
1742
- .map(createLintingProblem)
1743
- .map(problem => {
1744
- problem.fatal = true;
1745
- return problem;
1746
- })
1747
- );
1769
+ const inlineConfigResult = sourceCode.applyInlineConfig?.();
1770
+
1771
+ if (inlineConfigResult) {
1772
+ inlineConfigProblems.push(
1773
+ ...inlineConfigResult.problems
1774
+ .map(problem => createLintingProblem({ ...problem, language: config.language }))
1775
+ .map(problem => {
1776
+ problem.fatal = true;
1777
+ return problem;
1778
+ })
1779
+ );
1780
+
1781
+ // next we need to verify information about the specified rules
1782
+ const ruleValidator = new RuleValidator();
1783
+
1784
+ for (const { config: inlineConfig, loc } of inlineConfigResult.configs) {
1785
+
1786
+ Object.keys(inlineConfig.rules).forEach(ruleId => {
1787
+ const rule = getRuleFromConfig(ruleId, config);
1788
+ const ruleValue = inlineConfig.rules[ruleId];
1789
+
1790
+ if (!rule) {
1791
+ inlineConfigProblems.push(createLintingProblem({
1792
+ ruleId,
1793
+ loc,
1794
+ language: config.language
1795
+ }));
1796
+ return;
1797
+ }
1748
1798
 
1749
- // next we need to verify information about the specified rules
1750
- const ruleValidator = new RuleValidator();
1799
+ if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
1800
+ inlineConfigProblems.push(createLintingProblem({
1801
+ message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
1802
+ loc,
1803
+ language: config.language
1804
+ }));
1805
+ return;
1806
+ }
1751
1807
 
1752
- for (const { config: inlineConfig, node } of inlineConfigResult.configs) {
1808
+ try {
1753
1809
 
1754
- Object.keys(inlineConfig.rules).forEach(ruleId => {
1755
- const rule = getRuleFromConfig(ruleId, config);
1756
- const ruleValue = inlineConfig.rules[ruleId];
1810
+ let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1757
1811
 
1758
- if (!rule) {
1759
- inlineConfigProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
1760
- return;
1761
- }
1812
+ assertIsRuleSeverity(ruleId, ruleOptions[0]);
1762
1813
 
1763
- if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
1764
- inlineConfigProblems.push(createLintingProblem({
1765
- message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
1766
- loc: node.loc
1767
- }));
1768
- return;
1769
- }
1814
+ /*
1815
+ * If the rule was already configured, inline rule configuration that
1816
+ * only has severity should retain options from the config and just override the severity.
1817
+ *
1818
+ * Example:
1819
+ *
1820
+ * {
1821
+ * rules: {
1822
+ * curly: ["error", "multi"]
1823
+ * }
1824
+ * }
1825
+ *
1826
+ * /* eslint curly: ["warn"] * /
1827
+ *
1828
+ * Results in:
1829
+ *
1830
+ * curly: ["warn", "multi"]
1831
+ */
1770
1832
 
1771
- try {
1833
+ let shouldValidateOptions = true;
1772
1834
 
1773
- let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1835
+ if (
1774
1836
 
1775
- assertIsRuleSeverity(ruleId, ruleOptions[0]);
1837
+ /*
1838
+ * If inline config for the rule has only severity
1839
+ */
1840
+ ruleOptions.length === 1 &&
1776
1841
 
1777
- /*
1778
- * If the rule was already configured, inline rule configuration that
1779
- * only has severity should retain options from the config and just override the severity.
1780
- *
1781
- * Example:
1782
- *
1783
- * {
1784
- * rules: {
1785
- * curly: ["error", "multi"]
1786
- * }
1787
- * }
1788
- *
1789
- * /* eslint curly: ["warn"] * /
1790
- *
1791
- * Results in:
1792
- *
1793
- * curly: ["warn", "multi"]
1794
- */
1842
+ /*
1843
+ * And the rule was already configured
1844
+ */
1845
+ config.rules && Object.hasOwn(config.rules, ruleId)
1846
+ ) {
1795
1847
 
1796
- let shouldValidateOptions = true;
1848
+ /*
1849
+ * Then use severity from the inline config and options from the provided config
1850
+ */
1851
+ ruleOptions = [
1852
+ ruleOptions[0], // severity from the inline config
1853
+ ...config.rules[ruleId].slice(1) // options from the provided config
1854
+ ];
1797
1855
 
1798
- if (
1856
+ // if the rule was enabled, the options have already been validated
1857
+ if (config.rules[ruleId][0] > 0) {
1858
+ shouldValidateOptions = false;
1859
+ }
1860
+ }
1799
1861
 
1800
- /*
1801
- * If inline config for the rule has only severity
1802
- */
1803
- ruleOptions.length === 1 &&
1862
+ if (shouldValidateOptions) {
1863
+ ruleValidator.validate({
1864
+ plugins: config.plugins,
1865
+ rules: {
1866
+ [ruleId]: ruleOptions
1867
+ }
1868
+ });
1869
+ }
1804
1870
 
1805
- /*
1806
- * And the rule was already configured
1807
- */
1808
- config.rules && Object.hasOwn(config.rules, ruleId)
1809
- ) {
1871
+ mergedInlineConfig.rules[ruleId] = ruleOptions;
1872
+ } catch (err) {
1810
1873
 
1811
1874
  /*
1812
- * Then use severity from the inline config and options from the provided config
1875
+ * If the rule has invalid `meta.schema`, throw the error because
1876
+ * this is not an invalid inline configuration but an invalid rule.
1813
1877
  */
1814
- ruleOptions = [
1815
- ruleOptions[0], // severity from the inline config
1816
- ...config.rules[ruleId].slice(1) // options from the provided config
1817
- ];
1818
-
1819
- // if the rule was enabled, the options have already been validated
1820
- if (config.rules[ruleId][0] > 0) {
1821
- shouldValidateOptions = false;
1878
+ if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
1879
+ throw err;
1822
1880
  }
1823
- }
1824
-
1825
- if (shouldValidateOptions) {
1826
- ruleValidator.validate({
1827
- plugins: config.plugins,
1828
- rules: {
1829
- [ruleId]: ruleOptions
1830
- }
1831
- });
1832
- }
1833
1881
 
1834
- mergedInlineConfig.rules[ruleId] = ruleOptions;
1835
- } catch (err) {
1836
-
1837
- /*
1838
- * If the rule has invalid `meta.schema`, throw the error because
1839
- * this is not an invalid inline configuration but an invalid rule.
1840
- */
1841
- if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
1842
- throw err;
1843
- }
1882
+ let baseMessage = err.message.slice(
1883
+ err.message.startsWith("Key \"rules\":")
1884
+ ? err.message.indexOf(":", 12) + 1
1885
+ : err.message.indexOf(":") + 1
1886
+ ).trim();
1844
1887
 
1845
- let baseMessage = err.message.slice(
1846
- err.message.startsWith("Key \"rules\":")
1847
- ? err.message.indexOf(":", 12) + 1
1848
- : err.message.indexOf(":") + 1
1849
- ).trim();
1888
+ if (err.messageTemplate) {
1889
+ baseMessage += ` You passed "${ruleValue}".`;
1890
+ }
1850
1891
 
1851
- if (err.messageTemplate) {
1852
- baseMessage += ` You passed "${ruleValue}".`;
1892
+ inlineConfigProblems.push(createLintingProblem({
1893
+ ruleId,
1894
+ message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
1895
+ loc,
1896
+ language: config.language
1897
+ }));
1853
1898
  }
1854
-
1855
- inlineConfigProblems.push(createLintingProblem({
1856
- ruleId,
1857
- message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
1858
- loc: node.loc
1859
- }));
1860
- }
1861
- });
1899
+ });
1900
+ }
1862
1901
  }
1863
1902
  }
1864
1903
  }
@@ -1866,7 +1905,8 @@ class Linter {
1866
1905
  const commentDirectives = options.allowInlineConfig && !options.warnInlineConfig
1867
1906
  ? getDirectiveCommentsForFlatConfig(
1868
1907
  sourceCode,
1869
- ruleId => getRuleFromConfig(ruleId, config)
1908
+ ruleId => getRuleFromConfig(ruleId, config),
1909
+ config.language
1870
1910
  )
1871
1911
  : { problems: [], disableDirectives: [] };
1872
1912
 
@@ -1874,7 +1914,7 @@ class Linter {
1874
1914
 
1875
1915
  let lintingProblems;
1876
1916
 
1877
- sourceCode.finalize();
1917
+ sourceCode.finalize?.();
1878
1918
 
1879
1919
  try {
1880
1920
  lintingProblems = runRules(
@@ -1882,6 +1922,7 @@ class Linter {
1882
1922
  configuredRules,
1883
1923
  ruleId => getRuleFromConfig(ruleId, config),
1884
1924
  void 0,
1925
+ config.language,
1885
1926
  languageOptions,
1886
1927
  settings,
1887
1928
  options.filename,
@@ -1915,6 +1956,7 @@ class Linter {
1915
1956
  }
1916
1957
 
1917
1958
  return applyDisableDirectives({
1959
+ language: config.language,
1918
1960
  directives: commentDirectives.disableDirectives,
1919
1961
  disableFixes: options.disableFixes,
1920
1962
  problems: lintingProblems