eslint 9.5.0 → 9.7.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.
@@ -45,6 +45,7 @@ const { RuleValidator } = require("../config/rule-validator");
45
45
  const { assertIsRuleSeverity } = require("../config/flat-config-schema");
46
46
  const { normalizeSeverityToString } = require("../shared/severity");
47
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";
@@ -71,6 +72,10 @@ const STEP_KIND_CALL = 2;
71
72
  /** @typedef {import("../shared/types").Processor} Processor */
72
73
  /** @typedef {import("../shared/types").Rule} Rule */
73
74
  /** @typedef {import("../shared/types").Times} Times */
75
+ /** @typedef {import("@eslint/core").Language} Language */
76
+ /** @typedef {import("@eslint/core").RuleSeverity} RuleSeverity */
77
+ /** @typedef {import("@eslint/core").RuleConfig} RuleConfig */
78
+
74
79
 
75
80
  /* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
76
81
  /**
@@ -275,7 +280,7 @@ function updateLocationInformation({ line, column, endLine, endColumn }, languag
275
280
  * @param {string} [options.ruleId] the ruleId to report
276
281
  * @param {Object} [options.loc] the loc to report
277
282
  * @param {string} [options.message] the error message to report
278
- * @param {string} [options.severity] the error message to report
283
+ * @param {RuleSeverity} [options.severity] the error message to report
279
284
  * @param {Language} [options.language] the language to use to adjust the location information
280
285
  * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
281
286
  * @private
@@ -317,9 +322,10 @@ function createLintingProblem(options) {
317
322
  * @param {string} options.justification The justification of the directive
318
323
  * @param {ASTNode|token} options.node The Comment node/token.
319
324
  * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
325
+ * @param {Language} language The language to use to adjust the location information.
320
326
  * @returns {Object} Directives and problems from the comment
321
327
  */
322
- function createDisableDirectives({ type, value, justification, node }, ruleMapper) {
328
+ function createDisableDirectives({ type, value, justification, node }, ruleMapper, language) {
323
329
  const ruleIds = Object.keys(commentParser.parseListConfig(value));
324
330
  const directiveRules = ruleIds.length ? ruleIds : [null];
325
331
  const result = {
@@ -333,26 +339,36 @@ function createDisableDirectives({ type, value, justification, node }, ruleMappe
333
339
  // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
334
340
  if (ruleId === null || !!ruleMapper(ruleId)) {
335
341
  if (type === "disable-next-line") {
342
+ const { line, column } = updateLocationInformation(
343
+ node.loc.end,
344
+ language
345
+ );
346
+
336
347
  result.directives.push({
337
348
  parentDirective,
338
349
  type,
339
- line: node.loc.end.line,
340
- column: node.loc.end.column + 1,
350
+ line,
351
+ column,
341
352
  ruleId,
342
353
  justification
343
354
  });
344
355
  } else {
356
+ const { line, column } = updateLocationInformation(
357
+ node.loc.start,
358
+ language
359
+ );
360
+
345
361
  result.directives.push({
346
362
  parentDirective,
347
363
  type,
348
- line: node.loc.start.line,
349
- column: node.loc.start.column + 1,
364
+ line,
365
+ column,
350
366
  ruleId,
351
367
  justification
352
368
  });
353
369
  }
354
370
  } else {
355
- result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
371
+ result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc, language }));
356
372
  }
357
373
  }
358
374
  return result;
@@ -430,7 +446,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
430
446
  value: directiveValue,
431
447
  justification: justificationPart,
432
448
  node: comment
433
- }, ruleMapper);
449
+ }, ruleMapper, jslang);
434
450
 
435
451
  disableDirectives.push(...directives);
436
452
  problems.push(...directiveProblems);
@@ -470,7 +486,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
470
486
  break;
471
487
 
