eslint 9.4.0 → 9.5.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 (32) hide show
  1. package/README.md +2 -2
  2. package/lib/api.js +1 -1
  3. package/lib/config/default-config.js +5 -0
  4. package/lib/config/flat-config-array.js +71 -8
  5. package/lib/config/flat-config-schema.js +46 -62
  6. package/lib/eslint/eslint-helpers.js +27 -17
  7. package/lib/eslint/eslint.js +14 -10
  8. package/lib/languages/js/index.js +247 -0
  9. package/lib/{source-code → languages/js/source-code}/source-code.js +38 -18
  10. package/lib/languages/js/validate-language-options.js +181 -0
  11. package/lib/linter/apply-disable-directives.js +8 -3
  12. package/lib/linter/linter.js +122 -121
  13. package/lib/linter/report-translator.js +14 -7
  14. package/lib/linter/vfile.js +104 -0
  15. package/lib/rule-tester/rule-tester.js +5 -2
  16. package/lib/rules/no-sparse-arrays.js +26 -3
  17. package/messages/all-matched-files-ignored.js +21 -0
  18. package/package.json +11 -11
  19. /package/lib/{source-code → languages/js/source-code}/index.js +0 -0
  20. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-comment-cursor.js +0 -0
  21. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-cursor.js +0 -0
  22. /package/lib/{source-code → languages/js/source-code}/token-store/cursor.js +0 -0
  23. /package/lib/{source-code → languages/js/source-code}/token-store/cursors.js +0 -0
  24. /package/lib/{source-code → languages/js/source-code}/token-store/decorative-cursor.js +0 -0
  25. /package/lib/{source-code → languages/js/source-code}/token-store/filter-cursor.js +0 -0
  26. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-comment-cursor.js +0 -0
  27. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-cursor.js +0 -0
  28. /package/lib/{source-code → languages/js/source-code}/token-store/index.js +0 -0
  29. /package/lib/{source-code → languages/js/source-code}/token-store/limit-cursor.js +0 -0
  30. /package/lib/{source-code → languages/js/source-code}/token-store/padded-token-cursor.js +0 -0
  31. /package/lib/{source-code → languages/js/source-code}/token-store/skip-cursor.js +0 -0
  32. /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,7 @@ 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
48
  const debug = require("debug")("eslint:linter");
49
49
  const MAX_AUTOFIX_PASSES = 10;
50
50
  const DEFAULT_PARSER_NAME = "espree";
@@ -53,6 +53,7 @@ const commentParser = new ConfigCommentParser();
53
53
  const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
54
54
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
55
55
  const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
56
+ const { VFile } = require("./vfile");
56
57
  const STEP_KIND_VISIT = 1;
57
58
  const STEP_KIND_CALL = 2;
58
59
 
@@ -239,6 +240,35 @@ function createMissingRuleMessage(ruleId) {
239
240
  : `Definition for rule '${ruleId}' was not found.`;
240
241
  }
241
242
 
243
+ /**
244
+ * Updates a given location based on the language offsets. This allows us to
245
+ * change 0-based locations to 1-based locations. We always want ESLint
246
+ * reporting lines and columns starting from 1.
247
+ * @param {Object} location The location to update.
248
+ * @param {number} location.line The starting line number.
249
+ * @param {number} location.column The starting column number.
250
+ * @param {number} [location.endLine] The ending line number.
251
+ * @param {number} [location.endColumn] The ending column number.
252
+ * @param {Language} language The language to use to adjust the location information.
253
+ * @returns {Object} The updated location.
254
+ */
255
+ function updateLocationInformation({ line, column, endLine, endColumn }, language) {
256
+
257
+ const columnOffset = language.columnStart === 1 ? 0 : 1;
258
+ const lineOffset = language.lineStart === 1 ? 0 : 1;
259
+
260
+ // calculate separately to account for undefined
261
+ const finalEndLine = endLine === void 0 ? endLine : endLine + lineOffset;
262
+ const finalEndColumn = endColumn === void 0 ? endColumn : endColumn + columnOffset;
263
+
264
+ return {
265
+ line: line + lineOffset,
266
+ column: column + columnOffset,
267
+ endLine: finalEndLine,
268
+ endColumn: finalEndColumn
269
+ };
270
+ }
271
+
242
272
  /**
243
273
  * creates a linting problem
244
274
  * @param {Object} options to create linting error
@@ -246,6 +276,7 @@ function createMissingRuleMessage(ruleId) {
246
276
  * @param {Object} [options.loc] the loc to report
247
277
  * @param {string} [options.message] the error message to report
248
278
  * @param {string} [options.severity] the error message to report
279
+ * @param {Language} [options.language] the language to use to adjust the location information
249
280
  * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
250
281
  * @private
251
282
  */
