eslint-plugin-flawless 1.0.0 → 1.2.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.
@@ -1,21 +1,20 @@
1
1
  import { createRequire } from "node:module";
2
- import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
2
+ import { ASTUtils, AST_NODE_TYPES, AST_TOKEN_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
3
3
  import { existsSync, readFileSync, statSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { createSyncFn } from "synckit";
7
7
  import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
8
8
  import { findVariable, isArrowToken, isOpeningParenToken } from "@typescript-eslint/utils/ast-utils";
9
- import { DefinitionType, ImplicitLibVariable, PatternVisitor, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
10
- import { requiresQuoting } from "@typescript-eslint/type-utils";
9
+ import ts9, { ScriptTarget, TypeFlags, isIdentifierPart, isIdentifierStart } from "typescript";
10
+ import { DefinitionType, ImplicitLibVariable, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
11
11
  import assert from "node:assert";
12
12
  import { getStaticJSONValue, parseForESLint } from "jsonc-eslint-parser";
13
13
  import * as core from "@eslint-react/core";
14
- import ts9 from "typescript";
15
14
  import { getStaticTOMLValue } from "toml-eslint-parser";
16
15
  //#region package.json
17
16
  var name = "eslint-plugin-flawless";
18
- var version = "1.0.0";
17
+ var version = "1.2.0";
19
18
  var repository = {
20
19
  "url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
21
20
  "type": "git"
@@ -85,7 +84,7 @@ function createFlawlessRule({ createOnce, ...meta }) {
85
84
  }
86
85
  //#endregion
87
86
  //#region src/rules/arrow-return-style/rule.ts
88
- const RULE_NAME$15 = "arrow-return-style";
87
+ const RULE_NAME$16 = "arrow-return-style";
89
88
  const IMPLICIT = "useImplicitReturn";
90
89
  const EXPLICIT = "useExplicitReturn";
91
90
  const COMPLEX_EXPLICIT = "useExplicitReturnComplex";
@@ -233,7 +232,7 @@ function collectArrows(root) {
233
232
  return arrows.sort((a, b) => a.range[0] - b.range[0]);
234
233
  }
235
234
  const arrowReturnStyle = createFlawlessRule({
236
- name: RULE_NAME$15,
235
+ name: RULE_NAME$16,
237
236
  createOnce(context) {
238
237
  let config = DEFAULTS$1;
239
238
  let sourceCode;
@@ -599,10 +598,10 @@ const arrowReturnStyle = createFlawlessRule({
599
598
  });
600
599
  //#endregion
601
600
  //#region src/rules/jsx-shorthand-boolean/rule.ts
602
- const RULE_NAME$14 = "jsx-shorthand-boolean";
601
+ const RULE_NAME$15 = "jsx-shorthand-boolean";
603
602
  const MESSAGE_ID$7 = "setAttributeValue";
604
- const messages$14 = { [MESSAGE_ID$7]: "Set an explicit value for boolean attribute '{{name}}'." };
605
- function createOnce$9(context) {
603
+ const messages$15 = { [MESSAGE_ID$7]: "Set an explicit value for boolean attribute '{{name}}'." };
604
+ function createOnce$10(context) {
606
605
  return { JSXAttribute(node) {
607
606
  if (node.value !== null) return;
608
607
  context.report({
@@ -614,8 +613,8 @@ function createOnce$9(context) {
614
613
  } };
615
614
  }
616
615
  const jsxShorthandBoolean = createFlawlessRule({
617
- name: RULE_NAME$14,
618
- createOnce: createOnce$9,
616
+ name: RULE_NAME$15,
617
+ createOnce: createOnce$10,
619
618
  defaultOptions: [],
620
619
  meta: {
621
620
  docs: {
@@ -625,19 +624,19 @@ const jsxShorthandBoolean = createFlawlessRule({
625
624
  },
626
625
  fixable: "code",
627
626
  hasSuggestions: false,
628
- messages: messages$14,
627
+ messages: messages$15,
629
628
  schema: [],
630
629
  type: "suggestion"
631
630
  }
632
631
  });
633
632
  //#endregion
634
633
  //#region src/rules/jsx-shorthand-fragment/rule.ts
635
- const RULE_NAME$13 = "jsx-shorthand-fragment";
634
+ const RULE_NAME$14 = "jsx-shorthand-fragment";
636
635
  const MESSAGE_ID_NAMED = "useNamedFragment";
637
636
  const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
638
637
  const DEFAULT_MODE = "syntax";
639
638
  const DEFAULT_FRAGMENT_NAME = "Fragment";
640
- const messages$13 = {
639
+ const messages$14 = {
641
640
  [MESSAGE_ID_NAMED]: "Use the '{{name}}' component instead of fragment shorthand syntax.",
642
641
  [MESSAGE_ID_SHORTHAND]: "Use the fragment shorthand syntax '<>...</>' instead of the '{{name}}' component."
643
642
  };
@@ -673,7 +672,7 @@ function jsxNameToString(node) {
673
672
  }
674
673
  return null;
675
674
  }
676
- function createOnce$8(context) {
675
+ function createOnce$9(context) {
677
676
  let mode;
678
677
  let fragmentName;
679
678
  let namedFragments;
@@ -720,8 +719,8 @@ function createOnce$8(context) {
720
719
  };
721
720
  }
722
721
  const jsxShorthandFragment = createFlawlessRule({
723
- name: RULE_NAME$13,
724
- createOnce: createOnce$8,
722
+ name: RULE_NAME$14,
723
+ createOnce: createOnce$9,
725
724
  defaultOptions: [{
726
725
  fragmentName: DEFAULT_FRAGMENT_NAME,
727
726
  mode: DEFAULT_MODE
@@ -738,7 +737,7 @@ const jsxShorthandFragment = createFlawlessRule({
738
737
  },
739
738
  fixable: "code",
740
739
  hasSuggestions: false,
741
- messages: messages$13,
740
+ messages: messages$14,
742
741
  schema: schema$3,
743
742
  type: "suggestion"
744
743
  }
@@ -878,7 +877,7 @@ function getOpeningParenOfParameters(node, sourceCode) {
878
877
  }
879
878
  //#endregion
880
879
  //#region src/rules/max-lines-per-function/rule.ts
881
- const RULE_NAME$12 = "max-lines-per-function";
880
+ const RULE_NAME$13 = "max-lines-per-function";
882
881
  const MESSAGE_ID_EXCEED = "exceed";
883
882
  /** A line that is empty or holds only whitespace. */
884
883
  const BLANK_LINE = /^\s*$/u;
@@ -889,7 +888,7 @@ const DEFAULTS = {
889
888
  skipBlankLines: false,
890
889
  skipComments: false
891
890
  };
892
- const messages$12 = { [MESSAGE_ID_EXCEED]: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}." };
891
+ const messages$13 = { [MESSAGE_ID_EXCEED]: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}." };
893
892
  const schema$2 = [{
894
893
  additionalProperties: false,
895
894
  properties: {
@@ -992,7 +991,7 @@ function getCountedLoc(countFrom, funcNode, reportNode) {
992
991
  * @param context - The rule context.
993
992
  * @returns The visitors measuring each function.
994
993
  */
995
- function createOnce$7(context) {
994
+ function createOnce$8(context) {
996
995
  let commentLineNumbers;
997
996
  let config;
998
997
  let lines;
@@ -1048,8 +1047,8 @@ function createOnce$7(context) {
1048
1047
  };
1049
1048
  }
1050
1049
  const maxLinesPerFunction = createFlawlessRule({
1051
- name: RULE_NAME$12,
1052
- createOnce: createOnce$7,
1050
+ name: RULE_NAME$13,
1051
+ createOnce: createOnce$8,
1053
1052
  defaultOptions: [DEFAULTS],
1054
1053
  meta: {
1055
1054
  defaultOptions: [DEFAULTS],
@@ -1059,7 +1058,7 @@ const maxLinesPerFunction = createFlawlessRule({
1059
1058
  requiresTypeChecking: false
1060
1059
  },
1061
1060
  hasSuggestions: false,
1062
- messages: messages$12,
1061
+ messages: messages$13,
1063
1062
  schema: schema$2,
1064
1063
  type: "suggestion"
1065
1064
  }
@@ -1539,6 +1538,77 @@ function isUsedVariable(variable) {
1539
1538
  });
1540
1539
  }
1541
1540
  //#endregion
1541
+ //#region src/rules/naming-convention/utils/contextual-type.ts
1542
+ /**
1543
+ * Determines if an object literal member's name is dictated by the contextual
1544
+ * type of the enclosing object literal (e.g. `{ ... } satisfies
1545
+ * Partial<Service>`). In that case the name is not the author's choice - it is
1546
+ * required by the declared type, which is itself validated at its declaration
1547
+ * site - so naming validation should be skipped.
1548
+ *
1549
+ * @param node - The non-computed object literal property or method node.
1550
+ * @param services - Parser services (may lack type information).
1551
+ * @param cache - Per-file cache of contextual types keyed by object literal.
1552
+ * @returns True if the member name is required by a contextual type.
1553
+ */
1554
+ function isDictatedByContextualType(node, services, cache) {
1555
+ if (!services.program || node.parent.type !== AST_NODE_TYPES.ObjectExpression) return false;
1556
+ const checker = services.program.getTypeChecker();
1557
+ const tsObject = services.esTreeNodeToTSNodeMap.get(node.parent);
1558
+ const candidates = getCandidateTypes(node.parent, tsObject, checker, cache);
1559
+ if (candidates === null) return false;
1560
+ const name = node.key.type === AST_NODE_TYPES.Identifier ? node.key.name : String(node.key.value);
1561
+ return candidates.some((candidate) => {
1562
+ const property = checker.getPropertyOfType(candidate, name);
1563
+ return property !== void 0 && !isDeclaredWithinLiteral(property, tsObject);
1564
+ });
1565
+ }
1566
+ /**
1567
+ * Resolves the contextual type of an object literal into its candidate
1568
+ * constituent types, cached per object literal.
1569
+ *
1570
+ * Union arms are checked individually because `getPropertyOfType` on a union
1571
+ * only finds properties present in every arm, while a name dictated by a
1572
+ * single arm still isn't the author's choice.
1573
+ *
1574
+ * @param objectNode - The enclosing object literal (ESTree).
1575
+ * @param tsObject - The corresponding TypeScript node.
1576
+ * @param checker - The type checker.
1577
+ * @param cache - Per-file cache of contextual types keyed by object literal.
1578
+ * @returns The candidate types, or null if there is no contextual type.
1579
+ */
1580
+ function getCandidateTypes(objectNode, tsObject, checker, cache) {
1581
+ const cached = cache.get(objectNode);
1582
+ if (cached !== void 0) return cached;
1583
+ const contextualType = checker.getContextualType(tsObject);
1584
+ let candidates = null;
1585
+ if (contextualType !== void 0) {
1586
+ const nonNullable = contextualType.getNonNullableType();
1587
+ candidates = (nonNullable.isUnion() ? nonNullable.types : [nonNullable]).map((arm) => checker.getApparentType(arm));
1588
+ }
1589
+ cache.set(objectNode, candidates);
1590
+ return candidates;
1591
+ }
1592
+ /**
1593
+ * Guards against self-inference: in generic calls like `identity({ Name: 1 })`
1594
+ * the contextual type is inferred from the literal itself, so the property
1595
+ * symbol's declarations all live inside the literal - the name is still the
1596
+ * author's choice. Transient symbols without declarations (such as those from
1597
+ * mapped types like `Partial<T>`) originate from the contextual type and count
1598
+ * as dictated.
1599
+ *
1600
+ * @param symbol - The property symbol found on a candidate contextual type.
1601
+ * @param literal - The object literal being checked.
1602
+ * @returns True if every declaration of the symbol lies inside the literal.
1603
+ */
1604
+ function isDeclaredWithinLiteral(symbol, literal) {
1605
+ const declarations = symbol.declarations ?? [];
1606
+ if (declarations.length === 0) return false;
1607
+ return declarations.every((declaration) => {
1608
+ return declaration.getSourceFile() === literal.getSourceFile() && declaration.pos >= literal.pos && declaration.end <= literal.end;
1609
+ });
1610
+ }
1611
+ //#endregion
1542
1612
  //#region src/rules/naming-convention/utils/enums.ts
1543
1613
  const Selector = {
1544
1614
  variable: 1,
@@ -1588,7 +1658,8 @@ const Modifier = {
1588
1658
  "override": 8192,
1589
1659
  "async": 16384,
1590
1660
  "default": 32768,
1591
- "namespace": 65536
1661
+ "namespace": 65536,
1662
+ "constAsserted": 131072
1592
1663
  };
1593
1664
  const PredefinedFormat = {
1594
1665
  camelCase: 1,
@@ -1600,13 +1671,15 @@ const PredefinedFormat = {
1600
1671
  };
1601
1672
  const PredefinedFormatValueToKey = Object.fromEntries(Object.entries(PredefinedFormat).map(([key, value]) => [value, key]));
1602
1673
  const TypeModifier = {
1603
- boolean: 131072,
1604
- string: 262144,
1605
- number: 524288,
1606
- function: 1048576,
1607
- array: 2097152
1674
+ boolean: 262144,
1675
+ string: 524288,
1676
+ number: 1048576,
1677
+ function: 2097152,
1678
+ array: 4194304
1608
1679
  };
1609
1680
  const TypeModifierValueToKey = Object.fromEntries(Object.entries(TypeModifier).map(([key, value]) => [value, key]));
1681
+ const TYPE_REFERENCE_LOOSE_WEIGHT = 1 << 23;
1682
+ const TYPE_REFERENCE_STRICT_WEIGHT = 1 << 24;
1610
1683
  const UnderscoreOption = {
1611
1684
  forbid: 1,
1612
1685
  allow: 2,
@@ -1701,7 +1774,7 @@ function createValidator(type, context, allConfigs) {
1701
1774
  if (!aIsMethodOrProperty && bIsMethodOrProperty) return 1;
1702
1775
  return b.selector - a.selector;
1703
1776
  });
1704
- return (node, modifiers = /* @__PURE__ */ new Set()) => {
1777
+ return (node, modifiers = /* @__PURE__ */ new Set(), isObjectStyleEnumName = false) => {
1705
1778
  const originalName = node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`;
1706
1779
  for (const config of configs) {
1707
1780
  if (config.filter?.regex.test(originalName) !== config.filter?.match) continue;
@@ -1711,6 +1784,7 @@ function createValidator(type, context, allConfigs) {
1711
1784
  name = validateUnderscore({
1712
1785
  name,
1713
1786
  config,
1787
+ isObjectStyleEnumName,
1714
1788
  node,
1715
1789
  originalName,
1716
1790
  position: "leading"
@@ -1719,6 +1793,7 @@ function createValidator(type, context, allConfigs) {
1719
1793
  name = validateUnderscore({
1720
1794
  name,
1721
1795
  config,
1796
+ isObjectStyleEnumName,
1722
1797
  node,
1723
1798
  originalName,
1724
1799
  position: "trailing"
@@ -1727,6 +1802,7 @@ function createValidator(type, context, allConfigs) {
1727
1802
  name = validateAffix({
1728
1803
  name,
1729
1804
  config,
1805
+ isObjectStyleEnumName,
1730
1806
  node,
1731
1807
  originalName,
1732
1808
  position: "prefix"
@@ -1735,6 +1811,7 @@ function createValidator(type, context, allConfigs) {
1735
1811
  name = validateAffix({
1736
1812
  name,
1737
1813
  config,
1814
+ isObjectStyleEnumName,
1738
1815
  node,
1739
1816
  originalName,
1740
1817
  position: "suffix"
@@ -1743,12 +1820,14 @@ function createValidator(type, context, allConfigs) {
1743
1820
  if (!validateCustom({
1744
1821
  name,
1745
1822
  config,
1823
+ isObjectStyleEnumName,
1746
1824
  node,
1747
1825
  originalName
1748
1826
  })) return;
1749
1827
  if (!validatePredefinedFormat({
1750
1828
  name,
1751
1829
  config,
1830
+ isObjectStyleEnumName,
1752
1831
  modifiers,
1753
1832
  node,
1754
1833
  originalName
@@ -1772,7 +1851,7 @@ function createValidator(type, context, allConfigs) {
1772
1851
  type: selectorTypeToMessageString(type)
1773
1852
  };
1774
1853
  }
1775
- function validateUnderscore({ name, config, node, originalName, position }) {
1854
+ function validateUnderscore({ name, config, isObjectStyleEnumName, node, originalName, position }) {
1776
1855
  const option = position === "leading" ? config.leadingUnderscore : config.trailingUnderscore;
1777
1856
  if (!option) return name;
1778
1857
  const hasSingleUnderscore = position === "leading" ? () => name.startsWith("_") : () => name.endsWith("_");
@@ -1798,7 +1877,7 @@ function createValidator(type, context, allConfigs) {
1798
1877
  originalName,
1799
1878
  position
1800
1879
  }),
1801
- messageId: "unexpectedUnderscore",
1880
+ messageId: pickMessageId("unexpectedUnderscore", isObjectStyleEnumName),
1802
1881
  node
1803
1882
  });
1804
1883
  return;
@@ -1812,7 +1891,7 @@ function createValidator(type, context, allConfigs) {
1812
1891
  originalName,
1813
1892
  position
1814
1893
  }),
1815
- messageId: "missingUnderscore",
1894
+ messageId: pickMessageId("missingUnderscore", isObjectStyleEnumName),
1816
1895
  node
1817
1896
  });
1818
1897
  return;
@@ -1826,7 +1905,7 @@ function createValidator(type, context, allConfigs) {
1826
1905
  originalName,
1827
1906
  position
1828
1907
  }),
1829
- messageId: "missingUnderscore",
1908
+ messageId: pickMessageId("missingUnderscore", isObjectStyleEnumName),
1830
1909
  node
1831
1910
  });
1832
1911
  return;
@@ -1834,7 +1913,7 @@ function createValidator(type, context, allConfigs) {
1834
1913
  return trimDoubleUnderscore();
1835
1914
  }
1836
1915
  }
1837
- function validateAffix({ name, config, node, originalName, position }) {
1916
+ function validateAffix({ name, config, isObjectStyleEnumName, node, originalName, position }) {
1838
1917
  const affixes = config[position];
1839
1918
  if (!affixes || affixes.length === 0) return name;
1840
1919
  for (const affix of affixes) {
@@ -1848,11 +1927,11 @@ function createValidator(type, context, allConfigs) {
1848
1927
  originalName,
1849
1928
  position
1850
1929
  }),
1851
- messageId: "missingAffix",
1930
+ messageId: pickMessageId("missingAffix", isObjectStyleEnumName),
1852
1931
  node
1853
1932
  });
1854
1933
  }
1855
- function validateCustom({ name, config, node, originalName }) {
1934
+ function validateCustom({ name, config, isObjectStyleEnumName, node, originalName }) {
1856
1935
  const { custom } = config;
1857
1936
  if (!custom) return true;
1858
1937
  const result = custom.regex.test(name);
@@ -1863,12 +1942,12 @@ function createValidator(type, context, allConfigs) {
1863
1942
  custom,
1864
1943
  originalName
1865
1944
  }),
1866
- messageId: "satisfyCustom",
1945
+ messageId: pickMessageId("satisfyCustom", isObjectStyleEnumName),
1867
1946
  node
1868
1947
  });
1869
1948
  return false;
1870
1949
  }
1871
- function validatePredefinedFormat({ name, config, modifiers, node, originalName }) {
1950
+ function validatePredefinedFormat({ name, config, isObjectStyleEnumName, modifiers, node, originalName }) {
1872
1951
  const formats = config.format;
1873
1952
  if (!formats || formats.length === 0) return true;
1874
1953
  if (!modifiers.has(Modifier.requiresQuotes)) for (const format of formats) {
@@ -1881,37 +1960,150 @@ function createValidator(type, context, allConfigs) {
1881
1960
  originalName,
1882
1961
  processedName: name
1883
1962
  }),
1884
- messageId: originalName === name ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed",
1963
+ messageId: pickMessageId(originalName === name ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed", isObjectStyleEnumName),
1885
1964
  node
1886
1965
  });
1887
1966
  return false;
1888
1967
  }
1889
1968
  }
1890
- const SelectorsAllowedToHaveTypes = Selector.variable | Selector.parameter | Selector.classProperty | Selector.objectLiteralProperty | Selector.typeProperty | Selector.parameterProperty | Selector.classicAccessor;
1969
+ /**
1970
+ * Picks the `*ForeignContract` variant of a message id when the name belongs
1971
+ * to an `objectStyleEnum` - a plain object literal, not a real enum, so the
1972
+ * violation message should point at the `satisfies` escape instead of a
1973
+ * rename.
1974
+ *
1975
+ * @param baseMessageId - The base message id for the violation kind.
1976
+ * @param isObjectStyleEnumName - Whether the name is an objectStyleEnum
1977
+ * container or key.
1978
+ * @returns The message id to report.
1979
+ */
1980
+ function pickMessageId(baseMessageId, isObjectStyleEnumName) {
1981
+ return isObjectStyleEnumName ? `${baseMessageId}ForeignContract` : baseMessageId;
1982
+ }
1983
+ const SelectorsAllowedToHaveTypes = Selector.variable | Selector.function | Selector.parameter | Selector.classProperty | Selector.objectLiteralProperty | Selector.typeProperty | Selector.parameterProperty | Selector.classicAccessor | Selector.classMethod | Selector.objectLiteralMethod | Selector.typeMethod;
1891
1984
  function isAllTypesMatch(type, callback) {
1892
1985
  if (type.isUnion()) return type.types.every((inner) => callback(inner));
1893
1986
  return callback(type);
1894
1987
  }
1988
+ function isAnyType(type) {
1989
+ return (type.flags & TypeFlags.Any) !== 0;
1990
+ }
1991
+ function symbolMatchesTypeReference(symbol, reference) {
1992
+ if (symbol === void 0 || reference.name === void 0 || symbol.name !== reference.name) return false;
1993
+ if (reference.from === void 0) return true;
1994
+ const { declarations } = symbol;
1995
+ if (!declarations || declarations.length === 0) return false;
1996
+ for (const declaration of declarations) {
1997
+ const { fileName } = declaration.getSourceFile();
1998
+ if (moduleSpecifierMatches(fileName, reference.from)) return true;
1999
+ }
2000
+ return false;
2001
+ }
2002
+ function matchesNamedTypeReference(type, reference) {
2003
+ if (isAnyType(type)) return false;
2004
+ if (symbolMatchesTypeReference(type.aliasSymbol, reference)) return true;
2005
+ if (symbolMatchesTypeReference(type.symbol, reference)) return true;
2006
+ if (type.isIntersection() || type.isUnion()) {
2007
+ for (const inner of type.types) if (matchesNamedTypeReference(inner, reference)) return true;
2008
+ }
2009
+ return false;
2010
+ }
2011
+ function matchesReturnTypeReference(type, reference) {
2012
+ if (isAnyType(type)) return false;
2013
+ for (const signature of type.getCallSignatures()) if (matchesTypeReference(signature.getReturnType(), reference)) return true;
2014
+ if (type.isIntersection() || type.isUnion()) {
2015
+ for (const inner of type.types) if (matchesReturnTypeReference(inner, reference)) return true;
2016
+ }
2017
+ return false;
2018
+ }
2019
+ function matchesTypeReference(type, reference) {
2020
+ if (isAnyType(type)) return false;
2021
+ if (reference.name === void 0 && reference.returns === void 0) return false;
2022
+ if (reference.name !== void 0 && !matchesNamedTypeReference(type, reference)) return false;
2023
+ if (reference.returns !== void 0 && !matchesReturnTypeReference(type, reference.returns)) return false;
2024
+ return true;
2025
+ }
1895
2026
  function isCorrectType(node, config, context, selector) {
1896
2027
  if (config.types === void 0) return true;
1897
2028
  if ((SelectorsAllowedToHaveTypes & selector) === 0) return true;
1898
2029
  const services = getParserServices(context);
1899
2030
  const checker = services.program.getTypeChecker();
1900
2031
  const type = services.getTypeAtLocation(node).getNonNullableType();
1901
- for (const allowedType of config.types) switch (allowedType) {
1902
- case TypeModifier.array:
1903
- if (isAllTypesMatch(type, (inner) => checker.isArrayType(inner) || checker.isTupleType(inner))) return true;
1904
- break;
1905
- case TypeModifier.boolean:
1906
- case TypeModifier.number:
1907
- case TypeModifier.string:
1908
- if (checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(type))) === TypeModifierValueToKey[allowedType]) return true;
1909
- break;
1910
- case TypeModifier.function:
1911
- if (isAllTypesMatch(type, (inner) => inner.getCallSignatures().length > 0)) return true;
1912
- break;
2032
+ const predicates = [];
2033
+ for (const allowedType of config.types) {
2034
+ if (typeof allowedType === "object") {
2035
+ predicates.push((inner) => matchesTypeReference(inner, allowedType));
2036
+ continue;
2037
+ }
2038
+ switch (allowedType) {
2039
+ case TypeModifier.array:
2040
+ predicates.push((inner) => checker.isArrayType(inner) || checker.isTupleType(inner));
2041
+ break;
2042
+ case TypeModifier.boolean:
2043
+ case TypeModifier.number:
2044
+ case TypeModifier.string:
2045
+ predicates.push((inner) => {
2046
+ return checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(inner))) === TypeModifierValueToKey[allowedType];
2047
+ });
2048
+ break;
2049
+ case TypeModifier.function:
2050
+ predicates.push((inner) => inner.getCallSignatures().length > 0);
2051
+ break;
2052
+ }
1913
2053
  }
1914
- return false;
2054
+ return isAllTypesMatch(type, (inner) => predicates.some((predicate) => predicate(inner)));
2055
+ }
2056
+ const BACKSLASH_PATTERN = /\\/gu;
2057
+ const TYPESCRIPT_EXTENSION_PATTERN = /\.d\.ts$|\.tsx?$/u;
2058
+ const WINDOWS_DRIVE_PATTERN = /^[A-Za-z]:\//u;
2059
+ const LEADING_DOT_OR_SLASH_PATTERN = /^(\.\/|\/)/u;
2060
+ /**
2061
+ * Path-form specifiers start with `.`, `/`, or a Windows drive letter (e.g.
2062
+ * `C:/`).
2063
+ *
2064
+ * @param specifier - The module specifier to classify.
2065
+ * @returns True if the specifier should be treated as a filesystem path rather
2066
+ * than a package name.
2067
+ */
2068
+ function looksLikePath(specifier) {
2069
+ if (specifier.startsWith(".")) return true;
2070
+ if (specifier.startsWith("/")) return true;
2071
+ return WINDOWS_DRIVE_PATTERN.test(specifier);
2072
+ }
2073
+ /**
2074
+ * Checks whether a declaration's source file matches a module specifier.
2075
+ *
2076
+ * Two specifier shapes are supported:
2077
+ *
2078
+ * 1. **Bare package specifier** (e.g. `"@rbxts/jecs"`, `"lodash"`) — matches
2079
+ * when the declaration's file path contains `/node_modules/<specifier>/` as
2080
+ * a substring. Handles flat and pnpm-style layouts (pnpm paths still contain
2081
+ * a final `/node_modules/<specifier>/` segment after the virtual store
2082
+ * directory). **Not** supported: Yarn Plug'n'Play (no `node_modules` on
2083
+ * disk), vendored packages outside `node_modules`, or types provided by
2084
+ * separate `@types/*` packages.
2085
+ * 2. **Path specifier** (starts with `.`, `/`, or a Windows drive letter) —
2086
+ * matches against the normalized declaration path with `.d.ts` / `.tsx?`
2087
+ * stripped. Windows absolute paths require exact equality. POSIX-style
2088
+ * absolute or relative paths are normalized to a bare tail and matched as a
2089
+ * suffix; this means `"./shared/network"` matches a declaration at
2090
+ * `<root>/shared/network.ts`.
2091
+ *
2092
+ * @param declarationFile - Path of the file declaring the matched symbol.
2093
+ * @param specifier - The `from` module specifier from the rule config.
2094
+ * @returns True if the declaration file matches the specifier.
2095
+ */
2096
+ function moduleSpecifierMatches(declarationFile, specifier) {
2097
+ const normalizedFile = declarationFile.replace(BACKSLASH_PATTERN, "/");
2098
+ const normalizedSpecifier = specifier.replace(BACKSLASH_PATTERN, "/");
2099
+ if (looksLikePath(normalizedSpecifier)) {
2100
+ const stripped = normalizedFile.replace(TYPESCRIPT_EXTENSION_PATTERN, "");
2101
+ if (WINDOWS_DRIVE_PATTERN.test(normalizedSpecifier)) return stripped === normalizedSpecifier;
2102
+ const tail = normalizedSpecifier.replace(LEADING_DOT_OR_SLASH_PATTERN, "");
2103
+ if (stripped === tail) return true;
2104
+ return stripped.endsWith(`/${tail}`);
2105
+ }
2106
+ return normalizedFile.includes(`/node_modules/${normalizedSpecifier}/`);
1915
2107
  }
1916
2108
  //#endregion
1917
2109
  //#region src/rules/naming-convention/utils/parse-options.ts
@@ -1919,10 +2111,22 @@ function parseOptions(context) {
1919
2111
  const normalizedOptions = context.options.flatMap(normalizeOption);
1920
2112
  return Object.fromEntries(Object.keys(Selector).map((key) => [key, createValidator(key, context, normalizedOptions)]));
1921
2113
  }
2114
+ /**
2115
+ * A type-reference matcher counts as "strict" for sorting when any level of it
2116
+ * (including nested `returns` matchers) constrains the declaring module.
2117
+ *
2118
+ * @param reference - The matcher to inspect.
2119
+ * @returns True if the matcher carries a `from` constraint at any depth.
2120
+ */
2121
+ function hasFromConstraint(reference) {
2122
+ if (reference.from !== void 0) return true;
2123
+ return reference.returns !== void 0 && hasFromConstraint(reference.returns);
2124
+ }
1922
2125
  function normalizeOption(option) {
1923
2126
  let weight = 0;
1924
2127
  if (option.modifiers) for (const modifier of option.modifiers) weight |= Modifier[modifier];
1925
- if (option.types) for (const type of option.types) weight |= TypeModifier[type];
2128
+ if (option.types) for (const type of option.types) if (typeof type === "string") weight |= TypeModifier[type];
2129
+ else weight |= hasFromConstraint(type) ? TYPE_REFERENCE_STRICT_WEIGHT : TYPE_REFERENCE_LOOSE_WEIGHT;
1926
2130
  if (option.filter !== void 0) weight |= 1 << 30;
1927
2131
  const normalizedOption = {
1928
2132
  custom: option.custom ? {
@@ -1943,7 +2147,7 @@ function normalizeOption(option) {
1943
2147
  prefix: option.prefix && option.prefix.length > 0 ? option.prefix : void 0,
1944
2148
  suffix: option.suffix && option.suffix.length > 0 ? option.suffix : void 0,
1945
2149
  trailingUnderscore: option.trailingUnderscore !== void 0 ? UnderscoreOption[option.trailingUnderscore] : void 0,
1946
- types: option.types?.map((type) => TypeModifier[type]) ?? void 0
2150
+ types: option.types?.map((type) => typeof type === "string" ? TypeModifier[type] : type) ?? void 0
1947
2151
  };
1948
2152
  return (Array.isArray(option.selector) ? option.selector : [option.selector]).map((selector) => {
1949
2153
  return {
@@ -1981,9 +2185,39 @@ const $DEFS = {
1981
2185
  },
1982
2186
  type: "array"
1983
2187
  },
1984
- typeModifiers: {
2188
+ typeMatcher: { oneOf: [{
2189
+ description: "Built-in type modifier matching by widened TS type.",
1985
2190
  enum: Object.keys(TypeModifier),
1986
2191
  type: "string"
2192
+ }, { $ref: "#/$defs/typeReferenceMatcher" }] },
2193
+ typeReferenceMatcher: {
2194
+ additionalProperties: false,
2195
+ anyOf: [{
2196
+ required: ["name"],
2197
+ type: "object"
2198
+ }, {
2199
+ required: ["returns"],
2200
+ type: "object"
2201
+ }],
2202
+ dependencies: { from: ["name"] },
2203
+ description: "Type-reference matcher. Matches when the variable's type resolves to a symbol with the given `name`; if `from` is supplied, the symbol's declaration must also live in a file the specifier resolves to. `returns` instead (or additionally) matches callable types by the return type of their call signatures.",
2204
+ properties: {
2205
+ name: {
2206
+ description: "Symbol name to match (compared against the type's `aliasSymbol` then `symbol`).",
2207
+ minLength: 1,
2208
+ type: "string"
2209
+ },
2210
+ from: {
2211
+ description: "Module specifier the type must originate from. Bare package name (e.g. `@rbxts/jecs`) matches `/node_modules/<from>/` in the declaration path. Path-form (starts with `.`, `/`, or a Windows drive letter) matches the declaration path with extension stripped. Omit to match any source.",
2212
+ minLength: 1,
2213
+ type: "string"
2214
+ },
2215
+ returns: {
2216
+ $ref: "#/$defs/typeReferenceMatcher",
2217
+ description: "Nested matcher applied to call-signature return types. The type matches when at least one call signature's return type satisfies it. Lets anonymous function types (which have no symbol name) be matched, e.g. React components typed `(props: P) => ReactNode`."
2218
+ }
2219
+ },
2220
+ type: "object"
1987
2221
  },
1988
2222
  underscoreOptions: {
1989
2223
  enum: Object.keys(UnderscoreOption),
@@ -2023,7 +2257,7 @@ function selectorSchema(selectorString, allowType, modifiers) {
2023
2257
  };
2024
2258
  if (allowType) selector["types"] = {
2025
2259
  additionalItems: false,
2026
- items: { $ref: "#/$defs/typeModifiers" },
2260
+ items: { $ref: "#/$defs/typeMatcher" },
2027
2261
  type: "array"
2028
2262
  };
2029
2263
  return [{
@@ -2065,7 +2299,7 @@ function selectorsSchema() {
2065
2299
  },
2066
2300
  types: {
2067
2301
  additionalItems: false,
2068
- items: { $ref: "#/$defs/typeModifiers" },
2302
+ items: { $ref: "#/$defs/typeMatcher" },
2069
2303
  type: "array"
2070
2304
  }
2071
2305
  },
@@ -2082,13 +2316,14 @@ const SCHEMA = {
2082
2316
  ...selectorSchema("variableLike", false, ["unused", "async"]),
2083
2317
  ...selectorSchema("variable", true, [
2084
2318
  "const",
2319
+ "constAsserted",
2085
2320
  "destructured",
2086
2321
  "exported",
2087
2322
  "global",
2088
2323
  "unused",
2089
2324
  "async"
2090
2325
  ]),
2091
- ...selectorSchema("function", false, [
2326
+ ...selectorSchema("function", true, [
2092
2327
  "exported",
2093
2328
  "global",
2094
2329
  "unused",
@@ -2148,7 +2383,7 @@ const SCHEMA = {
2148
2383
  "override",
2149
2384
  "async"
2150
2385
  ]),
2151
- ...selectorSchema("classMethod", false, [
2386
+ ...selectorSchema("classMethod", true, [
2152
2387
  "abstract",
2153
2388
  "private",
2154
2389
  "#private",
@@ -2159,13 +2394,13 @@ const SCHEMA = {
2159
2394
  "override",
2160
2395
  "async"
2161
2396
  ]),
2162
- ...selectorSchema("objectLiteralMethod", false, [
2397
+ ...selectorSchema("objectLiteralMethod", true, [
2163
2398
  "public",
2164
2399
  "requiresQuotes",
2165
2400
  "async"
2166
2401
  ]),
2167
- ...selectorSchema("typeMethod", false, ["public", "requiresQuotes"]),
2168
- ...selectorSchema("method", false, [
2402
+ ...selectorSchema("typeMethod", true, ["public", "requiresQuotes"]),
2403
+ ...selectorSchema("method", true, [
2169
2404
  "abstract",
2170
2405
  "private",
2171
2406
  "#private",
@@ -2224,14 +2459,21 @@ const SCHEMA = {
2224
2459
  };
2225
2460
  //#endregion
2226
2461
  //#region src/rules/naming-convention/rule.ts
2227
- const RULE_NAME$11 = "naming-convention";
2228
- const messages$11 = {
2462
+ const RULE_NAME$12 = "naming-convention";
2463
+ const FOREIGN_CONTRACT_HINT = " If this is data conforming to an external shape, declare it with `satisfies` instead.";
2464
+ const messages$12 = {
2229
2465
  doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
2466
+ doesNotMatchFormatForeignContract: `{{type}} name \`{{name}}\` must match one of the following formats: {{formats}}${FOREIGN_CONTRACT_HINT}`,
2230
2467
  doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
2468
+ doesNotMatchFormatTrimmedForeignContract: `{{type}} name \`{{name}}\` trimmed as \`{{processedName}}\` must match one of the following formats: {{formats}}${FOREIGN_CONTRACT_HINT}`,
2231
2469
  missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
2470
+ missingAffixForeignContract: `{{type}} name \`{{name}}\` must have one of the following {{position}}es: {{affixes}}${FOREIGN_CONTRACT_HINT}`,
2232
2471
  missingUnderscore: "{{type}} name `{{name}}` must have {{count}} {{position}} underscore(s).",
2472
+ missingUnderscoreForeignContract: `{{type}} name \`{{name}}\` must have {{count}} {{position}} underscore(s).${FOREIGN_CONTRACT_HINT}`,
2233
2473
  satisfyCustom: "{{type}} name `{{name}}` must {{regexMatch}} the RegExp: {{regex}}",
2234
- unexpectedUnderscore: "{{type}} name `{{name}}` must not have a {{position}} underscore."
2474
+ satisfyCustomForeignContract: `{{type}} name \`{{name}}\` must {{regexMatch}} the RegExp: {{regex}}${FOREIGN_CONTRACT_HINT}`,
2475
+ unexpectedUnderscore: "{{type}} name `{{name}}` must not have a {{position}} underscore.",
2476
+ unexpectedUnderscoreForeignContract: `{{type}} name \`{{name}}\` must not have a {{position}} underscore.${FOREIGN_CONTRACT_HINT}`
2235
2477
  };
2236
2478
  const camelCaseNamingConfig = [
2237
2479
  {
@@ -2258,22 +2500,13 @@ const camelCaseNamingConfig = [
2258
2500
  function create$4(contextWithoutDefaults) {
2259
2501
  const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
2260
2502
  const validators = parseOptions(context);
2261
- const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
2503
+ const services = getParserServices(context, true);
2504
+ const compilerOptions = services.program?.getCompilerOptions() ?? {};
2505
+ const contextualTypeCache = /* @__PURE__ */ new WeakMap();
2262
2506
  function handleMember(validator, { key }, modifiers) {
2263
- if (requiresQuoting$1(key, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2507
+ if (requiresQuoting(key, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2264
2508
  validator(key, modifiers);
2265
2509
  }
2266
- function getMemberModifiers(node) {
2267
- const modifiers = /* @__PURE__ */ new Set();
2268
- if ("key" in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) modifiers.add(Modifier["#private"]);
2269
- else if (node.accessibility) modifiers.add(Modifier[node.accessibility]);
2270
- else modifiers.add(Modifier.public);
2271
- if (node.static) modifiers.add(Modifier.static);
2272
- if ("readonly" in node && node.readonly) modifiers.add(Modifier.readonly);
2273
- if ("override" in node && node.override) modifiers.add(Modifier.override);
2274
- if (node.type === AST_NODE_TYPES.TSAbstractPropertyDefinition || node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || node.type === AST_NODE_TYPES.TSAbstractAccessorProperty) modifiers.add(Modifier.abstract);
2275
- return modifiers;
2276
- }
2277
2510
  const { unusedVariables } = collectVariables(context);
2278
2511
  function isUnused(name, initialScope) {
2279
2512
  let variable = null;
@@ -2286,52 +2519,6 @@ function create$4(contextWithoutDefaults) {
2286
2519
  if (!variable) return false;
2287
2520
  return unusedVariables.has(variable);
2288
2521
  }
2289
- function isDestructured(id) {
2290
- return id.parent.type === AST_NODE_TYPES.Property && id.parent.shorthand || id.parent.type === AST_NODE_TYPES.AssignmentPattern && id.parent.parent.type === AST_NODE_TYPES.Property && id.parent.parent.shorthand;
2291
- }
2292
- function isAsyncMemberOrProperty(propertyOrMemberNode) {
2293
- return Boolean("value" in propertyOrMemberNode && propertyOrMemberNode.value && "async" in propertyOrMemberNode.value && propertyOrMemberNode.value.async);
2294
- }
2295
- function isAsyncVariableIdentifier(id) {
2296
- return Boolean("async" in id.parent && id.parent.async || "init" in id.parent && id.parent.init && "async" in id.parent.init && id.parent.init.async);
2297
- }
2298
- /**
2299
- * Determines if a VariableDeclarator represents an object-style enum declaration.
2300
- *
2301
- * Object-style enums are const assertions applied to object expressions, commonly
2302
- * used as an alternative to TypeScript enums. Examples:
2303
- * - `const Colors = { RED: 'red', BLUE: 'blue' } as const`
2304
- * - `const Status = <const>{ OK: 200, ERROR: 500 }`.
2305
- *
2306
- * @param node - The VariableDeclarator AST node to check.
2307
- * @param parent - The parent VariableDeclaration node.
2308
- * @returns True if this represents an object-style enum declaration.
2309
- */
2310
- function isObjectStyleEnumDeclaration(node, parent) {
2311
- if (parent.kind !== "const") return false;
2312
- if (!node.init) return false;
2313
- if (node.init.type === AST_NODE_TYPES.TSAsExpression && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
2314
- if (node.init.type === AST_NODE_TYPES.TSTypeAssertion && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
2315
- return false;
2316
- }
2317
- /**
2318
- * Checks if a method name exists in any of the implemented interfaces.
2319
- *
2320
- * @param implementsList - List of implemented interfaces.
2321
- * @param methodName - Name of the method to check.
2322
- * @param checker - TypeScript type checker.
2323
- * @param services - Parser services.
2324
- * @returns True if the method name is found in any interface.
2325
- */
2326
- function checkInterfacesForMethod(implementsList, methodName, checker, services) {
2327
- for (const implementsClause of implementsList) {
2328
- const interfaceType = checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(implementsClause));
2329
- if (!interfaceType.getSymbol()) continue;
2330
- const interfaceMembers = checker.getPropertiesOfType(interfaceType);
2331
- for (const member of interfaceMembers) if (member.name === methodName) return true;
2332
- }
2333
- return false;
2334
- }
2335
2522
  /**
2336
2523
  * Determines if a class method is implementing an interface method.
2337
2524
  *
@@ -2339,7 +2526,6 @@ function create$4(contextWithoutDefaults) {
2339
2526
  * @returns True if the method is implementing an interface method.
2340
2527
  */
2341
2528
  function isImplementingInterfaceMethod(node) {
2342
- const services = getParserServices(context, true);
2343
2529
  if (!services.program) return false;
2344
2530
  const checker = services.program.getTypeChecker();
2345
2531
  let parentClass = null;
@@ -2367,12 +2553,20 @@ function create$4(contextWithoutDefaults) {
2367
2553
  },
2368
2554
  ":not(ObjectPattern) > Property[computed = false][kind = \"init\"][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
2369
2555
  handler: (node, validator) => {
2556
+ if (isDictatedByContextualType(node, services, contextualTypeCache)) return;
2557
+ if (isObjectStyleEnumKey(node)) {
2558
+ const enumModifiers = /* @__PURE__ */ new Set();
2559
+ if (requiresQuoting(node.key, compilerOptions.target)) enumModifiers.add(Modifier.requiresQuotes);
2560
+ validators.enumMember(node.key, enumModifiers, true);
2561
+ return;
2562
+ }
2370
2563
  handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
2371
2564
  },
2372
2565
  validator: validators.objectLiteralProperty
2373
2566
  },
2374
2567
  [["TSMethodSignature[computed = false]", "TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type = \"TSFunctionType\"]"].join(", ")]: {
2375
2568
  handler: (node, validator) => {
2569
+ if (isExternalMember(node, context.sourceCode)) return;
2376
2570
  handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
2377
2571
  },
2378
2572
  validator: validators.typeMethod
@@ -2403,6 +2597,7 @@ function create$4(contextWithoutDefaults) {
2403
2597
  "Property[computed = false][kind = \"init\"][value.type = \"TSEmptyBodyFunctionExpression\"]"
2404
2598
  ].join(", ")]: {
2405
2599
  handler: (node, validator) => {
2600
+ if (node.type === AST_NODE_TYPES.Property && isDictatedByContextualType(node, services, contextualTypeCache)) return;
2406
2601
  const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
2407
2602
  if (isAsyncMemberOrProperty(node)) modifiers.add(Modifier.async);
2408
2603
  handleMember(validator, node, modifiers);
@@ -2494,7 +2689,7 @@ function create$4(contextWithoutDefaults) {
2494
2689
  "TSEnumMember": {
2495
2690
  handler: ({ id }, validator) => {
2496
2691
  const modifiers = /* @__PURE__ */ new Set();
2497
- if (requiresQuoting$1(id, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2692
+ if (requiresQuoting(id, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2498
2693
  validator(id, modifiers);
2499
2694
  },
2500
2695
  validator: validators.enumMember
@@ -2519,6 +2714,7 @@ function create$4(contextWithoutDefaults) {
2519
2714
  },
2520
2715
  "TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type != \"TSFunctionType\"]": {
2521
2716
  handler: (node, validator) => {
2717
+ if (isExternalMember(node, context.sourceCode)) return;
2522
2718
  const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
2523
2719
  if (node.readonly) modifiers.add(Modifier.readonly);
2524
2720
  handleMember(validator, node, modifiers);
@@ -2548,9 +2744,10 @@ function create$4(contextWithoutDefaults) {
2548
2744
  handler: (node, validator) => {
2549
2745
  const identifiers = getIdentifiersFromPattern(node.id);
2550
2746
  const baseModifiers = /* @__PURE__ */ new Set();
2551
- const { parent } = node;
2747
+ const { init, parent } = node;
2552
2748
  if (parent.kind === "const") baseModifiers.add(Modifier.const);
2553
2749
  if (isGlobal(context.sourceCode.getScope(node))) baseModifiers.add(Modifier.global);
2750
+ if (isConstAssertedInitializer(init)) baseModifiers.add(Modifier.constAsserted);
2554
2751
  const isObjectStyleEnum = isObjectStyleEnumDeclaration(node, parent);
2555
2752
  for (const id of identifiers) {
2556
2753
  const modifiers = new Set(baseModifiers);
@@ -2559,7 +2756,7 @@ function create$4(contextWithoutDefaults) {
2559
2756
  if (isExported(parent, id.name, scope)) modifiers.add(Modifier.exported);
2560
2757
  if (isUnused(id.name, scope)) modifiers.add(Modifier.unused);
2561
2758
  if (isAsyncVariableIdentifier(id)) modifiers.add(Modifier.async);
2562
- if (isObjectStyleEnum) validators.objectStyleEnum(id, modifiers);
2759
+ if (isObjectStyleEnum) validators.objectStyleEnum(id, modifiers, true);
2563
2760
  else validator(id, modifiers);
2564
2761
  }
2565
2762
  },
@@ -2573,7 +2770,7 @@ function create$4(contextWithoutDefaults) {
2573
2770
  }));
2574
2771
  }
2575
2772
  const namingConvention = createEslintRule({
2576
- name: RULE_NAME$11,
2773
+ name: RULE_NAME$12,
2577
2774
  create: create$4,
2578
2775
  defaultOptions: camelCaseNamingConfig,
2579
2776
  meta: {
@@ -2584,18 +2781,75 @@ const namingConvention = createEslintRule({
2584
2781
  },
2585
2782
  fixable: void 0,
2586
2783
  hasSuggestions: false,
2587
- messages: messages$11,
2784
+ messages: messages$12,
2588
2785
  schema: SCHEMA,
2589
2786
  type: "suggestion"
2590
2787
  }
2591
2788
  });
2789
+ /**
2790
+ * Checks if a method name exists in any of the implemented interfaces.
2791
+ *
2792
+ * @param implementsList - List of implemented interfaces.
2793
+ * @param methodName - Name of the method to check.
2794
+ * @param checker - TypeScript type checker.
2795
+ * @param services - Parser services.
2796
+ * @returns True if the method name is found in any interface.
2797
+ */
2798
+ function checkInterfacesForMethod(implementsList, methodName, checker, services) {
2799
+ for (const implementsClause of implementsList) {
2800
+ const interfaceType = checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(implementsClause));
2801
+ if (!interfaceType.getSymbol()) continue;
2802
+ const interfaceMembers = checker.getPropertiesOfType(interfaceType);
2803
+ for (const member of interfaceMembers) if (member.name === methodName) return true;
2804
+ }
2805
+ return false;
2806
+ }
2592
2807
  function getIdentifiersFromPattern(pattern) {
2593
2808
  const identifiers = [];
2594
- new PatternVisitor({}, pattern, (id) => {
2595
- identifiers.push(id);
2596
- }).visit(pattern);
2809
+ const stack = [pattern];
2810
+ for (const node of stack) switch (node.type) {
2811
+ case AST_NODE_TYPES.ArrayPattern:
2812
+ for (const element of node.elements) if (element !== null) stack.push(element);
2813
+ break;
2814
+ case AST_NODE_TYPES.AssignmentPattern:
2815
+ stack.push(node.left);
2816
+ break;
2817
+ case AST_NODE_TYPES.Identifier:
2818
+ identifiers.push(node);
2819
+ break;
2820
+ case AST_NODE_TYPES.ObjectPattern:
2821
+ for (const property of node.properties) stack.push(property);
2822
+ break;
2823
+ case AST_NODE_TYPES.Property:
2824
+ stack.push(node.value);
2825
+ break;
2826
+ case AST_NODE_TYPES.RestElement:
2827
+ stack.push(node.argument);
2828
+ break;
2829
+ default: break;
2830
+ }
2597
2831
  return identifiers;
2598
2832
  }
2833
+ function getMemberModifiers(node) {
2834
+ const modifiers = /* @__PURE__ */ new Set();
2835
+ if ("key" in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) modifiers.add(Modifier["#private"]);
2836
+ else if (node.accessibility) modifiers.add(Modifier[node.accessibility]);
2837
+ else modifiers.add(Modifier.public);
2838
+ if (node.static) modifiers.add(Modifier.static);
2839
+ if ("readonly" in node && node.readonly) modifiers.add(Modifier.readonly);
2840
+ if ("override" in node && node.override) modifiers.add(Modifier.override);
2841
+ if (node.type === AST_NODE_TYPES.TSAbstractPropertyDefinition || node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || node.type === AST_NODE_TYPES.TSAbstractAccessorProperty) modifiers.add(Modifier.abstract);
2842
+ return modifiers;
2843
+ }
2844
+ function isAsyncMemberOrProperty(propertyOrMemberNode) {
2845
+ return Boolean("value" in propertyOrMemberNode && propertyOrMemberNode.value && "async" in propertyOrMemberNode.value && propertyOrMemberNode.value.async);
2846
+ }
2847
+ function isAsyncVariableIdentifier(id) {
2848
+ return Boolean("async" in id.parent && id.parent.async || "init" in id.parent && id.parent.init && "async" in id.parent.init && id.parent.init.async);
2849
+ }
2850
+ function isDestructured(id) {
2851
+ return id.parent.type === AST_NODE_TYPES.Property && id.parent.shorthand || id.parent.type === AST_NODE_TYPES.AssignmentPattern && id.parent.parent.type === AST_NODE_TYPES.Property && id.parent.parent.shorthand;
2852
+ }
2599
2853
  function isExported(node, name, scope) {
2600
2854
  if (node?.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || node?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) return true;
2601
2855
  if (scope === null) return false;
@@ -2610,14 +2864,167 @@ function isGlobal(scope) {
2610
2864
  if (scope === null) return false;
2611
2865
  return scope.type === TSESLint.Scope.ScopeType.global || scope.type === TSESLint.Scope.ScopeType.module;
2612
2866
  }
2613
- function requiresQuoting$1(node, target) {
2614
- return requiresQuoting(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target);
2867
+ /**
2868
+ * Checks whether a type annotation is the bare `const` type reference used by
2869
+ * const assertions (`as const`, `<const>`).
2870
+ *
2871
+ * @param typeAnnotation - The type node to check.
2872
+ * @returns True if the type node is the `const` reference.
2873
+ */
2874
+ function isConstTypeReference(typeAnnotation) {
2875
+ return typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "const";
2876
+ }
2877
+ /**
2878
+ * Unwraps a *bare* const-asserted object expression - `{...} as const` or
2879
+ * `<const>{...}` - directly, with no `satisfies` wrapper. A `satisfies`
2880
+ * wrapper is a distinct, intentional escape hatch (declaring conformance to
2881
+ * an externally-owned type) and must never be treated as the same thing by
2882
+ * this helper; callers that also want to recognize the `satisfies`-wrapped
2883
+ * form unwrap it themselves first (see `isConstAssertedInitializer`).
2884
+ *
2885
+ * @param expression - The expression to unwrap.
2886
+ * @returns The inner object expression, or undefined if `expression` isn't a
2887
+ * bare const-asserted object literal.
2888
+ */
2889
+ function getConstAssertedObject(expression) {
2890
+ if ((expression.type === AST_NODE_TYPES.TSAsExpression || expression.type === AST_NODE_TYPES.TSTypeAssertion) && isConstTypeReference(expression.typeAnnotation) && expression.expression.type === AST_NODE_TYPES.ObjectExpression) return expression.expression;
2891
+ }
2892
+ /**
2893
+ * Determines if a VariableDeclarator represents an object-style enum
2894
+ * declaration.
2895
+ *
2896
+ * Object-style enums are *bare* const assertions applied to object
2897
+ * expressions, commonly used as an alternative to TypeScript enums. Examples:
2898
+ * - `const Colors = { RED: 'red', BLUE: 'blue' } as const`
2899
+ * - `const Status = <const>{ OK: 200, ERROR: 500 }`
2900
+ * - `const Config: Options = { RED: 'red' } as const` (a type annotation on
2901
+ * the binding doesn't opt out - `satisfies` is the intended escape).
2902
+ *
2903
+ * A const assertion wrapped in `satisfies` (`{...} as const satisfies T`) is
2904
+ * explicitly excluded: it declares conformance to an externally-owned type,
2905
+ * which is the foreign-contract escape hatch this rule wants authors to
2906
+ * reach for instead of renaming keys to fit.
2907
+ *
2908
+ * @param node - The VariableDeclarator AST node to check.
2909
+ * @param parent - The parent VariableDeclaration node.
2910
+ * @returns True if this represents an object-style enum declaration.
2911
+ */
2912
+ function isObjectStyleEnumDeclaration(node, parent) {
2913
+ if (parent.kind !== "const" || !node.init) return false;
2914
+ if (node.init.type === AST_NODE_TYPES.TSSatisfiesExpression) return false;
2915
+ return getConstAssertedObject(node.init) !== void 0;
2916
+ }
2917
+ /**
2918
+ * True when `init` is a const-asserted object expression, bare (`{...} as
2919
+ * const`) or `satisfies`-wrapped (`{...} as const satisfies T`). Used for the
2920
+ * `constAsserted` variable modifier, which - unlike `isObjectStyleEnumDeclaration`
2921
+ * - deliberately covers both forms: it exists so consumers can pin a format on
2922
+ * const-asserted data objects regardless of whether they carry a `satisfies`
2923
+ * clause.
2924
+ *
2925
+ * @param init - The VariableDeclarator's initializer, if any.
2926
+ * @returns True if `init` is a const-asserted object expression.
2927
+ */
2928
+ function isConstAssertedInitializer(init) {
2929
+ if (!init) return false;
2930
+ return getConstAssertedObject(init.type === AST_NODE_TYPES.TSSatisfiesExpression ? init.expression : init) !== void 0;
2931
+ }
2932
+ /**
2933
+ * Determines if an object literal property is a direct key of an
2934
+ * object-style-enum declaration's object literal - i.e. `RED` in `const
2935
+ * Colors = { RED: 'red' } as const`, but not a key of a nested object value.
2936
+ * Nested object values aren't reached because their enclosing ObjectExpression
2937
+ * is parented by a Property, not the const-assertion node this checks for.
2938
+ *
2939
+ * @param node - The non-computed object literal property node.
2940
+ * @returns True if this property is a top-level object-style-enum key.
2941
+ */
2942
+ function isObjectStyleEnumKey(node) {
2943
+ const objectExpression = node.parent;
2944
+ if (objectExpression.type !== AST_NODE_TYPES.ObjectExpression) return false;
2945
+ const assertion = objectExpression.parent;
2946
+ if (assertion.type !== AST_NODE_TYPES.TSAsExpression && assertion.type !== AST_NODE_TYPES.TSTypeAssertion || getConstAssertedObject(assertion) !== objectExpression) return false;
2947
+ const declarator = assertion.parent;
2948
+ return declarator.type === AST_NODE_TYPES.VariableDeclarator && declarator.parent.kind === "const";
2949
+ }
2950
+ const EXTERNAL_JSDOC_TAG_PATTERN = /@external\b/u;
2951
+ /**
2952
+ * Resolves the node whose leading comments should be inspected for JSDoc -
2953
+ * for an exported declaration, the JSDoc block precedes the `export` keyword,
2954
+ * not the declaration itself.
2955
+ *
2956
+ * @param node - The declaration node.
2957
+ * @returns The export wrapper if present, otherwise `node` itself.
2958
+ */
2959
+ function getJsDocumentTargetNode(node) {
2960
+ const { parent } = node;
2961
+ if (parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) return parent;
2962
+ return node;
2963
+ }
2964
+ /**
2965
+ * Checks whether `node`'s immediately preceding JSDoc block comment carries
2966
+ * an `@external` tag, marking the name(s) it documents as coming from a
2967
+ * foreign wire format.
2968
+ *
2969
+ * @param node - The node to check.
2970
+ * @param sourceCode - Used to read the comments preceding `node`.
2971
+ * @returns True if the leading JSDoc comment contains `@external`.
2972
+ */
2973
+ function hasExternalJsDocumentTag(node, sourceCode) {
2974
+ const lastComment = sourceCode.getCommentsBefore(getJsDocumentTargetNode(node)).at(-1);
2975
+ return lastComment?.type === AST_TOKEN_TYPES.Block && lastComment.value.startsWith("*") && EXTERNAL_JSDOC_TAG_PATTERN.test(lastComment.value);
2976
+ }
2977
+ /**
2978
+ * Walks up from a type member to the nearest enclosing interface or type
2979
+ * alias declaration, passing through any nested type literals along the way
2980
+ * - foreign formats nest, so an `@external` tag on the outer declaration
2981
+ * covers members at any depth.
2982
+ *
2983
+ * @param node - The member node to walk up from.
2984
+ * @returns The enclosing declaration, or undefined if none is found.
2985
+ */
2986
+ function findEnclosingTypeDeclaration(node) {
2987
+ let current = node.parent;
2988
+ while (current) {
2989
+ if (current.type === AST_NODE_TYPES.TSInterfaceDeclaration || current.type === AST_NODE_TYPES.TSTypeAliasDeclaration) return current;
2990
+ current = current.parent;
2991
+ }
2992
+ }
2993
+ /**
2994
+ * Determines if a typeProperty/typeMethod member should skip naming
2995
+ * validation because it (or its enclosing interface/type-alias declaration)
2996
+ * is marked `@external` - a foreign wire-format name, not the author's
2997
+ * choice. The type's own name (validated via `typeLike`) is unaffected.
2998
+ *
2999
+ * @param node - The typeProperty/typeMethod node to check.
3000
+ * @param sourceCode - Used to read leading comments for the JSDoc check.
3001
+ * @returns True if the member should be skipped.
3002
+ */
3003
+ function isExternalMember(node, sourceCode) {
3004
+ if (hasExternalJsDocumentTag(node, sourceCode)) return true;
3005
+ const enclosing = findEnclosingTypeDeclaration(node);
3006
+ return enclosing !== void 0 && hasExternalJsDocumentTag(enclosing, sourceCode);
3007
+ }
3008
+ function isValidIdentifierText(name, languageVersion) {
3009
+ if (name.length === 0) return false;
3010
+ const firstCodePoint = name.codePointAt(0);
3011
+ if (firstCodePoint === void 0 || !isIdentifierStart(firstCodePoint, languageVersion)) return false;
3012
+ let index = firstCodePoint > 65535 ? 2 : 1;
3013
+ while (index < name.length) {
3014
+ const codePoint = name.codePointAt(index);
3015
+ if (codePoint === void 0 || !isIdentifierPart(codePoint, languageVersion)) return false;
3016
+ index += codePoint > 65535 ? 2 : 1;
3017
+ }
3018
+ return true;
3019
+ }
3020
+ function requiresQuoting(node, target) {
3021
+ return !isValidIdentifierText(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target ?? ScriptTarget.Latest);
2615
3022
  }
2616
3023
  //#endregion
2617
3024
  //#region src/rules/no-export-default-arrow/rule.ts
2618
- const RULE_NAME$10 = "no-export-default-arrow";
3025
+ const RULE_NAME$11 = "no-export-default-arrow";
2619
3026
  const MESSAGE_ID$6 = "disallowExportDefaultArrow";
2620
- const messages$10 = { [MESSAGE_ID$6]: "Assign the arrow function to a named constant instead of exporting it anonymously." };
3027
+ const messages$11 = { [MESSAGE_ID$6]: "Assign the arrow function to a named constant instead of exporting it anonymously." };
2621
3028
  /**
2622
3029
  * Converts a filename stem to camelCase, treating `-`, `_`, and whitespace as
2623
3030
  * word separators (`use-mouse` -> `useMouse`).
@@ -2695,7 +3102,7 @@ function createFixFunction({ arrowFunction, context, exportDeclaration, sourceCo
2695
3102
  * @param context - The rule context.
2696
3103
  * @returns The rule listener.
2697
3104
  */
2698
- function createOnce$6(context) {
3105
+ function createOnce$7(context) {
2699
3106
  return { ArrowFunctionExpression(node) {
2700
3107
  const { parent } = node;
2701
3108
  if (parent.type !== AST_NODE_TYPES.ExportDefaultDeclaration) return;
@@ -2712,8 +3119,8 @@ function createOnce$6(context) {
2712
3119
  } };
2713
3120
  }
2714
3121
  const noExportDefaultArrow = createFlawlessRule({
2715
- name: RULE_NAME$10,
2716
- createOnce: createOnce$6,
3122
+ name: RULE_NAME$11,
3123
+ createOnce: createOnce$7,
2717
3124
  defaultOptions: [],
2718
3125
  meta: {
2719
3126
  docs: {
@@ -2723,7 +3130,7 @@ const noExportDefaultArrow = createFlawlessRule({
2723
3130
  },
2724
3131
  fixable: "code",
2725
3132
  hasSuggestions: false,
2726
- messages: messages$10,
3133
+ messages: messages$11,
2727
3134
  schema: [],
2728
3135
  type: "suggestion"
2729
3136
  }
@@ -2888,7 +3295,7 @@ function flatten(file, context) {
2888
3295
  }
2889
3296
  //#endregion
2890
3297
  //#region src/rules/no-redundant-tsconfig-options/rule.ts
2891
- const RULE_NAME$9 = "no-redundant-tsconfig-options";
3298
+ const RULE_NAME$10 = "no-redundant-tsconfig-options";
2892
3299
  const MESSAGE_ID$5 = "redundant";
2893
3300
  /**
2894
3301
  * Compiler options TypeScript resolves case-insensitively; a child that re-sets
@@ -2924,7 +3331,7 @@ const PATH_KEYS = /* @__PURE__ */ new Set([
2924
3331
  "tsBuildInfoFile",
2925
3332
  "typeRoots"
2926
3333
  ]);
2927
- const messages$9 = { [MESSAGE_ID$5]: "'{{option}}' is redundant: it is already set to this value by {{source}}. Remove it." };
3334
+ const messages$10 = { [MESSAGE_ID$5]: "'{{option}}' is redundant: it is already set to this value by {{source}}. Remove it." };
2928
3335
  /**
2929
3336
  * Structural equality between two static JSON values. Strings compare
2930
3337
  * case-insensitively when `caseInsensitive` is set (used for enum-valued
@@ -3096,7 +3503,7 @@ function create$3(context) {
3096
3503
  } };
3097
3504
  }
3098
3505
  const noRedundantTsconfigOptions = createEslintRule({
3099
- name: RULE_NAME$9,
3506
+ name: RULE_NAME$10,
3100
3507
  create: create$3,
3101
3508
  defaultOptions: [],
3102
3509
  meta: {
@@ -3107,7 +3514,7 @@ const noRedundantTsconfigOptions = createEslintRule({
3107
3514
  },
3108
3515
  fixable: "code",
3109
3516
  hasSuggestions: false,
3110
- messages: messages$9,
3517
+ messages: messages$10,
3111
3518
  schema: [],
3112
3519
  type: "suggestion"
3113
3520
  }
@@ -3350,15 +3757,15 @@ function resolveFactory(sourceCode, node) {
3350
3757
  }
3351
3758
  //#endregion
3352
3759
  //#region src/rules/no-unnecessary-use-callback/rule.ts
3353
- const RULE_NAME$8 = "no-unnecessary-use-callback";
3760
+ const RULE_NAME$9 = "no-unnecessary-use-callback";
3354
3761
  const MESSAGE_ID_DEFAULT$1 = "default";
3355
3762
  const MESSAGE_ID_INSIDE_USE_EFFECT$1 = "noUnnecessaryUseCallbackInsideUseEffect";
3356
- const messages$8 = {
3763
+ const messages$9 = {
3357
3764
  [MESSAGE_ID_DEFAULT$1]: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
3358
3765
  [MESSAGE_ID_INSIDE_USE_EFFECT$1]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
3359
3766
  };
3360
3767
  const noUnnecessaryUseCallback = createFlawlessRule({
3361
- name: RULE_NAME$8,
3768
+ name: RULE_NAME$9,
3362
3769
  createOnce: createUnnecessaryHookRule({
3363
3770
  hook: "useCallback",
3364
3771
  messageIds: {
@@ -3374,22 +3781,22 @@ const noUnnecessaryUseCallback = createFlawlessRule({
3374
3781
  requiresTypeChecking: false
3375
3782
  },
3376
3783
  hasSuggestions: false,
3377
- messages: messages$8,
3784
+ messages: messages$9,
3378
3785
  schema: [],
3379
3786
  type: "suggestion"
3380
3787
  }
3381
3788
  });
3382
3789
  //#endregion
3383
3790
  //#region src/rules/no-unnecessary-use-memo/rule.ts
3384
- const RULE_NAME$7 = "no-unnecessary-use-memo";
3791
+ const RULE_NAME$8 = "no-unnecessary-use-memo";
3385
3792
  const MESSAGE_ID_DEFAULT = "default";
3386
3793
  const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
3387
- const messages$7 = {
3794
+ const messages$8 = {
3388
3795
  [MESSAGE_ID_DEFAULT]: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
3389
3796
  [MESSAGE_ID_INSIDE_USE_EFFECT]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
3390
3797
  };
3391
3798
  const noUnnecessaryUseMemo = createFlawlessRule({
3392
- name: RULE_NAME$7,
3799
+ name: RULE_NAME$8,
3393
3800
  createOnce: createUnnecessaryHookRule({
3394
3801
  hook: "useMemo",
3395
3802
  messageIds: {
@@ -3405,16 +3812,16 @@ const noUnnecessaryUseMemo = createFlawlessRule({
3405
3812
  requiresTypeChecking: false
3406
3813
  },
3407
3814
  hasSuggestions: false,
3408
- messages: messages$7,
3815
+ messages: messages$8,
3409
3816
  schema: [],
3410
3817
  type: "suggestion"
3411
3818
  }
3412
3819
  });
3413
3820
  //#endregion
3414
3821
  //#region src/rules/padding-after-expect-assertions/rule.ts
3415
- const RULE_NAME$6 = "padding-after-expect-assertions";
3822
+ const RULE_NAME$7 = "padding-after-expect-assertions";
3416
3823
  const MESSAGE_ID$4 = "missingPadding";
3417
- const messages$6 = { [MESSAGE_ID$4]: "Expected a blank line after '{{name}}'." };
3824
+ const messages$7 = { [MESSAGE_ID$4]: "Expected a blank line after '{{name}}'." };
3418
3825
  /**
3419
3826
  * Matches a statement that declares the expected assertion count at the top of
3420
3827
  * a test: `expect.assertions(n)` or `expect.hasAssertions()`.
@@ -3489,7 +3896,7 @@ function findPaddingAnchor(sourceCode, node, nextNode) {
3489
3896
  * @param context - The rule context.
3490
3897
  * @returns The rule listener.
3491
3898
  */
3492
- function createOnce$3(context) {
3899
+ function createOnce$4(context) {
3493
3900
  return { ExpressionStatement(node) {
3494
3901
  const name = getAssertionName(node);
3495
3902
  if (name === void 0) return;
@@ -3514,8 +3921,8 @@ function createOnce$3(context) {
3514
3921
  } };
3515
3922
  }
3516
3923
  const paddingAfterExpectAssertions = createFlawlessRule({
3517
- name: RULE_NAME$6,
3518
- createOnce: createOnce$3,
3924
+ name: RULE_NAME$7,
3925
+ createOnce: createOnce$4,
3519
3926
  defaultOptions: [],
3520
3927
  meta: {
3521
3928
  docs: {
@@ -3525,16 +3932,16 @@ const paddingAfterExpectAssertions = createFlawlessRule({
3525
3932
  },
3526
3933
  fixable: "whitespace",
3527
3934
  hasSuggestions: false,
3528
- messages: messages$6,
3935
+ messages: messages$7,
3529
3936
  schema: [],
3530
3937
  type: "layout"
3531
3938
  }
3532
3939
  });
3533
3940
  //#endregion
3534
3941
  //#region src/rules/prefer-destructuring-assignment/rule.ts
3535
- const RULE_NAME$5 = "prefer-destructuring-assignment";
3942
+ const RULE_NAME$6 = "prefer-destructuring-assignment";
3536
3943
  const MESSAGE_ID$3 = "default";
3537
- const messages$5 = { [MESSAGE_ID$3]: "Use destructuring assignment for component props." };
3944
+ const messages$6 = { [MESSAGE_ID$3]: "Use destructuring assignment for component props." };
3538
3945
  /**
3539
3946
  * Identifiers that cannot be used as a binding name in strict-mode module code.
3540
3947
  * A property may be accessed with such a name (`props.default`), but a shorthand
@@ -3696,7 +4103,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
3696
4103
  * @param context - The rule context.
3697
4104
  * @returns The rule listener.
3698
4105
  */
3699
- function createOnce$2(context) {
4106
+ function createOnce$3(context) {
3700
4107
  let collector = core.getFunctionComponentCollector(context);
3701
4108
  const selectorKeys = Object.keys(collector.visitor);
3702
4109
  const listener = {
@@ -3722,8 +4129,8 @@ function createOnce$2(context) {
3722
4129
  return listener;
3723
4130
  }
3724
4131
  const preferDestructuringAssignment = createFlawlessRule({
3725
- name: RULE_NAME$5,
3726
- createOnce: createOnce$2,
4132
+ name: RULE_NAME$6,
4133
+ createOnce: createOnce$3,
3727
4134
  defaultOptions: [],
3728
4135
  meta: {
3729
4136
  docs: {
@@ -3733,16 +4140,16 @@ const preferDestructuringAssignment = createFlawlessRule({
3733
4140
  },
3734
4141
  fixable: "code",
3735
4142
  hasSuggestions: false,
3736
- messages: messages$5,
4143
+ messages: messages$6,
3737
4144
  schema: [],
3738
4145
  type: "problem"
3739
4146
  }
3740
4147
  });
3741
4148
  //#endregion
3742
4149
  //#region src/rules/prefer-parameter-destructuring/rule.ts
3743
- const RULE_NAME$4 = "prefer-parameter-destructuring";
4150
+ const RULE_NAME$5 = "prefer-parameter-destructuring";
3744
4151
  const MESSAGE_ID$2 = "default";
3745
- const messages$4 = { [MESSAGE_ID$2]: "Destructure parameter '{{name}}' in the function signature instead of the body." };
4152
+ const messages$5 = { [MESSAGE_ID$2]: "Destructure parameter '{{name}}' in the function signature instead of the body." };
3746
4153
  /**
3747
4154
  * Collects the binding names introduced by a pattern (or parameter), including
3748
4155
  * names nested in object/array patterns, defaults, and rest elements.
@@ -4017,7 +4424,7 @@ function planFix(query) {
4017
4424
  * @param context - The rule context.
4018
4425
  * @returns The rule listener.
4019
4426
  */
4020
- function createOnce$1(context) {
4427
+ function createOnce$2(context) {
4021
4428
  let allowSideEffectReordering = true;
4022
4429
  function checkFunction(node) {
4023
4430
  const { body, params } = node;
@@ -4068,8 +4475,8 @@ function createOnce$1(context) {
4068
4475
  };
4069
4476
  }
4070
4477
  const preferParameterDestructuring = createFlawlessRule({
4071
- name: RULE_NAME$4,
4072
- createOnce: createOnce$1,
4478
+ name: RULE_NAME$5,
4479
+ createOnce: createOnce$2,
4073
4480
  defaultOptions: [{ allowSideEffectReordering: true }],
4074
4481
  meta: {
4075
4482
  defaultOptions: [{ allowSideEffectReordering: true }],
@@ -4080,7 +4487,7 @@ const preferParameterDestructuring = createFlawlessRule({
4080
4487
  },
4081
4488
  fixable: "code",
4082
4489
  hasSuggestions: false,
4083
- messages: messages$4,
4490
+ messages: messages$5,
4084
4491
  schema: [{
4085
4492
  additionalProperties: false,
4086
4493
  properties: { allowSideEffectReordering: {
@@ -4362,9 +4769,9 @@ function isReadonlyPropertiesProperty(checker, type, property) {
4362
4769
  }
4363
4770
  //#endregion
4364
4771
  //#region src/rules/prefer-read-only-props/rule.ts
4365
- const RULE_NAME$3 = "prefer-read-only-props";
4772
+ const RULE_NAME$4 = "prefer-read-only-props";
4366
4773
  const DEFAULT_WRAPPER_TYPE = "Readonly";
4367
- const messages$3 = { preferReadOnlyProps: "A function component's props should be read-only." };
4774
+ const messages$4 = { preferReadOnlyProps: "A function component's props should be read-only." };
4368
4775
  /** Names of the `React.forwardRef` wrapper whose props are its second type argument. */
4369
4776
  const FORWARD_REF_NAMES = /* @__PURE__ */ new Set(["forwardRef"]);
4370
4777
  /** Names of the `React.memo` wrapper whose props are its first type argument. */
@@ -4573,7 +4980,7 @@ function create$2(context) {
4573
4980
  };
4574
4981
  }
4575
4982
  const preferReadOnlyProps = createEslintRule({
4576
- name: RULE_NAME$3,
4983
+ name: RULE_NAME$4,
4577
4984
  create: create$2,
4578
4985
  defaultOptions: [{}],
4579
4986
  meta: {
@@ -4585,7 +4992,7 @@ const preferReadOnlyProps = createEslintRule({
4585
4992
  },
4586
4993
  fixable: "code",
4587
4994
  hasSuggestions: false,
4588
- messages: messages$3,
4995
+ messages: messages$4,
4589
4996
  schema: [{
4590
4997
  additionalProperties: false,
4591
4998
  properties: {
@@ -4610,7 +5017,7 @@ const preferReadOnlyProps = createEslintRule({
4610
5017
  });
4611
5018
  //#endregion
4612
5019
  //#region src/rules/purity/rule.ts
4613
- const RULE_NAME$2 = "purity";
5020
+ const RULE_NAME$3 = "purity";
4614
5021
  const MESSAGE_ID$1 = "impureCall";
4615
5022
  /**
4616
5023
  * Non-deterministic Luau / Roblox calls, written as dotted paths matching how
@@ -4643,7 +5050,7 @@ const GUARDED_GLOBALS = /* @__PURE__ */ new Set([
4643
5050
  "tick",
4644
5051
  "time"
4645
5052
  ]);
4646
- const messages$2 = { [MESSAGE_ID$1]: "Do not call '{{name}}' during render. Components and hooks must be pure. Move this call into an event handler, effect, or state initializer." };
5053
+ const messages$3 = { [MESSAGE_ID$1]: "Do not call '{{name}}' during render. Components and hooks must be pure. Move this call into an event handler, effect, or state initializer." };
4647
5054
  const schema$1 = [{
4648
5055
  additionalProperties: false,
4649
5056
  properties: {
@@ -4743,7 +5150,7 @@ function isRenderContext(reactContext, func) {
4743
5150
  const { parent } = func;
4744
5151
  return parent.type === AST_NODE_TYPES.CallExpression && parent.arguments[0] === func && core.isUseMemoCall(reactContext, parent);
4745
5152
  }
4746
- function createOnce(context) {
5153
+ function createOnce$1(context) {
4747
5154
  const reactContext = context;
4748
5155
  let signatures;
4749
5156
  function handle(node, match, path) {
@@ -4776,8 +5183,8 @@ function createOnce(context) {
4776
5183
  };
4777
5184
  }
4778
5185
  const purity = createFlawlessRule({
4779
- name: RULE_NAME$2,
4780
- createOnce,
5186
+ name: RULE_NAME$3,
5187
+ createOnce: createOnce$1,
4781
5188
  defaultOptions: [{}],
4782
5189
  meta: {
4783
5190
  defaultOptions: [{}],
@@ -4787,12 +5194,314 @@ const purity = createFlawlessRule({
4787
5194
  requiresTypeChecking: false
4788
5195
  },
4789
5196
  hasSuggestions: false,
4790
- messages: messages$2,
5197
+ messages: messages$3,
4791
5198
  schema: schema$1,
4792
5199
  type: "problem"
4793
5200
  }
4794
5201
  });
4795
5202
  //#endregion
5203
+ //#region src/rules/react-namespace/imports.ts
5204
+ /**
5205
+ * Whether a module specifier refers to the configured React import source. Matches
5206
+ * the exact source or any subpath (`@rbxts/react` and `@rbxts/react/jsx-runtime`).
5207
+ *
5208
+ * @param value - The `source.value` of an import declaration.
5209
+ * @param importSource - The configured React import source.
5210
+ * @returns True when the specifier resolves to the React package.
5211
+ */
5212
+ function matchesSource(value, importSource) {
5213
+ return value === importSource || value.startsWith(`${importSource}/`);
5214
+ }
5215
+ /**
5216
+ * Collects the import declarations in a program that come from the React source.
5217
+ *
5218
+ * @param program - The program node whose top-level statements are scanned.
5219
+ * @param importSource - The configured React import source.
5220
+ * @returns The matching import declarations, in source order.
5221
+ */
5222
+ function getReactImportDeclarations(program, importSource) {
5223
+ return program.body.filter((statement) => {
5224
+ return statement.type === AST_NODE_TYPES.ImportDeclaration && typeof statement.source.value === "string" && matchesSource(statement.source.value, importSource);
5225
+ });
5226
+ }
5227
+ /**
5228
+ * Classifies an import specifier by the kind of React binding it introduces:
5229
+ * named for `ImportSpecifier`, namespace for default and namespace specifiers.
5230
+ *
5231
+ * @param specifier - The specifier to classify.
5232
+ * @returns `"named"` or `"namespace"`.
5233
+ */
5234
+ function classifySpecifier(specifier) {
5235
+ return specifier.type === AST_NODE_TYPES.ImportSpecifier ? "named" : "namespace";
5236
+ }
5237
+ /**
5238
+ * Finds the local name of a default or namespace React import, used to qualify a
5239
+ * bare type reference (`React.ReactNode`). Returns `null` when no such import
5240
+ * exists, in which case the caller ensures one under the name `React`.
5241
+ *
5242
+ * @param declarations - The React import declarations of the file.
5243
+ * @returns The local namespace name, or `null`.
5244
+ */
5245
+ function findReactNamespaceLocal(declarations) {
5246
+ for (const declaration of declarations) for (const specifier of declaration.specifiers) if (specifier.type === AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier) return specifier.local.name;
5247
+ return null;
5248
+ }
5249
+ /**
5250
+ * Whether the file already imports React as a default or namespace binding (so a
5251
+ * namespace type qualification resolves without adding an import).
5252
+ *
5253
+ * @param declarations - The React import declarations of the file.
5254
+ * @returns True when a default or namespace React import is present.
5255
+ */
5256
+ function hasReactNamespaceImport(declarations) {
5257
+ return findReactNamespaceLocal(declarations) !== null;
5258
+ }
5259
+ /**
5260
+ * Inserts a fresh import statement above the first statement of the program.
5261
+ *
5262
+ * @param fixer - The fixer used to build the edit.
5263
+ * @param program - The program whose head receives the import.
5264
+ * @param text - The full import statement (including trailing newline).
5265
+ * @returns The insertion fix.
5266
+ */
5267
+ function insertImportStatement(fixer, program, text) {
5268
+ const first = program.body.at(0);
5269
+ if (first === void 0) return fixer.insertTextAfterRange([0, 0], text);
5270
+ return fixer.insertTextBefore(first, text);
5271
+ }
5272
+ /**
5273
+ * Builds the fix that ensures a named value import of `name` from the React
5274
+ * source, or `null` when it is already present. Prefers extending an existing
5275
+ * named import, then appending to a default import, and otherwise inserts a new
5276
+ * statement. Never touched for namespace-only imports, which cannot carry named
5277
+ * siblings and instead receive a fresh statement.
5278
+ *
5279
+ * @param fixer - The fixer used to build the edit.
5280
+ * @param parameters - The declarations, program, name, and source to import.
5281
+ * @returns The fix, or `null` when the import already exists.
5282
+ */
5283
+ function ensureNamedValueImport(fixer, { name, declarations, importSource, program }) {
5284
+ const valueDeclarations = declarations.filter((declaration) => declaration.importKind !== "type");
5285
+ for (const declaration of valueDeclarations) for (const specifier of declaration.specifiers) if (isNamedSpecifier(specifier) && specifier.imported.type === AST_NODE_TYPES.Identifier && specifier.imported.name === name) return null;
5286
+ for (const declaration of valueDeclarations) {
5287
+ const last = declaration.specifiers.filter(isNamedSpecifier).at(-1);
5288
+ if (last !== void 0) return fixer.insertTextAfter(last, `, ${name}`);
5289
+ }
5290
+ for (const declaration of valueDeclarations) {
5291
+ const defaultSpecifier = declaration.specifiers.find((specifier) => specifier.type === AST_NODE_TYPES.ImportDefaultSpecifier);
5292
+ if (defaultSpecifier !== void 0) return fixer.insertTextAfter(defaultSpecifier, `, { ${name} }`);
5293
+ }
5294
+ return insertImportStatement(fixer, program, `import { ${name} } from "${importSource}";\n`);
5295
+ }
5296
+ /**
5297
+ * Removes a single named specifier from a React import, dropping the whole
5298
+ * declaration when it was the only binding. Handles the comma and braces that
5299
+ * surround the specifier so the remaining import stays well-formed.
5300
+ *
5301
+ * @param fixer - The fixer used to build the edit.
5302
+ * @param sourceCode - The source code, used to locate surrounding tokens.
5303
+ * @param declaration - The declaration owning the specifier.
5304
+ * @param specifier - The named specifier to remove.
5305
+ * @returns The removal fix.
5306
+ */
5307
+ function removeNamedSpecifier(fixer, sourceCode, declaration, specifier) {
5308
+ const named = declaration.specifiers.filter(isNamedSpecifier);
5309
+ const others = declaration.specifiers.filter((entry) => !isNamedSpecifier(entry));
5310
+ if (named.length === 1) {
5311
+ const lastOther = others.at(-1);
5312
+ if (lastOther === void 0) return removeWholeDeclaration(fixer, sourceCode, declaration);
5313
+ const comma = sourceCode.getTokenAfter(lastOther, (token) => token.value === ",");
5314
+ const closeBrace = sourceCode.getTokenAfter(specifier, (token) => token.value === "}");
5315
+ if (comma !== null && closeBrace !== null) return fixer.removeRange([comma.range[0], closeBrace.range[1]]);
5316
+ return fixer.remove(specifier);
5317
+ }
5318
+ if (named.indexOf(specifier) === named.length - 1) {
5319
+ const comma = sourceCode.getTokenBefore(specifier, (token) => token.value === ",");
5320
+ if (comma !== null) return fixer.removeRange([comma.range[0], specifier.range[1]]);
5321
+ return fixer.remove(specifier);
5322
+ }
5323
+ const comma = sourceCode.getTokenAfter(specifier, (token) => token.value === ",");
5324
+ if (comma !== null) return fixer.removeRange([specifier.range[0], comma.range[1]]);
5325
+ return fixer.remove(specifier);
5326
+ }
5327
+ /**
5328
+ * Finds the named specifier that binds `name` across the React import
5329
+ * declarations, or `null` when none does.
5330
+ *
5331
+ * @param declarations - The React import declarations of the file.
5332
+ * @param name - The local name of the binding to find.
5333
+ * @returns A specifier together with its declaration, or `null`.
5334
+ */
5335
+ function findNamedSpecifier(declarations, name) {
5336
+ for (const declaration of declarations) for (const specifier of declaration.specifiers) if (isNamedSpecifier(specifier) && specifier.local.name === name) return {
5337
+ declaration,
5338
+ specifier
5339
+ };
5340
+ return null;
5341
+ }
5342
+ function isNamedSpecifier(specifier) {
5343
+ return specifier.type === AST_NODE_TYPES.ImportSpecifier;
5344
+ }
5345
+ /**
5346
+ * Removes a whole import declaration together with the line break that follows
5347
+ * it, so dropping the sole specifier of an import leaves no blank line.
5348
+ *
5349
+ * @param fixer - The fixer used to build the edit.
5350
+ * @param sourceCode - The source code, used to inspect trailing characters.
5351
+ * @param declaration - The declaration to remove.
5352
+ * @returns The removal fix.
5353
+ */
5354
+ function removeWholeDeclaration(fixer, { text }, declaration) {
5355
+ const [start] = declaration.range;
5356
+ let end = declaration.range[1];
5357
+ if (text[end] === "\r") end += 1;
5358
+ if (text[end] === "\n") end += 1;
5359
+ return fixer.removeRange([start, end]);
5360
+ }
5361
+ //#endregion
5362
+ //#region src/rules/react-namespace/rule.ts
5363
+ const RULE_NAME$2 = "react-namespace";
5364
+ const MESSAGE_ID_RUNTIME = "runtimeNamespace";
5365
+ const MESSAGE_ID_TYPE = "typeNamedImport";
5366
+ const DEFAULT_IMPORT_SOURCE = "react";
5367
+ const messages$2 = {
5368
+ [MESSAGE_ID_RUNTIME]: "Use a named import of '{{name}}' instead of accessing it through the React namespace.",
5369
+ [MESSAGE_ID_TYPE]: "Access the '{{name}}' type through the React namespace instead of importing it by name."
5370
+ };
5371
+ /**
5372
+ * Whether a member access is the target of an assignment (`React.x = …`), which
5373
+ * must be left alone — rewriting it to a bare name would create an assignment to
5374
+ * an import binding.
5375
+ *
5376
+ * @param node - The member expression under consideration.
5377
+ * @returns True when the member is an assignment target.
5378
+ */
5379
+ function isAssignmentTarget(node) {
5380
+ return node.parent.type === AST_NODE_TYPES.AssignmentExpression && node.parent.left === node;
5381
+ }
5382
+ /**
5383
+ * Scans a binding's references once, recording whether all of them are bare
5384
+ * type references (`typeName` is the identifier itself) and where the first one
5385
+ * starts. The named import can be removed only when every reference is
5386
+ * convertible, and only the *first* reference carries the import edits.
5387
+ *
5388
+ * The import edits (adding the namespace import, removing the redundant
5389
+ * specifier) sit at the top of the file. ESLint merges a report's fix array into
5390
+ * one edit spanning `[min, max]`, so anchoring those edits to the first
5391
+ * reference keeps that span from the file head to the first usage — later
5392
+ * references stay disjoint and their own qualify edits survive a single pass.
5393
+ *
5394
+ * @param references - The references recorded for the binding.
5395
+ * @returns The convertibility and first-reference facts for the binding.
5396
+ */
5397
+ function computeRemovableInfo(references) {
5398
+ let allConvertible = true;
5399
+ let firstStart = Number.POSITIVE_INFINITY;
5400
+ for (const { identifier } of references) {
5401
+ firstStart = Math.min(firstStart, identifier.range[0]);
5402
+ if (identifier.parent.type !== AST_NODE_TYPES.TSTypeReference || identifier.parent.typeName !== identifier) allConvertible = false;
5403
+ }
5404
+ return {
5405
+ allConvertible,
5406
+ firstStart
5407
+ };
5408
+ }
5409
+ function createOnce(context) {
5410
+ let importSource;
5411
+ let program;
5412
+ let declarations;
5413
+ let sourceCode;
5414
+ let resolutions;
5415
+ let removableInfoByVariable;
5416
+ function getRemovableInfo(variable) {
5417
+ let info = removableInfoByVariable.get(variable);
5418
+ if (info === void 0) {
5419
+ info = computeRemovableInfo(variable.references);
5420
+ removableInfoByVariable.set(variable, info);
5421
+ }
5422
+ return info;
5423
+ }
5424
+ return {
5425
+ before() {
5426
+ ({sourceCode} = context);
5427
+ importSource = context.settings["react-x"]?.importSource ?? DEFAULT_IMPORT_SOURCE;
5428
+ program = sourceCode.ast;
5429
+ declarations = getReactImportDeclarations(program, importSource);
5430
+ resolutions = /* @__PURE__ */ new Map();
5431
+ removableInfoByVariable = /* @__PURE__ */ new Map();
5432
+ for (const declaration of declarations) for (const specifier of declaration.specifiers) {
5433
+ const kind = classifySpecifier(specifier);
5434
+ const [variable] = sourceCode.getDeclaredVariables(specifier);
5435
+ if (variable === void 0) continue;
5436
+ for (const reference of variable.references) resolutions.set(reference.identifier, {
5437
+ kind,
5438
+ variable
5439
+ });
5440
+ }
5441
+ },
5442
+ MemberExpression(node) {
5443
+ if (node.computed || node.object.type !== AST_NODE_TYPES.Identifier || node.property.type !== AST_NODE_TYPES.Identifier || isAssignmentTarget(node)) return;
5444
+ const { object, property } = node;
5445
+ if (resolutions.get(object)?.kind !== "namespace") return;
5446
+ context.report({
5447
+ data: { name: property.name },
5448
+ fix: (fixer) => {
5449
+ const fixes = [fixer.removeRange([object.range[0], property.range[0]])];
5450
+ const ensure = ensureNamedValueImport(fixer, {
5451
+ name: property.name,
5452
+ declarations,
5453
+ importSource,
5454
+ program
5455
+ });
5456
+ if (ensure !== null) fixes.push(ensure);
5457
+ return fixes;
5458
+ },
5459
+ messageId: MESSAGE_ID_RUNTIME,
5460
+ node
5461
+ });
5462
+ },
5463
+ TSTypeReference(node) {
5464
+ if (node.typeName.type !== AST_NODE_TYPES.Identifier) return;
5465
+ const { typeName } = node;
5466
+ const resolution = resolutions.get(typeName);
5467
+ if (resolution?.kind !== "named") return;
5468
+ context.report({
5469
+ data: { name: typeName.name },
5470
+ fix: (fixer) => {
5471
+ const reactLocal = findReactNamespaceLocal(declarations) ?? "React";
5472
+ const fixes = [fixer.insertTextBefore(typeName, `${reactLocal}.`)];
5473
+ const info = getRemovableInfo(resolution.variable);
5474
+ if (info.allConvertible && typeName.range[0] === info.firstStart) {
5475
+ if (!hasReactNamespaceImport(declarations)) fixes.push(insertImportStatement(fixer, program, `import ${reactLocal} from "${importSource}";\n`));
5476
+ const found = findNamedSpecifier(declarations, typeName.name);
5477
+ if (found !== null) fixes.push(removeNamedSpecifier(fixer, sourceCode, found.declaration, found.specifier));
5478
+ }
5479
+ return fixes;
5480
+ },
5481
+ messageId: MESSAGE_ID_TYPE,
5482
+ node
5483
+ });
5484
+ }
5485
+ };
5486
+ }
5487
+ const reactNamespace = createFlawlessRule({
5488
+ name: RULE_NAME$2,
5489
+ createOnce,
5490
+ defaultOptions: [],
5491
+ meta: {
5492
+ docs: {
5493
+ description: "Prefer named imports for React runtime values and the React namespace for React types",
5494
+ recommended: false,
5495
+ requiresTypeChecking: false
5496
+ },
5497
+ fixable: "code",
5498
+ hasSuggestions: false,
5499
+ messages: messages$2,
5500
+ schema: [],
5501
+ type: "problem"
5502
+ }
5503
+ });
5504
+ //#endregion
4796
5505
  //#region src/rules/toml-sort-keys/rule.ts
4797
5506
  const RULE_NAME$1 = "toml-sort-keys";
4798
5507
  const MESSAGE_ID = "unsorted";
@@ -5105,6 +5814,7 @@ const plugin = {
5105
5814
  "prefer-parameter-destructuring": preferParameterDestructuring,
5106
5815
  "prefer-read-only-props": preferReadOnlyProps,
5107
5816
  "purity": purity,
5817
+ "react-namespace": reactNamespace,
5108
5818
  "toml-sort-keys": tomlSortKeys,
5109
5819
  "yaml-block-key-blank-lines": yamlBlockKeyBlankLines
5110
5820
  }