472
488
  case "eslint": {
473
- const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
489
+ const parseResult = commentParser.parseJsonConfig(directiveValue);
474
490
 
475
491
  if (parseResult.success) {
476
492
  Object.keys(parseResult.config).forEach(name => {
@@ -557,7 +573,14 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
557
573
  configuredRules[name] = ruleOptions;
558
574
  });
559
575
  } else {
560
- problems.push(parseResult.error);
576
+ const problem = createLintingProblem({
577
+ ruleId: null,
578
+ loc: comment.loc,
579
+ message: parseResult.error.message
580
+ });
581
+
582
+ problem.fatal = true;
583
+ problems.push(problem);
561
584
  }
562
585
 
563
586
  break;
@@ -588,22 +611,24 @@ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) {
588
611
  const disableDirectives = [];
589
612
  const problems = [];
590
613
 
591
- const {
592
- directives: directivesSources,
593
- problems: directivesProblems
594
- } = sourceCode.getDisableDirectives();
614
+ if (sourceCode.getDisableDirectives) {
615
+ const {
616
+ directives: directivesSources,
617
+ problems: directivesProblems
618
+ } = sourceCode.getDisableDirectives();
595
619
 
596
- problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({
597
- ...directiveProblem,
598
- language
599
- })));
620
+ problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({
621
+ ...directiveProblem,
622
+ language
623
+ })));
600
624
 
601
- directivesSources.forEach(directive => {
602
- const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);
625
+ directivesSources.forEach(directive => {
626
+ const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper, language);
603
627
 
604
- disableDirectives.push(...directives);
605
- problems.push(...directiveProblems);
606
- });
628
+ disableDirectives.push(...directives);
629
+ problems.push(...directiveProblems);
630
+ });
631
+ }
607
632
 
608
633
  return {
609
634
  problems,
@@ -856,7 +881,7 @@ function storeTime(time, timeOpts, slots) {
856
881
 
857
882
  /**
858
883
  * Get the options for a rule (not including severity), if any
859
- * @param {Array|number} ruleConfig rule configuration
884
+ * @param {RuleConfig} ruleConfig rule configuration
860
885
  * @returns {Array} of rule options, empty Array if none
861
886
  */
862
887
  function getRuleOptions(ruleConfig) {
@@ -920,7 +945,7 @@ function parse(file, language, languageOptions) {
920
945
  nodeType: null,
921
946
  fatal: true,
922
947
  severity: 2,
923
- message: error.message,
948
+ message: `Parsing error: ${error.message}`,
924
949
  line: error.line,
925
950
  column: error.column
926
951
  }))
@@ -1122,9 +1147,9 @@ function runRules(
1122
1147
  });
1123
1148
 
1124
1149
  const eventGenerator = new NodeEventGenerator(emitter, {
1125
- visitorKeys: sourceCode.visitorKeys,
1150
+ visitorKeys: sourceCode.visitorKeys ?? language.visitorKeys,
1126
1151
  fallback: Traverser.getKeys,
1127
- matchClass: language.matchesSelectorClass,
1152
+ matchClass: language.matchesSelectorClass ?? (() => false),
1128
1153
  nodeTypeKey: language.nodeTypeKey
1129
1154
  });
1130
1155
 