@@ -254,16 +285,24 @@ function createLintingProblem(options) {
254
285
  ruleId = null,
255
286
  loc = DEFAULT_ERROR_LOC,
256
287
  message = createMissingRuleMessage(options.ruleId),
257
- severity = 2
288
+ severity = 2,
289
+
290
+ // fallback for eslintrc mode
291
+ language = {
292
+ columnStart: 0,
293
+ lineStart: 1
294
+ }
258
295
  } = options;
259
296
 
260
297
  return {
261
298
  ruleId,
262
299
  message,
263
- line: loc.start.line,
264
- column: loc.start.column + 1,
265
- endLine: loc.end.line,
266
- endColumn: loc.end.column + 1,
300
+ ...updateLocationInformation({
301
+ line: loc.start.line,
302
+ column: loc.start.column,
303
+ endLine: loc.end.line,
304
+ endColumn: loc.end.column
305
+ }, language),
267
306
  severity,
268
307
  nodeType: null
269
308
  };
@@ -541,10 +580,11 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
541
580
  * Parses comments in file to extract disable directives.
542
581
  * @param {SourceCode} sourceCode The SourceCode object to get comments from.
543
582
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
583
+ * @param {Language} language The language to use to adjust the location information
544
584
  * @returns {{problems: LintMessage[], disableDirectives: DisableDirective[]}}
545
585
  * A collection of the directive comments that were found, along with any problems that occurred when parsing
546
586
  */
547
- function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
587
+ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) {
548
588
  const disableDirectives = [];
549
589
  const problems = [];
550
590
 
@@ -553,7 +593,10 @@ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
553
593
  problems: directivesProblems
554
594
  } = sourceCode.getDisableDirectives();
555
595
 
556
- problems.push(...directivesProblems.map(createLintingProblem));
596
+ problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({
597
+ ...directiveProblem,
598
+ language
599
+ })));
557
600
 
558
601
  directivesSources.forEach(directive => {
559
602
  const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);
@@ -781,24 +824,6 @@ function resolveGlobals(providedGlobals, enabledEnvironments) {
781
824
  );
782
825
  }
783
826
 
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
827
  /**
803
828
  * Store time measurements in map
804
829
  * @param {number} time Time measurement
@@ -866,93 +891,40 @@ function analyzeScope(ast, languageOptions, visitorKeys) {
866
891
  }
867
892
 
868
893
  /**
869
- * Parses text into an AST. Moved out here because the try-catch prevents
894
+ * Parses file into an AST. Moved out here because the try-catch prevents
870
895
  * optimization of functions, so it's best to keep the try-catch as isolated
871
896
  * as possible
872
- * @param {string} text The text to parse.
897
+ * @param {VFile} file The file to parse.
898
+ * @param {Language} language The language to use.
873
899
  * @param {LanguageOptions} languageOptions Options to pass to the parser
874
- * @param {string} filePath The path to the file being parsed.
875
900
  * @returns {{success: false, error: LintMessage}|{success: true, sourceCode: SourceCode}}
876
901
  * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
877
902
  * @private
878
903
  */
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) };
904
+ function parse(file, language, languageOptions) {
908
905
 
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);
916
-
917
- debug("Scope analysis successful:", filePath);
906
+ const result = language.parse(file, { languageOptions });
918
907
 
908
+ if (result.ok) {
919
909
  return {
920
910
  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
- }
911
+ sourceCode: language.createSourceCode(file, result, { languageOptions })
954
912
  };
955
913
  }
914
+
915
+ // if we made it to here there was an error
916
+ return {
917
+ success: false,
918
+ errors: result.errors.map(error => ({
919
+ ruleId: null,
920
+ nodeType: null,
921
+ fatal: true,
922
+ severity: 2,
923
+ message: error.message,
924
+ line: error.line,
925
+ column: error.column
926
+ }))
927
+ };
956
928
  }
957
929
 
958
930
  /**
@@ -983,6 +955,7 @@ function createRuleListeners(rule, ruleContext) {
983
955
  * @param {Object} configuredRules The rules configuration
984
956
  * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
985
957
  * @param {string | undefined} parserName The name of the parser in the config
958
+ * @param {Language} language The language object used for parsing.
986
959
  * @param {LanguageOptions} languageOptions The options for parsing the code.
987
960
  * @param {Object} settings The settings that were enabled in the config
988
961
  * @param {string} filename The reported filename of the code
@@ -995,8 +968,11 @@ function createRuleListeners(rule, ruleContext) {
995
968
  * @returns {LintMessage[]} An array of reported problems
996
969
  * @throws {Error} If traversal into a node fails.
997
970
  */
998
- function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
999
- stats, slots) {
971
+ function runRules(
972
+ sourceCode, configuredRules, ruleMapper, parserName, language, languageOptions,
973
+ settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
974
+ stats, slots
975
+ ) {
1000
976
  const emitter = createEmitter();
1001
977
 
1002
978
  // must happen first to assign all node.parent properties
@@ -1043,7 +1019,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1043
1019
  const rule = ruleMapper(ruleId);
1044
1020
 
1045
1021
  if (!rule) {
1046
- lintingProblems.push(createLintingProblem({ ruleId }));
1022
+ lintingProblems.push(createLintingProblem({ ruleId, language }));
1047
1023
  return;
1048
1024
  }
1049
1025
 
@@ -1073,7 +1049,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1073
1049
  severity,
1074
1050
  sourceCode,
1075
1051
  messageIds,
1076
- disableFixes
1052
+ disableFixes,
1053
+ language
1077
1054
  });
1078
1055
  }
1079
1056
  const problem = reportTranslator(...args);
@@ -1144,7 +1121,12 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1144
1121
  });
1145
1122
  });
1146
1123
 
1147
- const eventGenerator = new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
1124
+ const eventGenerator = new NodeEventGenerator(emitter, {
1125
+ visitorKeys: sourceCode.visitorKeys,
1126
+ fallback: Traverser.getKeys,
1127
+ matchClass: language.matchesSelectorClass,
1128
+ nodeTypeKey: language.nodeTypeKey
1129
+ });
1148
1130
 