@@ -1253,11 +1278,13 @@ class Linter {
1253
1278
  * Initialize the Linter.
1254
1279
  * @param {Object} [config] the config object
1255
1280
  * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
1281
+ * @param {Array<string>} [config.flags] the feature flags to enable.
1256
1282
  * @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used.
1257
1283
  */
1258
- constructor({ cwd, configType = "flat" } = {}) {
1284
+ constructor({ cwd, configType = "flat", flags = [] } = {}) {
1259
1285
  internalSlotsMap.set(this, {
1260
1286
  cwd: normalizeCwd(cwd),
1287
+ flags: flags.filter(flag => activeFlags.has(flag)),
1261
1288
  lastConfigArray: null,
1262
1289
  lastSourceCode: null,
1263
1290
  lastSuppressedMessages: [],
@@ -1278,6 +1305,15 @@ class Linter {
1278
1305
  return pkg.version;
1279
1306
  }
1280
1307
 
1308
+ /**
1309
+ * Indicates if the given feature flag is enabled for this instance.
1310
+ * @param {string} flag The feature flag to check.
1311
+ * @returns {boolean} `true` if the feature flag is enabled, `false` if not.
1312
+ */
1313
+ hasFlag(flag) {
1314
+ return internalSlotsMap.get(this).flags.includes(flag);
1315
+ }
1316
+
1281
1317
  /**
1282
1318
  * Same as linter.verify, except without support for processors.
1283
1319
  * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
@@ -1679,8 +1715,13 @@ class Linter {
1679
1715
  /*
1680
1716
  * If the given source code object as the first argument does not have scopeManager, analyze the scope.
1681
1717
  * This is for backward compatibility (SourceCode is frozen so it cannot rebind).
1718
+ *
1719
+ * We check explicitly for `null` to ensure that this is a JS-flavored language.
1720
+ * For non-JS languages we don't want to do this.
1721
+ *
1722
+ * TODO: Remove this check when we stop exporting the `SourceCode` object.
1682
1723
  */
1683
- if (!slots.lastSourceCode.scopeManager) {
1724
+ if (slots.lastSourceCode.scopeManager === null) {
1684
1725
  slots.lastSourceCode = new SourceCode({
1685
1726
  text: slots.lastSourceCode.text,
1686
1727
  ast: slots.lastSourceCode.ast,
@@ -1699,7 +1740,7 @@ class Linter {
1699
1740
  * this is primarily about adding variables into the global scope
1700
1741
  * to account for ecmaVersion and configured globals.
1701
1742
  */
1702
- sourceCode.applyLanguageOptions(languageOptions);
1743
+ sourceCode.applyLanguageOptions?.(languageOptions);
1703
1744
 
1704
1745
  const mergedInlineConfig = {
1705
1746
  rules: {}
@@ -1716,147 +1757,151 @@ class Linter {
1716
1757
 
1717
1758
  // if inline config should warn then add the warnings
1718
1759
  if (options.warnInlineConfig) {
1719
- sourceCode.getInlineConfigNodes().forEach(node => {
1720
- inlineConfigProblems.push(createLintingProblem({
1721
- ruleId: null,
1722
- message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
1723
- loc: node.loc,
1724
- severity: 1,
1725
- language: config.language
1726
- }));
1760
+ if (sourceCode.getInlineConfigNodes) {
1761
+ sourceCode.getInlineConfigNodes().forEach(node => {
1762
+ inlineConfigProblems.push(createLintingProblem({
1763
+ ruleId: null,
1764
+ message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
1765
+ loc: node.loc,
1766
+ severity: 1,
1767
+ language: config.language
1768
+ }));
1727
1769
 
1728
- });
1770
+ });
1771
+ }
1729
1772
  } else {
1730
- const inlineConfigResult = sourceCode.applyInlineConfig();
1731
-
1732
- inlineConfigProblems.push(
1733
- ...inlineConfigResult.problems
1734
- .map(problem => createLintingProblem({ ...problem, language: config.language }))
1735
- .map(problem => {
1736
- problem.fatal = true;
1737
- return problem;
1738
- })
1739
- );
1773
+ const inlineConfigResult = sourceCode.applyInlineConfig?.();
1774
+
1775
+ if (inlineConfigResult) {
1776
+ inlineConfigProblems.push(
1777
+ ...inlineConfigResult.problems
1778
+ .map(problem => createLintingProblem({ ...problem, language: config.language }))
1779
+ .map(problem => {
1780
+ problem.fatal = true;
1781
+ return problem;
1782
+ })
1783
+ );
1784
+
1785
+ // next we need to verify information about the specified rules
1786
+ const ruleValidator = new RuleValidator();
1787
+
1788
+ for (const { config: inlineConfig, loc } of inlineConfigResult.configs) {
1789
+
1790
+ Object.keys(inlineConfig.rules).forEach(ruleId => {
1791
+ const rule = getRuleFromConfig(ruleId, config);
1792
+ const ruleValue = inlineConfig.rules[ruleId];
1793
+
1794
+ if (!rule) {
1795
+ inlineConfigProblems.push(createLintingProblem({
1796
+ ruleId,
1797
+ loc,
1798
+ language: config.language
1799
+ }));
1800
+ return;
1801
+ }
1740
1802
 
1741
- // next we need to verify information about the specified rules
1742
- const ruleValidator = new RuleValidator();
1803
+ if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
1804
+ inlineConfigProblems.push(createLintingProblem({
1805
+ message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
1806
+ loc,
1807
+ language: config.language
1808
+ }));
1809
+ return;
1810
+ }
1743
1811
 
1744
- for (const { config: inlineConfig, node } of inlineConfigResult.configs) {
1812
+ try {
1745
1813
 
1746
- Object.keys(inlineConfig.rules).forEach(ruleId => {
1747
- const rule = getRuleFromConfig(ruleId, config);
1748
- const ruleValue = inlineConfig.rules[ruleId];
1814
+ let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1749
1815
 
1750
- if (!rule) {
1751
- inlineConfigProblems.push(createLintingProblem({
1752
- ruleId,
1753
- loc: node.loc,
1754
- language: config.language
1755
- }));
1756
- return;
1757
- }
1816
+ assertIsRuleSeverity(ruleId, ruleOptions[0]);
1758
1817
 
1759
- if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
1760
- inlineConfigProblems.push(createLintingProblem({
1761
- message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
1762
- loc: node.loc,
1763
- language: config.language
1764
- }));
1765
- return;
1766
- }
1818
+ /*
1819
+ * If the rule was already configured, inline rule configuration that
1820
+ * only has severity should retain options from the config and just override the severity.
1821
+ *
1822
+ * Example:
1823
+ *
1824
+ * {
1825
+ * rules: {
1826
+ * curly: ["error", "multi"]
1827
+ * }
1828
+ * }
1829
+ *
1830
+ * /* eslint curly: ["warn"] * /
1831
+ *
1832
+ * Results in:
1833
+ *
1834
+ * curly: ["warn", "multi"]
1835
+ */
1767
1836
 
1768
- try {
1837
+ let shouldValidateOptions = true;
1769
1838
 
1770
- let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
1839
+ if (
1771
1840
 
1772
- assertIsRuleSeverity(ruleId, ruleOptions[0]);
1841
+ /*
1842
+ * If inline config for the rule has only severity
1843
+ */
1844
+ ruleOptions.length === 1 &&
1773
1845
 
1774
- /*
1775
- * If the rule was already configured, inline rule configuration that
1776
- * only has severity should retain options from the config and just override the severity.
1777
- *
1778
- * Example:
1779
- *
1780
- * {
1781
- * rules: {
1782
- * curly: ["error", "multi"]
1783
- * }
1784
- * }
1785
- *
1786
- * /* eslint curly: ["warn"] * /
1787
- *
1788
- * Results in:
1789
- *
1790
- * curly: ["warn", "multi"]
1791
- */
1846
+ /*
1847
+ * And the rule was already configured
1848
+ */
1849
+ config.rules && Object.hasOwn(config.rules, ruleId)
1850
+ ) {
1792
1851
 
1793
- let shouldValidateOptions = true;
1852
+ /*
1853
+ * Then use severity from the inline config and options from the provided config
1854
+ */
1855
+ ruleOptions = [
1856
+ ruleOptions[0], // severity from the inline config
1857
+ ...config.rules[ruleId].slice(1) // options from the provided config
1858
+ ];
1794
1859
 
1795
- if (
1860
+ // if the rule was enabled, the options have already been validated
1861
+ if (config.rules[ruleId][0] > 0) {
1862
+ shouldValidateOptions = false;
1863
+ }
1864
+ }
1796
1865
 
1797
- /*
1798
- * If inline config for the rule has only severity
1799
- */
1800
- ruleOptions.length === 1 &&
1866
+ if (shouldValidateOptions) {
1867
+ ruleValidator.validate({
1868
+ plugins: config.plugins,
1869
+ rules: {
1870
+ [ruleId]: ruleOptions
1871
+ }
1872
+ });
1873
+ }
1801
1874
 
1802
- /*
1803
- * And the rule was already configured
1804
- */
1805
- config.rules && Object.hasOwn(config.rules, ruleId)
1806
- ) {
1875
+ mergedInlineConfig.rules[ruleId] = ruleOptions;
1876
+ } catch (err) {
1807
1877
 
1808
1878
  /*
1809
- * Then use severity from the inline config and options from the provided config
1879
+ * If the rule has invalid `meta.schema`, throw the error because
1880
+ * this is not an invalid inline configuration but an invalid rule.
1810
1881
  */
1811
- ruleOptions = [
1812
- ruleOptions[0], // severity from the inline config
1813
- ...config.rules[ruleId].slice(1) // options from the provided config
1814
- ];
1815
-
1816
- // if the rule was enabled, the options have already been validated
1817
- if (config.rules[ruleId][0] > 0) {
1818
- shouldValidateOptions = false;
1882
+ if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
1883
+ throw err;
1819
1884
  }
1820
- }
1821
1885
 
1822
- if (shouldValidateOptions) {
1823
- ruleValidator.validate({
1824
- plugins: config.plugins,
1825
- rules: {
1826
- [ruleId]: ruleOptions
1827
- }
1828
- });
1829
- }
1886
+ let baseMessage = err.message.slice(
1887
+ err.message.startsWith("Key \"rules\":")
1888
+ ? err.message.indexOf(":", 12) + 1
1889
+ : err.message.indexOf(":") + 1
1890
+ ).trim();
1830
1891
 
1831
- mergedInlineConfig.rules[ruleId] = ruleOptions;
1832
- } catch (err) {
1833
-
1834
- /*
1835
- * If the rule has invalid `meta.schema`, throw the error because
1836
- * this is not an invalid inline configuration but an invalid rule.
1837
- */
1838
- if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
1839
- throw err;
1840
- }
1841
-
1842
- let baseMessage = err.message.slice(
1843
- err.message.startsWith("Key \"rules\":")
1844
- ? err.message.indexOf(":", 12) + 1
1845
- : err.message.indexOf(":") + 1
1846
- ).trim();
1892
+ if (err.messageTemplate) {
1893
+ baseMessage += ` You passed "${ruleValue}".`;
1894
+ }
1847
1895
 
1848
- if (err.messageTemplate) {
1849
- baseMessage += ` You passed "${ruleValue}".`;
1896
+ inlineConfigProblems.push(createLintingProblem({
1897
+ ruleId,
1898
+ message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
1899
+ loc,
1900
+ language: config.language
1901
+ }));
1850
1902
  }
1851
-
1852
- inlineConfigProblems.push(createLintingProblem({
1853
- ruleId,
1854
- message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
1855
- loc: node.loc,
1856
- language: config.language
1857
- }));
1858
- }
1859
- });
1903
+ });
1904
+ }
1860
1905
  }
1861
1906
  }
1862
1907
  }
@@ -1873,7 +1918,7 @@ class Linter {
1873
1918
 
1874
1919
  let lintingProblems;
1875
1920
 
1876
- sourceCode.finalize();
1921
+ sourceCode.finalize?.();
1877
1922
 
1878
1923
  try {
1879
1924
  lintingProblems = runRules(
@@ -334,10 +334,8 @@ class NodeEventGenerator {
334
334
  * @returns {void}
335
335
  */
336
336
  enterNode(node) {
337
- if (node.parent) {
338
- this.currentAncestry.unshift(node.parent);
339
- }
340
337
  this.applySelectors(node, false);
338
+ this.currentAncestry.unshift(node);
341
339
  }
342
340
 
343
341
  /**
@@ -346,8 +344,8 @@ class NodeEventGenerator {
346
344
  * @returns {void}
347
345
  */
348
346
  leaveNode(node) {
349
- this.applySelectors(node, true);
350
347
  this.currentAncestry.shift();
348
+ this.applySelectors(node, true);
351
349
  }
352
350
  }
353
351
 
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //-----------------------------------------------------------------------------
9
+ // Type Definitions
10
+ //-----------------------------------------------------------------------------
11
+
12
+ /** @typedef {import("@eslint/core").File} File */
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Helpers
10
16
  //------------------------------------------------------------------------------
@@ -54,6 +60,7 @@ function stripUnicodeBOM(value) {
54
60
 
55
61
  /**
56
62
  * Represents a virtual file inside of ESLint.
63
+ * @implements {File}
57
64
  */
58
65
  class VFile {
59
66
 
package/lib/options.js CHANGED
@@ -30,6 +30,7 @@ const optionator = require("optionator");
30
30
  * @property {boolean} errorOnUnmatchedPattern Prevent errors when pattern is unmatched
31
31
  * @property {boolean} eslintrc Disable use of configuration from .eslintrc.*
32
32
  * @property {string[]} [ext] Specify JavaScript file extensions
33
+ * @property {string[]} [flag] Feature flags
33
34
  * @property {boolean} fix Automatically fix problems
34
35
  * @property {boolean} fixDryRun Automatically fix problems without saving the changes to the file system
35
36
  * @property {("directive" | "problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (directive, problem, suggestion, layout)
@@ -176,6 +177,16 @@ module.exports = function(usingFlatConfig) {
176
177
  };
177
178
  }
178
179
 
180
+ let flagFlag;
181
+
182
+ if (usingFlatConfig) {
183
+ flagFlag = {
184
+ option: "flag",
185
+ type: "[String]",
186
+ description: "Enable a feature flag"
187
+ };
188
+ }
189
+
179
190
  return optionator({
180
191
  prepend: "eslint [options] file.js [file.js] [dir]",
181
192
  defaults: {
@@ -424,7 +435,8 @@ module.exports = function(usingFlatConfig) {
424
435
  type: "path::String",
425
436
  description: "Print the configuration for the given file"
426
437
  },
427
- statsFlag
438
+ statsFlag,
439
+ flagFlag
428
440
  ].filter(value => !!value)
429
441
  });
430
442
  };
@@ -89,6 +89,9 @@ const arrayOfStringsOrObjectPatterns = {
89
89
  minItems: 1,
90
90
  uniqueItems: true
91
91
  },
92
+ regex: {
93
+ type: "string"
94
+ },
92
95
  importNamePattern: {
93
96
  type: "string"
94
97
  },
@@ -104,7 +107,6 @@ const arrayOfStringsOrObjectPatterns = {
104
107
  }
105
108
  },
106
109
  additionalProperties: false,
107
- required: ["group"],
108
110
  not: {
109
111
  anyOf: [
110
112
  { required: ["importNames", "allowImportNames"] },
@@ -113,7 +115,11 @@ const arrayOfStringsOrObjectPatterns = {
113
115
  { required: ["importNamePattern", "allowImportNames"] },
114
116
  { required: ["allowImportNames", "allowImportNamePattern"] }
115
117
  ]
116
- }
118
+ },
119
+ oneOf: [
120
+ { required: ["group"] },
121
+ { required: ["regex"] }
122
+ ]
117
123
  },
118
124
  uniqueItems: true
119
125
  }
@@ -235,9 +241,10 @@ module.exports = {
235
241
 
236
242
  // relative paths are supported for this rule
237
243
  const restrictedPatternGroups = restrictedPatterns.map(
238
- ({ group, message, caseSensitive, importNames, importNamePattern, allowImportNames, allowImportNamePattern }) => (
244
+ ({ group, regex, message, caseSensitive, importNames, importNamePattern, allowImportNames, allowImportNamePattern }) => (
239
245
  {
240
- matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
246
+ ...(group ? { matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group) } : {}),
247
+ ...(typeof regex === "string" ? { regexMatcher: new RegExp(regex, caseSensitive ? "u" : "iu") } : {}),
241
248
  customMessage: message,
242
249
  importNames,
243
250
  importNamePattern,
@@ -493,7 +500,7 @@ module.exports = {
493
500
  * @private
494
501
  */
495
502
  function isRestrictedPattern(importSource, group) {
496
- return group.matcher.ignores(importSource);
503
+ return group.regexMatcher ? group.regexMatcher.test(importSource) : group.matcher.ignores(importSource);
497
504
  }
498
505
 
499
506
  /**