1149
1131
  for (const step of eventQueue) {
1150
1132
  switch (step.kind) {
@@ -1360,6 +1342,9 @@ class Linter {
1360
1342
  parser,
1361
1343
  parserOptions
1362
1344
  });
1345
+ const file = new VFile(options.filename, text, {
1346
+ physicalPath: providedOptions.physicalFilename
1347
+ });
1363
1348
 
1364
1349
  if (!slots.lastSourceCode) {
1365
1350
  let t;
@@ -1369,9 +1354,9 @@ class Linter {
1369
1354
  }
1370
1355
 
1371
1356
  const parseResult = parse(
1372
- text,
1373
- languageOptions,
1374
- options.filename
1357
+ file,
1358
+ jslang,
1359
+ languageOptions
1375
1360
  );
1376
1361
 
1377
1362
  if (options.stats) {
@@ -1382,7 +1367,7 @@ class Linter {
1382
1367
  }
1383
1368
 
1384
1369
  if (!parseResult.success) {
1385
- return [parseResult.error];
1370
+ return parseResult.errors;
1386
1371
  }
1387
1372
 
1388
1373
  slots.lastSourceCode = parseResult.sourceCode;
@@ -1396,6 +1381,7 @@ class Linter {
1396
1381
  slots.lastSourceCode = new SourceCode({
1397
1382
  text: slots.lastSourceCode.text,
1398
1383
  ast: slots.lastSourceCode.ast,
1384
+ hasBOM: slots.lastSourceCode.hasBOM,
1399
1385
  parserServices: slots.lastSourceCode.parserServices,
1400
1386
  visitorKeys: slots.lastSourceCode.visitorKeys,
1401
1387
  scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
@@ -1408,7 +1394,6 @@ class Linter {
1408
1394
  ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config)
1409
1395
  : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
1410
1396
 
1411
- // augment global scope with declared global variables
1412
1397
  addDeclaredGlobals(
1413
1398
  sourceCode.scopeManager.scopes[0],
1414
1399
  configuredGlobals,
@@ -1425,6 +1410,7 @@ class Linter {
1425
1410
  configuredRules,
1426
1411
  ruleId => getRule(slots, ruleId),
1427
1412
  parserName,
1413
+ jslang,
1428
1414
  languageOptions,
1429
1415
  settings,
1430
1416
  options.filename,
@@ -1457,6 +1443,7 @@ class Linter {
1457
1443
  }
1458
1444
 
1459
1445
  return applyDisableDirectives({
1446
+ language: jslang,
1460
1447
  directives: commentDirectives.disableDirectives,
1461
1448
  disableFixes: options.disableFixes,
1462
1449
  problems: lintingProblems
@@ -1659,6 +1646,9 @@ class Linter {
1659
1646
  }
1660
1647
 
1661
1648
  const settings = config.settings || {};
1649
+ const file = new VFile(options.filename, text, {
1650
+ physicalPath: providedOptions.physicalFilename
1651
+ });
1662
1652
 
1663
1653
  if (!slots.lastSourceCode) {
1664
1654
  let t;
@@ -1668,9 +1658,9 @@ class Linter {
1668
1658
  }
1669
1659
 
1670
1660
  const parseResult = parse(
1671
- text,
1672
- languageOptions,
1673
- options.filename
1661
+ file,
1662
+ config.language,
1663
+ languageOptions
1674
1664
  );
1675
1665
 
1676
1666
  if (options.stats) {
@@ -1680,7 +1670,7 @@ class Linter {
1680
1670
  }
1681
1671
 
1682
1672
  if (!parseResult.success) {
1683
- return [parseResult.error];
1673
+ return parseResult.errors;
1684
1674
  }
1685
1675
 
1686
1676
  slots.lastSourceCode = parseResult.sourceCode;
@@ -1694,6 +1684,7 @@ class Linter {
1694
1684
  slots.lastSourceCode = new SourceCode({
1695
1685
  text: slots.lastSourceCode.text,
1696
1686
  ast: slots.lastSourceCode.ast,
1687
+ hasBOM: slots.lastSourceCode.hasBOM,
1697
1688
  parserServices: slots.lastSourceCode.parserServices,
1698
1689
  visitorKeys: slots.lastSourceCode.visitorKeys,
1699
1690
  scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions)
@@ -1730,7 +1721,8 @@ class Linter {
1730
1721
  ruleId: null,
1731
1722
  message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
1732
1723
  loc: node.loc,
1733
- severity: 1
1724
+ severity: 1,
1725
+ language: config.language
1734
1726
  }));
1735
1727
 
1736
1728
  });
@@ -1739,7 +1731,7 @@ class Linter {
1739
1731
 
1740
1732
  inlineConfigProblems.push(
1741
1733
  ...inlineConfigResult.problems
1742
- .map(createLintingProblem)
1734
+ .map(problem => createLintingProblem({ ...problem, language: config.language }))
1743
1735
  .map(problem => {
1744
1736
  problem.fatal = true;
1745
1737
  return problem;
@@ -1756,14 +1748,19 @@ class Linter {
1756
1748
  const ruleValue = inlineConfig.rules[ruleId];
1757
1749
 
1758
1750
  if (!rule) {
1759
- inlineConfigProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
1751
+ inlineConfigProblems.push(createLintingProblem({
1752
+ ruleId,
1753
+ loc: node.loc,
1754
+ language: config.language
1755
+ }));
1760
1756
  return;
1761
1757
  }
1762
1758
 
1763
1759
  if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
1764
1760
  inlineConfigProblems.push(createLintingProblem({
1765
1761
  message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
1766
- loc: node.loc
1762
+ loc: node.loc,
1763
+ language: config.language
1767
1764
  }));
1768
1765
  return;
1769
1766
  }
@@ -1855,7 +1852,8 @@ class Linter {
1855
1852
  inlineConfigProblems.push(createLintingProblem({
1856
1853
  ruleId,
1857
1854
  message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
1858
- loc: node.loc
1855
+ loc: node.loc,
1856
+ language: config.language
1859
1857
  }));
1860
1858
  }
1861
1859
  });
@@ -1866,7 +1864,8 @@ class Linter {
1866
1864
  const commentDirectives = options.allowInlineConfig && !options.warnInlineConfig
1867
1865
  ? getDirectiveCommentsForFlatConfig(
1868
1866
  sourceCode,
1869
- ruleId => getRuleFromConfig(ruleId, config)
1867
+ ruleId => getRuleFromConfig(ruleId, config),
1868
+ config.language
1870
1869
  )
1871
1870
  : { problems: [], disableDirectives: [] };
1872
1871
 
@@ -1882,6 +1881,7 @@ class Linter {
1882
1881
  configuredRules,
1883
1882
  ruleId => getRuleFromConfig(ruleId, config),
1884
1883
  void 0,
1884
+ config.language,
1885
1885
  languageOptions,
1886
1886
  settings,
1887
1887
  options.filename,
@@ -1915,6 +1915,7 @@ class Linter {
1915
1915
  }
1916
1916
 
1917
1917
  return applyDisableDirectives({
1918
+ language: config.language,
1918
1919
  directives: commentDirectives.disableDirectives,
1919
1920
  disableFixes: options.disableFixes,
1920
1921
  problems: lintingProblems
@@ -240,15 +240,22 @@ function mapSuggestions(descriptor, sourceCode, messages) {
240
240
  * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location
241
241
  * @param {{text: string, range: (number[]|null)}} options.fix The fix object
242
242
  * @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects
243
+ * @param {Language} [options.language] The language to use to adjust line and column offsets.
243
244
  * @returns {LintMessage} Information about the report
244
245
  */
245
246
  function createProblem(options) {
247
+ const { language } = options;
248
+
249
+ // calculate offsets based on the language in use
250
+ const columnOffset = language.columnStart === 1 ? 0 : 1;
251
+ const lineOffset = language.lineStart === 1 ? 0 : 1;
252
+
246
253
  const problem = {
247
254
  ruleId: options.ruleId,
248
255
  severity: options.severity,
249
256
  message: options.message,
250
- line: options.loc.start.line,
251
- column: options.loc.start.column + 1,
257
+ line: options.loc.start.line + lineOffset,
258
+ column: options.loc.start.column + columnOffset,
252
259
  nodeType: options.node && options.node.type || null
253
260
  };
254
261
 
@@ -261,8 +268,8 @@ function createProblem(options) {
261
268
  }
262
269
 
263
270
  if (options.loc.end) {
264
- problem.endLine = options.loc.end.line;
265
- problem.endColumn = options.loc.end.column + 1;
271
+ problem.endLine = options.loc.end.line + lineOffset;
272
+ problem.endColumn = options.loc.end.column + columnOffset;
266
273
  }
267
274
 
268
275
  if (options.fix) {
@@ -313,8 +320,7 @@ function validateSuggestions(suggest, messages) {
313
320
  /**
314
321
  * Returns a function that converts the arguments of a `context.report` call from a rule into a reported
315
322
  * problem for the Node.js API.
316
- * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean}} metadata Metadata for the reported problem
317
- * @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted
323
+ * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean, language:Language}} metadata Metadata for the reported problem
318
324
  * @returns {function(...args): LintMessage} Function that returns information about the report
319
325
  */
320
326
 
@@ -363,7 +369,8 @@ module.exports = function createReportTranslator(metadata) {
363
369
  messageId: descriptor.messageId,
364
370
  loc: normalizeReportLoc(descriptor),
365
371
  fix: metadata.disableFixes ? null : normalizeFixes(descriptor, metadata.sourceCode),
366
- suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, metadata.sourceCode, messages)
372
+ suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, metadata.sourceCode, messages),
373
+ language: metadata.language
367
374
  });
368
375
  };
369
376
  };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @fileoverview Virtual file
3
+ * @author Nicholas C. Zakas
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Helpers
10
+ //------------------------------------------------------------------------------
11
+
12
+ /**
13
+ * Determines if a given value has a byte order mark (BOM).
14
+ * @param {string|Uint8Array} value The value to check.
15
+ * @returns {boolean} `true` if the value has a BOM, `false` otherwise.
16
+ */
17
+ function hasUnicodeBOM(value) {
18
+ return typeof value === "string"
19
+ ? value.charCodeAt(0) === 0xFEFF
20
+ : value[0] === 0xEF && value[1] === 0xBB && value[2] === 0xBF;
21
+ }
22
+
23
+ /**
24
+ * Strips Unicode BOM from the given value.
25
+ * @param {string|Uint8Array} value The value to remove the BOM from.
26
+ * @returns {string|Uint8Array} The stripped value.
27
+ */
28
+ function stripUnicodeBOM(value) {
29
+
30
+ if (!hasUnicodeBOM(value)) {
31
+ return value;
32
+ }
33
+
34
+ if (typeof value === "string") {
35
+
36
+ /*
37
+ * Check Unicode BOM.
38
+ * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
39
+ * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
40
+ */
41
+ return value.slice(1);
42
+ }
43
+
44
+ /*
45
+ * In a Uint8Array, the BOM is represented by three bytes: 0xEF, 0xBB, and 0xBF,
46
+ * so we can just remove the first three bytes.
47
+ */
48
+ return value.slice(3);
49
+ }
50
+
51
+ //------------------------------------------------------------------------------
52
+ // Exports
53
+ //------------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Represents a virtual file inside of ESLint.
57
+ */
58
+ class VFile {
59
+
60
+ /**
61
+ * The file path including any processor-created virtual path.
62
+ * @type {string}
63
+ * @readonly
64
+ */
65
+ path;
66
+
67
+ /**
68
+ * The file path on disk.
69
+ * @type {string}
70
+ * @readonly
71
+ */
72
+ physicalPath;
73
+
74
+ /**
75
+ * The file contents.
76
+ * @type {string|Uint8Array}
77
+ * @readonly
78
+ */
79
+ body;
80
+
81
+ /**
82
+ * Indicates whether the file has a byte order mark (BOM).
83
+ * @type {boolean}
84
+ * @readonly
85
+ */
86
+ bom;
87
+
88
+ /**
89
+ * Creates a new instance.
90
+ * @param {string} path The file path.
91
+ * @param {string|Uint8Array} body The file contents.
92
+ * @param {Object} [options] Additional options.
93
+ * @param {string} [options.physicalPath] The file path on disk.
94
+ */
95
+ constructor(path, body, { physicalPath } = {}) {
96
+ this.path = path;
97
+ this.physicalPath = physicalPath ?? path;
98
+ this.bom = hasUnicodeBOM(body);
99
+ this.body = stripUnicodeBOM(body);
100
+ }
101
+
102
+ }
103
+
104
+ module.exports = { VFile };
@@ -27,10 +27,11 @@ const { defaultConfig } = require("../config/default-config");
27
27
  const ajv = require("../shared/ajv")({ strictDefaults: true });
28
28
 
29
29
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
30
- const { SourceCode } = require("../source-code");
31
30
  const { ConfigArraySymbol } = require("@eslint/config-array");
32
31
  const { isSerializable } = require("../shared/serialization");
33
32
 
33
+ const { SourceCode } = require("../languages/js/source-code");
34
+
34
35
  //------------------------------------------------------------------------------
35
36
  // Typedefs
36
37
  //------------------------------------------------------------------------------
@@ -591,7 +592,8 @@ class RuleTester {
591
592
  * here, just use the default one to keep that performance
592
593
  * enhancement.
593
594
  */
594
- rules: defaultConfig[0].plugins["@"].rules
595
+ rules: defaultConfig[0].plugins["@"].rules,
596
+ languages: defaultConfig[0].plugins["@"].languages
595
597
  },
596
598
  "rule-to-test": {
597
599
  rules: {
@@ -611,6 +613,7 @@ class RuleTester {
611
613
  }
612
614
  }
613
615
  },
616
+ language: defaultConfig[0].language,
614
617
  languageOptions: {
615
618
  ...defaultConfig[0].languageOptions
616
619
  }