eslint-plugin-flawless 1.1.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.
- package/README.md +1 -0
- package/dist/index.d.mts +34 -25
- package/dist/index.mjs +1 -1
- package/dist/oxlint.mjs +1 -1
- package/dist/{plugin-B-YWsemf.mjs → plugin-K2GOCG3Z.mjs} +566 -103
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
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";
|
|
@@ -14,7 +14,7 @@ import * as core from "@eslint-react/core";
|
|
|
14
14
|
import { getStaticTOMLValue } from "toml-eslint-parser";
|
|
15
15
|
//#region package.json
|
|
16
16
|
var name = "eslint-plugin-flawless";
|
|
17
|
-
var version = "1.
|
|
17
|
+
var version = "1.2.0";
|
|
18
18
|
var repository = {
|
|
19
19
|
"url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
|
|
20
20
|
"type": "git"
|
|
@@ -84,7 +84,7 @@ function createFlawlessRule({ createOnce, ...meta }) {
|
|
|
84
84
|
}
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/rules/arrow-return-style/rule.ts
|
|
87
|
-
const RULE_NAME$
|
|
87
|
+
const RULE_NAME$16 = "arrow-return-style";
|
|
88
88
|
const IMPLICIT = "useImplicitReturn";
|
|
89
89
|
const EXPLICIT = "useExplicitReturn";
|
|
90
90
|
const COMPLEX_EXPLICIT = "useExplicitReturnComplex";
|
|
@@ -232,7 +232,7 @@ function collectArrows(root) {
|
|
|
232
232
|
return arrows.sort((a, b) => a.range[0] - b.range[0]);
|
|
233
233
|
}
|
|
234
234
|
const arrowReturnStyle = createFlawlessRule({
|
|
235
|
-
name: RULE_NAME$
|
|
235
|
+
name: RULE_NAME$16,
|
|
236
236
|
createOnce(context) {
|
|
237
237
|
let config = DEFAULTS$1;
|
|
238
238
|
let sourceCode;
|
|
@@ -598,10 +598,10 @@ const arrowReturnStyle = createFlawlessRule({
|
|
|
598
598
|
});
|
|
599
599
|
//#endregion
|
|
600
600
|
//#region src/rules/jsx-shorthand-boolean/rule.ts
|
|
601
|
-
const RULE_NAME$
|
|
601
|
+
const RULE_NAME$15 = "jsx-shorthand-boolean";
|
|
602
602
|
const MESSAGE_ID$7 = "setAttributeValue";
|
|
603
|
-
const messages$
|
|
604
|
-
function createOnce$
|
|
603
|
+
const messages$15 = { [MESSAGE_ID$7]: "Set an explicit value for boolean attribute '{{name}}'." };
|
|
604
|
+
function createOnce$10(context) {
|
|
605
605
|
return { JSXAttribute(node) {
|
|
606
606
|
if (node.value !== null) return;
|
|
607
607
|
context.report({
|
|
@@ -613,8 +613,8 @@ function createOnce$9(context) {
|
|
|
613
613
|
} };
|
|
614
614
|
}
|
|
615
615
|
const jsxShorthandBoolean = createFlawlessRule({
|
|
616
|
-
name: RULE_NAME$
|
|
617
|
-
createOnce: createOnce$
|
|
616
|
+
name: RULE_NAME$15,
|
|
617
|
+
createOnce: createOnce$10,
|
|
618
618
|
defaultOptions: [],
|
|
619
619
|
meta: {
|
|
620
620
|
docs: {
|
|
@@ -624,19 +624,19 @@ const jsxShorthandBoolean = createFlawlessRule({
|
|
|
624
624
|
},
|
|
625
625
|
fixable: "code",
|
|
626
626
|
hasSuggestions: false,
|
|
627
|
-
messages: messages$
|
|
627
|
+
messages: messages$15,
|
|
628
628
|
schema: [],
|
|
629
629
|
type: "suggestion"
|
|
630
630
|
}
|
|
631
631
|
});
|
|
632
632
|
//#endregion
|
|
633
633
|
//#region src/rules/jsx-shorthand-fragment/rule.ts
|
|
634
|
-
const RULE_NAME$
|
|
634
|
+
const RULE_NAME$14 = "jsx-shorthand-fragment";
|
|
635
635
|
const MESSAGE_ID_NAMED = "useNamedFragment";
|
|
636
636
|
const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
|
|
637
637
|
const DEFAULT_MODE = "syntax";
|
|
638
638
|
const DEFAULT_FRAGMENT_NAME = "Fragment";
|
|
639
|
-
const messages$
|
|
639
|
+
const messages$14 = {
|
|
640
640
|
[MESSAGE_ID_NAMED]: "Use the '{{name}}' component instead of fragment shorthand syntax.",
|
|
641
641
|
[MESSAGE_ID_SHORTHAND]: "Use the fragment shorthand syntax '<>...</>' instead of the '{{name}}' component."
|
|
642
642
|
};
|
|
@@ -672,7 +672,7 @@ function jsxNameToString(node) {
|
|
|
672
672
|
}
|
|
673
673
|
return null;
|
|
674
674
|
}
|
|
675
|
-
function createOnce$
|
|
675
|
+
function createOnce$9(context) {
|
|
676
676
|
let mode;
|
|
677
677
|
let fragmentName;
|
|
678
678
|
let namedFragments;
|
|
@@ -719,8 +719,8 @@ function createOnce$8(context) {
|
|
|
719
719
|
};
|
|
720
720
|
}
|
|
721
721
|
const jsxShorthandFragment = createFlawlessRule({
|
|
722
|
-
name: RULE_NAME$
|
|
723
|
-
createOnce: createOnce$
|
|
722
|
+
name: RULE_NAME$14,
|
|
723
|
+
createOnce: createOnce$9,
|
|
724
724
|
defaultOptions: [{
|
|
725
725
|
fragmentName: DEFAULT_FRAGMENT_NAME,
|
|
726
726
|
mode: DEFAULT_MODE
|
|
@@ -737,7 +737,7 @@ const jsxShorthandFragment = createFlawlessRule({
|
|
|
737
737
|
},
|
|
738
738
|
fixable: "code",
|
|
739
739
|
hasSuggestions: false,
|
|
740
|
-
messages: messages$
|
|
740
|
+
messages: messages$14,
|
|
741
741
|
schema: schema$3,
|
|
742
742
|
type: "suggestion"
|
|
743
743
|
}
|
|
@@ -877,7 +877,7 @@ function getOpeningParenOfParameters(node, sourceCode) {
|
|
|
877
877
|
}
|
|
878
878
|
//#endregion
|
|
879
879
|
//#region src/rules/max-lines-per-function/rule.ts
|
|
880
|
-
const RULE_NAME$
|
|
880
|
+
const RULE_NAME$13 = "max-lines-per-function";
|
|
881
881
|
const MESSAGE_ID_EXCEED = "exceed";
|
|
882
882
|
/** A line that is empty or holds only whitespace. */
|
|
883
883
|
const BLANK_LINE = /^\s*$/u;
|
|
@@ -888,7 +888,7 @@ const DEFAULTS = {
|
|
|
888
888
|
skipBlankLines: false,
|
|
889
889
|
skipComments: false
|
|
890
890
|
};
|
|
891
|
-
const messages$
|
|
891
|
+
const messages$13 = { [MESSAGE_ID_EXCEED]: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}." };
|
|
892
892
|
const schema$2 = [{
|
|
893
893
|
additionalProperties: false,
|
|
894
894
|
properties: {
|
|
@@ -991,7 +991,7 @@ function getCountedLoc(countFrom, funcNode, reportNode) {
|
|
|
991
991
|
* @param context - The rule context.
|
|
992
992
|
* @returns The visitors measuring each function.
|
|
993
993
|
*/
|
|
994
|
-
function createOnce$
|
|
994
|
+
function createOnce$8(context) {
|
|
995
995
|
let commentLineNumbers;
|
|
996
996
|
let config;
|
|
997
997
|
let lines;
|
|
@@ -1047,8 +1047,8 @@ function createOnce$7(context) {
|
|
|
1047
1047
|
};
|
|
1048
1048
|
}
|
|
1049
1049
|
const maxLinesPerFunction = createFlawlessRule({
|
|
1050
|
-
name: RULE_NAME$
|
|
1051
|
-
createOnce: createOnce$
|
|
1050
|
+
name: RULE_NAME$13,
|
|
1051
|
+
createOnce: createOnce$8,
|
|
1052
1052
|
defaultOptions: [DEFAULTS],
|
|
1053
1053
|
meta: {
|
|
1054
1054
|
defaultOptions: [DEFAULTS],
|
|
@@ -1058,7 +1058,7 @@ const maxLinesPerFunction = createFlawlessRule({
|
|
|
1058
1058
|
requiresTypeChecking: false
|
|
1059
1059
|
},
|
|
1060
1060
|
hasSuggestions: false,
|
|
1061
|
-
messages: messages$
|
|
1061
|
+
messages: messages$13,
|
|
1062
1062
|
schema: schema$2,
|
|
1063
1063
|
type: "suggestion"
|
|
1064
1064
|
}
|
|
@@ -1658,7 +1658,8 @@ const Modifier = {
|
|
|
1658
1658
|
"override": 8192,
|
|
1659
1659
|
"async": 16384,
|
|
1660
1660
|
"default": 32768,
|
|
1661
|
-
"namespace": 65536
|
|
1661
|
+
"namespace": 65536,
|
|
1662
|
+
"constAsserted": 131072
|
|
1662
1663
|
};
|
|
1663
1664
|
const PredefinedFormat = {
|
|
1664
1665
|
camelCase: 1,
|
|
@@ -1670,15 +1671,15 @@ const PredefinedFormat = {
|
|
|
1670
1671
|
};
|
|
1671
1672
|
const PredefinedFormatValueToKey = Object.fromEntries(Object.entries(PredefinedFormat).map(([key, value]) => [value, key]));
|
|
1672
1673
|
const TypeModifier = {
|
|
1673
|
-
boolean:
|
|
1674
|
-
string:
|
|
1675
|
-
number:
|
|
1676
|
-
function:
|
|
1677
|
-
array:
|
|
1674
|
+
boolean: 262144,
|
|
1675
|
+
string: 524288,
|
|
1676
|
+
number: 1048576,
|
|
1677
|
+
function: 2097152,
|
|
1678
|
+
array: 4194304
|
|
1678
1679
|
};
|
|
1679
1680
|
const TypeModifierValueToKey = Object.fromEntries(Object.entries(TypeModifier).map(([key, value]) => [value, key]));
|
|
1680
|
-
const TYPE_REFERENCE_LOOSE_WEIGHT = 1 <<
|
|
1681
|
-
const TYPE_REFERENCE_STRICT_WEIGHT = 1 <<
|
|
1681
|
+
const TYPE_REFERENCE_LOOSE_WEIGHT = 1 << 23;
|
|
1682
|
+
const TYPE_REFERENCE_STRICT_WEIGHT = 1 << 24;
|
|
1682
1683
|
const UnderscoreOption = {
|
|
1683
1684
|
forbid: 1,
|
|
1684
1685
|
allow: 2,
|
|
@@ -1773,7 +1774,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1773
1774
|
if (!aIsMethodOrProperty && bIsMethodOrProperty) return 1;
|
|
1774
1775
|
return b.selector - a.selector;
|
|
1775
1776
|
});
|
|
1776
|
-
return (node, modifiers = /* @__PURE__ */ new Set()) => {
|
|
1777
|
+
return (node, modifiers = /* @__PURE__ */ new Set(), isObjectStyleEnumName = false) => {
|
|
1777
1778
|
const originalName = node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`;
|
|
1778
1779
|
for (const config of configs) {
|
|
1779
1780
|
if (config.filter?.regex.test(originalName) !== config.filter?.match) continue;
|
|
@@ -1783,6 +1784,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1783
1784
|
name = validateUnderscore({
|
|
1784
1785
|
name,
|
|
1785
1786
|
config,
|
|
1787
|
+
isObjectStyleEnumName,
|
|
1786
1788
|
node,
|
|
1787
1789
|
originalName,
|
|
1788
1790
|
position: "leading"
|
|
@@ -1791,6 +1793,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1791
1793
|
name = validateUnderscore({
|
|
1792
1794
|
name,
|
|
1793
1795
|
config,
|
|
1796
|
+
isObjectStyleEnumName,
|
|
1794
1797
|
node,
|
|
1795
1798
|
originalName,
|
|
1796
1799
|
position: "trailing"
|
|
@@ -1799,6 +1802,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1799
1802
|
name = validateAffix({
|
|
1800
1803
|
name,
|
|
1801
1804
|
config,
|
|
1805
|
+
isObjectStyleEnumName,
|
|
1802
1806
|
node,
|
|
1803
1807
|
originalName,
|
|
1804
1808
|
position: "prefix"
|
|
@@ -1807,6 +1811,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1807
1811
|
name = validateAffix({
|
|
1808
1812
|
name,
|
|
1809
1813
|
config,
|
|
1814
|
+
isObjectStyleEnumName,
|
|
1810
1815
|
node,
|
|
1811
1816
|
originalName,
|
|
1812
1817
|
position: "suffix"
|
|
@@ -1815,12 +1820,14 @@ function createValidator(type, context, allConfigs) {
|
|
|
1815
1820
|
if (!validateCustom({
|
|
1816
1821
|
name,
|
|
1817
1822
|
config,
|
|
1823
|
+
isObjectStyleEnumName,
|
|
1818
1824
|
node,
|
|
1819
1825
|
originalName
|
|
1820
1826
|
})) return;
|
|
1821
1827
|
if (!validatePredefinedFormat({
|
|
1822
1828
|
name,
|
|
1823
1829
|
config,
|
|
1830
|
+
isObjectStyleEnumName,
|
|
1824
1831
|
modifiers,
|
|
1825
1832
|
node,
|
|
1826
1833
|
originalName
|
|
@@ -1844,7 +1851,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1844
1851
|
type: selectorTypeToMessageString(type)
|
|
1845
1852
|
};
|
|
1846
1853
|
}
|
|
1847
|
-
function validateUnderscore({ name, config, node, originalName, position }) {
|
|
1854
|
+
function validateUnderscore({ name, config, isObjectStyleEnumName, node, originalName, position }) {
|
|
1848
1855
|
const option = position === "leading" ? config.leadingUnderscore : config.trailingUnderscore;
|
|
1849
1856
|
if (!option) return name;
|
|
1850
1857
|
const hasSingleUnderscore = position === "leading" ? () => name.startsWith("_") : () => name.endsWith("_");
|
|
@@ -1870,7 +1877,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1870
1877
|
originalName,
|
|
1871
1878
|
position
|
|
1872
1879
|
}),
|
|
1873
|
-
messageId: "unexpectedUnderscore",
|
|
1880
|
+
messageId: pickMessageId("unexpectedUnderscore", isObjectStyleEnumName),
|
|
1874
1881
|
node
|
|
1875
1882
|
});
|
|
1876
1883
|
return;
|
|
@@ -1884,7 +1891,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1884
1891
|
originalName,
|
|
1885
1892
|
position
|
|
1886
1893
|
}),
|
|
1887
|
-
messageId: "missingUnderscore",
|
|
1894
|
+
messageId: pickMessageId("missingUnderscore", isObjectStyleEnumName),
|
|
1888
1895
|
node
|
|
1889
1896
|
});
|
|
1890
1897
|
return;
|
|
@@ -1898,7 +1905,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1898
1905
|
originalName,
|
|
1899
1906
|
position
|
|
1900
1907
|
}),
|
|
1901
|
-
messageId: "missingUnderscore",
|
|
1908
|
+
messageId: pickMessageId("missingUnderscore", isObjectStyleEnumName),
|
|
1902
1909
|
node
|
|
1903
1910
|
});
|
|
1904
1911
|
return;
|
|
@@ -1906,7 +1913,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
1906
1913
|
return trimDoubleUnderscore();
|
|
1907
1914
|
}
|
|
1908
1915
|
}
|
|
1909
|
-
function validateAffix({ name, config, node, originalName, position }) {
|
|
1916
|
+
function validateAffix({ name, config, isObjectStyleEnumName, node, originalName, position }) {
|
|
1910
1917
|
const affixes = config[position];
|
|
1911
1918
|
if (!affixes || affixes.length === 0) return name;
|
|
1912
1919
|
for (const affix of affixes) {
|
|
@@ -1920,11 +1927,11 @@ function createValidator(type, context, allConfigs) {
|
|
|
1920
1927
|
originalName,
|
|
1921
1928
|
position
|
|
1922
1929
|
}),
|
|
1923
|
-
messageId: "missingAffix",
|
|
1930
|
+
messageId: pickMessageId("missingAffix", isObjectStyleEnumName),
|
|
1924
1931
|
node
|
|
1925
1932
|
});
|
|
1926
1933
|
}
|
|
1927
|
-
function validateCustom({ name, config, node, originalName }) {
|
|
1934
|
+
function validateCustom({ name, config, isObjectStyleEnumName, node, originalName }) {
|
|
1928
1935
|
const { custom } = config;
|
|
1929
1936
|
if (!custom) return true;
|
|
1930
1937
|
const result = custom.regex.test(name);
|
|
@@ -1935,12 +1942,12 @@ function createValidator(type, context, allConfigs) {
|
|
|
1935
1942
|
custom,
|
|
1936
1943
|
originalName
|
|
1937
1944
|
}),
|
|
1938
|
-
messageId: "satisfyCustom",
|
|
1945
|
+
messageId: pickMessageId("satisfyCustom", isObjectStyleEnumName),
|
|
1939
1946
|
node
|
|
1940
1947
|
});
|
|
1941
1948
|
return false;
|
|
1942
1949
|
}
|
|
1943
|
-
function validatePredefinedFormat({ name, config, modifiers, node, originalName }) {
|
|
1950
|
+
function validatePredefinedFormat({ name, config, isObjectStyleEnumName, modifiers, node, originalName }) {
|
|
1944
1951
|
const formats = config.format;
|
|
1945
1952
|
if (!formats || formats.length === 0) return true;
|
|
1946
1953
|
if (!modifiers.has(Modifier.requiresQuotes)) for (const format of formats) {
|
|
@@ -1953,12 +1960,26 @@ function createValidator(type, context, allConfigs) {
|
|
|
1953
1960
|
originalName,
|
|
1954
1961
|
processedName: name
|
|
1955
1962
|
}),
|
|
1956
|
-
messageId: originalName === name ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed",
|
|
1963
|
+
messageId: pickMessageId(originalName === name ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed", isObjectStyleEnumName),
|
|
1957
1964
|
node
|
|
1958
1965
|
});
|
|
1959
1966
|
return false;
|
|
1960
1967
|
}
|
|
1961
1968
|
}
|
|
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
|
+
}
|
|
1962
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;
|
|
1963
1984
|
function isAllTypesMatch(type, callback) {
|
|
1964
1985
|
if (type.isUnion()) return type.types.every((inner) => callback(inner));
|
|
@@ -2295,6 +2316,7 @@ const SCHEMA = {
|
|
|
2295
2316
|
...selectorSchema("variableLike", false, ["unused", "async"]),
|
|
2296
2317
|
...selectorSchema("variable", true, [
|
|
2297
2318
|
"const",
|
|
2319
|
+
"constAsserted",
|
|
2298
2320
|
"destructured",
|
|
2299
2321
|
"exported",
|
|
2300
2322
|
"global",
|
|
@@ -2437,14 +2459,21 @@ const SCHEMA = {
|
|
|
2437
2459
|
};
|
|
2438
2460
|
//#endregion
|
|
2439
2461
|
//#region src/rules/naming-convention/rule.ts
|
|
2440
|
-
const RULE_NAME$
|
|
2441
|
-
const
|
|
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 = {
|
|
2442
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}`,
|
|
2443
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}`,
|
|
2444
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}`,
|
|
2445
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}`,
|
|
2446
2473
|
satisfyCustom: "{{type}} name `{{name}}` must {{regexMatch}} the RegExp: {{regex}}",
|
|
2447
|
-
|
|
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}`
|
|
2448
2477
|
};
|
|
2449
2478
|
const camelCaseNamingConfig = [
|
|
2450
2479
|
{
|
|
@@ -2525,12 +2554,19 @@ function create$4(contextWithoutDefaults) {
|
|
|
2525
2554
|
":not(ObjectPattern) > Property[computed = false][kind = \"init\"][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
|
|
2526
2555
|
handler: (node, validator) => {
|
|
2527
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
|
+
}
|
|
2528
2563
|
handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
|
|
2529
2564
|
},
|
|
2530
2565
|
validator: validators.objectLiteralProperty
|
|
2531
2566
|
},
|
|
2532
2567
|
[["TSMethodSignature[computed = false]", "TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type = \"TSFunctionType\"]"].join(", ")]: {
|
|
2533
2568
|
handler: (node, validator) => {
|
|
2569
|
+
if (isExternalMember(node, context.sourceCode)) return;
|
|
2534
2570
|
handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
|
|
2535
2571
|
},
|
|
2536
2572
|
validator: validators.typeMethod
|
|
@@ -2678,6 +2714,7 @@ function create$4(contextWithoutDefaults) {
|
|
|
2678
2714
|
},
|
|
2679
2715
|
"TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type != \"TSFunctionType\"]": {
|
|
2680
2716
|
handler: (node, validator) => {
|
|
2717
|
+
if (isExternalMember(node, context.sourceCode)) return;
|
|
2681
2718
|
const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
|
|
2682
2719
|
if (node.readonly) modifiers.add(Modifier.readonly);
|
|
2683
2720
|
handleMember(validator, node, modifiers);
|
|
@@ -2707,9 +2744,10 @@ function create$4(contextWithoutDefaults) {
|
|
|
2707
2744
|
handler: (node, validator) => {
|
|
2708
2745
|
const identifiers = getIdentifiersFromPattern(node.id);
|
|
2709
2746
|
const baseModifiers = /* @__PURE__ */ new Set();
|
|
2710
|
-
const { parent } = node;
|
|
2747
|
+
const { init, parent } = node;
|
|
2711
2748
|
if (parent.kind === "const") baseModifiers.add(Modifier.const);
|
|
2712
2749
|
if (isGlobal(context.sourceCode.getScope(node))) baseModifiers.add(Modifier.global);
|
|
2750
|
+
if (isConstAssertedInitializer(init)) baseModifiers.add(Modifier.constAsserted);
|
|
2713
2751
|
const isObjectStyleEnum = isObjectStyleEnumDeclaration(node, parent);
|
|
2714
2752
|
for (const id of identifiers) {
|
|
2715
2753
|
const modifiers = new Set(baseModifiers);
|
|
@@ -2718,7 +2756,7 @@ function create$4(contextWithoutDefaults) {
|
|
|
2718
2756
|
if (isExported(parent, id.name, scope)) modifiers.add(Modifier.exported);
|
|
2719
2757
|
if (isUnused(id.name, scope)) modifiers.add(Modifier.unused);
|
|
2720
2758
|
if (isAsyncVariableIdentifier(id)) modifiers.add(Modifier.async);
|
|
2721
|
-
if (isObjectStyleEnum) validators.objectStyleEnum(id, modifiers);
|
|
2759
|
+
if (isObjectStyleEnum) validators.objectStyleEnum(id, modifiers, true);
|
|
2722
2760
|
else validator(id, modifiers);
|
|
2723
2761
|
}
|
|
2724
2762
|
},
|
|
@@ -2732,7 +2770,7 @@ function create$4(contextWithoutDefaults) {
|
|
|
2732
2770
|
}));
|
|
2733
2771
|
}
|
|
2734
2772
|
const namingConvention = createEslintRule({
|
|
2735
|
-
name: RULE_NAME$
|
|
2773
|
+
name: RULE_NAME$12,
|
|
2736
2774
|
create: create$4,
|
|
2737
2775
|
defaultOptions: camelCaseNamingConfig,
|
|
2738
2776
|
meta: {
|
|
@@ -2743,7 +2781,7 @@ const namingConvention = createEslintRule({
|
|
|
2743
2781
|
},
|
|
2744
2782
|
fixable: void 0,
|
|
2745
2783
|
hasSuggestions: false,
|
|
2746
|
-
messages: messages$
|
|
2784
|
+
messages: messages$12,
|
|
2747
2785
|
schema: SCHEMA,
|
|
2748
2786
|
type: "suggestion"
|
|
2749
2787
|
}
|
|
@@ -2827,23 +2865,145 @@ function isGlobal(scope) {
|
|
|
2827
2865
|
return scope.type === TSESLint.Scope.ScopeType.global || scope.type === TSESLint.Scope.ScopeType.module;
|
|
2828
2866
|
}
|
|
2829
2867
|
/**
|
|
2830
|
-
*
|
|
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`).
|
|
2831
2884
|
*
|
|
2832
|
-
*
|
|
2833
|
-
*
|
|
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:
|
|
2834
2898
|
* - `const Colors = { RED: 'red', BLUE: 'blue' } as const`
|
|
2835
|
-
* - `const Status = <const>{ OK: 200, ERROR: 500 }
|
|
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.
|
|
2836
2907
|
*
|
|
2837
2908
|
* @param node - The VariableDeclarator AST node to check.
|
|
2838
2909
|
* @param parent - The parent VariableDeclaration node.
|
|
2839
2910
|
* @returns True if this represents an object-style enum declaration.
|
|
2840
2911
|
*/
|
|
2841
2912
|
function isObjectStyleEnumDeclaration(node, parent) {
|
|
2842
|
-
if (parent.kind !== "const") return false;
|
|
2843
|
-
if (
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
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);
|
|
2847
3007
|
}
|
|
2848
3008
|
function isValidIdentifierText(name, languageVersion) {
|
|
2849
3009
|
if (name.length === 0) return false;
|
|
@@ -2862,9 +3022,9 @@ function requiresQuoting(node, target) {
|
|
|
2862
3022
|
}
|
|
2863
3023
|
//#endregion
|
|
2864
3024
|
//#region src/rules/no-export-default-arrow/rule.ts
|
|
2865
|
-
const RULE_NAME$
|
|
3025
|
+
const RULE_NAME$11 = "no-export-default-arrow";
|
|
2866
3026
|
const MESSAGE_ID$6 = "disallowExportDefaultArrow";
|
|
2867
|
-
const messages$
|
|
3027
|
+
const messages$11 = { [MESSAGE_ID$6]: "Assign the arrow function to a named constant instead of exporting it anonymously." };
|
|
2868
3028
|
/**
|
|
2869
3029
|
* Converts a filename stem to camelCase, treating `-`, `_`, and whitespace as
|
|
2870
3030
|
* word separators (`use-mouse` -> `useMouse`).
|
|
@@ -2942,7 +3102,7 @@ function createFixFunction({ arrowFunction, context, exportDeclaration, sourceCo
|
|
|
2942
3102
|
* @param context - The rule context.
|
|
2943
3103
|
* @returns The rule listener.
|
|
2944
3104
|
*/
|
|
2945
|
-
function createOnce$
|
|
3105
|
+
function createOnce$7(context) {
|
|
2946
3106
|
return { ArrowFunctionExpression(node) {
|
|
2947
3107
|
const { parent } = node;
|
|
2948
3108
|
if (parent.type !== AST_NODE_TYPES.ExportDefaultDeclaration) return;
|
|
@@ -2959,8 +3119,8 @@ function createOnce$6(context) {
|
|
|
2959
3119
|
} };
|
|
2960
3120
|
}
|
|
2961
3121
|
const noExportDefaultArrow = createFlawlessRule({
|
|
2962
|
-
name: RULE_NAME$
|
|
2963
|
-
createOnce: createOnce$
|
|
3122
|
+
name: RULE_NAME$11,
|
|
3123
|
+
createOnce: createOnce$7,
|
|
2964
3124
|
defaultOptions: [],
|
|
2965
3125
|
meta: {
|
|
2966
3126
|
docs: {
|
|
@@ -2970,7 +3130,7 @@ const noExportDefaultArrow = createFlawlessRule({
|
|
|
2970
3130
|
},
|
|
2971
3131
|
fixable: "code",
|
|
2972
3132
|
hasSuggestions: false,
|
|
2973
|
-
messages: messages$
|
|
3133
|
+
messages: messages$11,
|
|
2974
3134
|
schema: [],
|
|
2975
3135
|
type: "suggestion"
|
|
2976
3136
|
}
|
|
@@ -3135,7 +3295,7 @@ function flatten(file, context) {
|
|
|
3135
3295
|
}
|
|
3136
3296
|
//#endregion
|
|
3137
3297
|
//#region src/rules/no-redundant-tsconfig-options/rule.ts
|
|
3138
|
-
const RULE_NAME$
|
|
3298
|
+
const RULE_NAME$10 = "no-redundant-tsconfig-options";
|
|
3139
3299
|
const MESSAGE_ID$5 = "redundant";
|
|
3140
3300
|
/**
|
|
3141
3301
|
* Compiler options TypeScript resolves case-insensitively; a child that re-sets
|
|
@@ -3171,7 +3331,7 @@ const PATH_KEYS = /* @__PURE__ */ new Set([
|
|
|
3171
3331
|
"tsBuildInfoFile",
|
|
3172
3332
|
"typeRoots"
|
|
3173
3333
|
]);
|
|
3174
|
-
const messages$
|
|
3334
|
+
const messages$10 = { [MESSAGE_ID$5]: "'{{option}}' is redundant: it is already set to this value by {{source}}. Remove it." };
|
|
3175
3335
|
/**
|
|
3176
3336
|
* Structural equality between two static JSON values. Strings compare
|
|
3177
3337
|
* case-insensitively when `caseInsensitive` is set (used for enum-valued
|
|
@@ -3343,7 +3503,7 @@ function create$3(context) {
|
|
|
3343
3503
|
} };
|
|
3344
3504
|
}
|
|
3345
3505
|
const noRedundantTsconfigOptions = createEslintRule({
|
|
3346
|
-
name: RULE_NAME$
|
|
3506
|
+
name: RULE_NAME$10,
|
|
3347
3507
|
create: create$3,
|
|
3348
3508
|
defaultOptions: [],
|
|
3349
3509
|
meta: {
|
|
@@ -3354,7 +3514,7 @@ const noRedundantTsconfigOptions = createEslintRule({
|
|
|
3354
3514
|
},
|
|
3355
3515
|
fixable: "code",
|
|
3356
3516
|
hasSuggestions: false,
|
|
3357
|
-
messages: messages$
|
|
3517
|
+
messages: messages$10,
|
|
3358
3518
|
schema: [],
|
|
3359
3519
|
type: "suggestion"
|
|
3360
3520
|
}
|
|
@@ -3597,15 +3757,15 @@ function resolveFactory(sourceCode, node) {
|
|
|
3597
3757
|
}
|
|
3598
3758
|
//#endregion
|
|
3599
3759
|
//#region src/rules/no-unnecessary-use-callback/rule.ts
|
|
3600
|
-
const RULE_NAME$
|
|
3760
|
+
const RULE_NAME$9 = "no-unnecessary-use-callback";
|
|
3601
3761
|
const MESSAGE_ID_DEFAULT$1 = "default";
|
|
3602
3762
|
const MESSAGE_ID_INSIDE_USE_EFFECT$1 = "noUnnecessaryUseCallbackInsideUseEffect";
|
|
3603
|
-
const messages$
|
|
3763
|
+
const messages$9 = {
|
|
3604
3764
|
[MESSAGE_ID_DEFAULT$1]: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
|
|
3605
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."
|
|
3606
3766
|
};
|
|
3607
3767
|
const noUnnecessaryUseCallback = createFlawlessRule({
|
|
3608
|
-
name: RULE_NAME$
|
|
3768
|
+
name: RULE_NAME$9,
|
|
3609
3769
|
createOnce: createUnnecessaryHookRule({
|
|
3610
3770
|
hook: "useCallback",
|
|
3611
3771
|
messageIds: {
|
|
@@ -3621,22 +3781,22 @@ const noUnnecessaryUseCallback = createFlawlessRule({
|
|
|
3621
3781
|
requiresTypeChecking: false
|
|
3622
3782
|
},
|
|
3623
3783
|
hasSuggestions: false,
|
|
3624
|
-
messages: messages$
|
|
3784
|
+
messages: messages$9,
|
|
3625
3785
|
schema: [],
|
|
3626
3786
|
type: "suggestion"
|
|
3627
3787
|
}
|
|
3628
3788
|
});
|
|
3629
3789
|
//#endregion
|
|
3630
3790
|
//#region src/rules/no-unnecessary-use-memo/rule.ts
|
|
3631
|
-
const RULE_NAME$
|
|
3791
|
+
const RULE_NAME$8 = "no-unnecessary-use-memo";
|
|
3632
3792
|
const MESSAGE_ID_DEFAULT = "default";
|
|
3633
3793
|
const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
|
|
3634
|
-
const messages$
|
|
3794
|
+
const messages$8 = {
|
|
3635
3795
|
[MESSAGE_ID_DEFAULT]: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
|
|
3636
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."
|
|
3637
3797
|
};
|
|
3638
3798
|
const noUnnecessaryUseMemo = createFlawlessRule({
|
|
3639
|
-
name: RULE_NAME$
|
|
3799
|
+
name: RULE_NAME$8,
|
|
3640
3800
|
createOnce: createUnnecessaryHookRule({
|
|
3641
3801
|
hook: "useMemo",
|
|
3642
3802
|
messageIds: {
|
|
@@ -3652,16 +3812,16 @@ const noUnnecessaryUseMemo = createFlawlessRule({
|
|
|
3652
3812
|
requiresTypeChecking: false
|
|
3653
3813
|
},
|
|
3654
3814
|
hasSuggestions: false,
|
|
3655
|
-
messages: messages$
|
|
3815
|
+
messages: messages$8,
|
|
3656
3816
|
schema: [],
|
|
3657
3817
|
type: "suggestion"
|
|
3658
3818
|
}
|
|
3659
3819
|
});
|
|
3660
3820
|
//#endregion
|
|
3661
3821
|
//#region src/rules/padding-after-expect-assertions/rule.ts
|
|
3662
|
-
const RULE_NAME$
|
|
3822
|
+
const RULE_NAME$7 = "padding-after-expect-assertions";
|
|
3663
3823
|
const MESSAGE_ID$4 = "missingPadding";
|
|
3664
|
-
const messages$
|
|
3824
|
+
const messages$7 = { [MESSAGE_ID$4]: "Expected a blank line after '{{name}}'." };
|
|
3665
3825
|
/**
|
|
3666
3826
|
* Matches a statement that declares the expected assertion count at the top of
|
|
3667
3827
|
* a test: `expect.assertions(n)` or `expect.hasAssertions()`.
|
|
@@ -3736,7 +3896,7 @@ function findPaddingAnchor(sourceCode, node, nextNode) {
|
|
|
3736
3896
|
* @param context - The rule context.
|
|
3737
3897
|
* @returns The rule listener.
|
|
3738
3898
|
*/
|
|
3739
|
-
function createOnce$
|
|
3899
|
+
function createOnce$4(context) {
|
|
3740
3900
|
return { ExpressionStatement(node) {
|
|
3741
3901
|
const name = getAssertionName(node);
|
|
3742
3902
|
if (name === void 0) return;
|
|
@@ -3761,8 +3921,8 @@ function createOnce$3(context) {
|
|
|
3761
3921
|
} };
|
|
3762
3922
|
}
|
|
3763
3923
|
const paddingAfterExpectAssertions = createFlawlessRule({
|
|
3764
|
-
name: RULE_NAME$
|
|
3765
|
-
createOnce: createOnce$
|
|
3924
|
+
name: RULE_NAME$7,
|
|
3925
|
+
createOnce: createOnce$4,
|
|
3766
3926
|
defaultOptions: [],
|
|
3767
3927
|
meta: {
|
|
3768
3928
|
docs: {
|
|
@@ -3772,16 +3932,16 @@ const paddingAfterExpectAssertions = createFlawlessRule({
|
|
|
3772
3932
|
},
|
|
3773
3933
|
fixable: "whitespace",
|
|
3774
3934
|
hasSuggestions: false,
|
|
3775
|
-
messages: messages$
|
|
3935
|
+
messages: messages$7,
|
|
3776
3936
|
schema: [],
|
|
3777
3937
|
type: "layout"
|
|
3778
3938
|
}
|
|
3779
3939
|
});
|
|
3780
3940
|
//#endregion
|
|
3781
3941
|
//#region src/rules/prefer-destructuring-assignment/rule.ts
|
|
3782
|
-
const RULE_NAME$
|
|
3942
|
+
const RULE_NAME$6 = "prefer-destructuring-assignment";
|
|
3783
3943
|
const MESSAGE_ID$3 = "default";
|
|
3784
|
-
const messages$
|
|
3944
|
+
const messages$6 = { [MESSAGE_ID$3]: "Use destructuring assignment for component props." };
|
|
3785
3945
|
/**
|
|
3786
3946
|
* Identifiers that cannot be used as a binding name in strict-mode module code.
|
|
3787
3947
|
* A property may be accessed with such a name (`props.default`), but a shorthand
|
|
@@ -3943,7 +4103,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
3943
4103
|
* @param context - The rule context.
|
|
3944
4104
|
* @returns The rule listener.
|
|
3945
4105
|
*/
|
|
3946
|
-
function createOnce$
|
|
4106
|
+
function createOnce$3(context) {
|
|
3947
4107
|
let collector = core.getFunctionComponentCollector(context);
|
|
3948
4108
|
const selectorKeys = Object.keys(collector.visitor);
|
|
3949
4109
|
const listener = {
|
|
@@ -3969,8 +4129,8 @@ function createOnce$2(context) {
|
|
|
3969
4129
|
return listener;
|
|
3970
4130
|
}
|
|
3971
4131
|
const preferDestructuringAssignment = createFlawlessRule({
|
|
3972
|
-
name: RULE_NAME$
|
|
3973
|
-
createOnce: createOnce$
|
|
4132
|
+
name: RULE_NAME$6,
|
|
4133
|
+
createOnce: createOnce$3,
|
|
3974
4134
|
defaultOptions: [],
|
|
3975
4135
|
meta: {
|
|
3976
4136
|
docs: {
|
|
@@ -3980,16 +4140,16 @@ const preferDestructuringAssignment = createFlawlessRule({
|
|
|
3980
4140
|
},
|
|
3981
4141
|
fixable: "code",
|
|
3982
4142
|
hasSuggestions: false,
|
|
3983
|
-
messages: messages$
|
|
4143
|
+
messages: messages$6,
|
|
3984
4144
|
schema: [],
|
|
3985
4145
|
type: "problem"
|
|
3986
4146
|
}
|
|
3987
4147
|
});
|
|
3988
4148
|
//#endregion
|
|
3989
4149
|
//#region src/rules/prefer-parameter-destructuring/rule.ts
|
|
3990
|
-
const RULE_NAME$
|
|
4150
|
+
const RULE_NAME$5 = "prefer-parameter-destructuring";
|
|
3991
4151
|
const MESSAGE_ID$2 = "default";
|
|
3992
|
-
const messages$
|
|
4152
|
+
const messages$5 = { [MESSAGE_ID$2]: "Destructure parameter '{{name}}' in the function signature instead of the body." };
|
|
3993
4153
|
/**
|
|
3994
4154
|
* Collects the binding names introduced by a pattern (or parameter), including
|
|
3995
4155
|
* names nested in object/array patterns, defaults, and rest elements.
|
|
@@ -4264,7 +4424,7 @@ function planFix(query) {
|
|
|
4264
4424
|
* @param context - The rule context.
|
|
4265
4425
|
* @returns The rule listener.
|
|
4266
4426
|
*/
|
|
4267
|
-
function createOnce$
|
|
4427
|
+
function createOnce$2(context) {
|
|
4268
4428
|
let allowSideEffectReordering = true;
|
|
4269
4429
|
function checkFunction(node) {
|
|
4270
4430
|
const { body, params } = node;
|
|
@@ -4315,8 +4475,8 @@ function createOnce$1(context) {
|
|
|
4315
4475
|
};
|
|
4316
4476
|
}
|
|
4317
4477
|
const preferParameterDestructuring = createFlawlessRule({
|
|
4318
|
-
name: RULE_NAME$
|
|
4319
|
-
createOnce: createOnce$
|
|
4478
|
+
name: RULE_NAME$5,
|
|
4479
|
+
createOnce: createOnce$2,
|
|
4320
4480
|
defaultOptions: [{ allowSideEffectReordering: true }],
|
|
4321
4481
|
meta: {
|
|
4322
4482
|
defaultOptions: [{ allowSideEffectReordering: true }],
|
|
@@ -4327,7 +4487,7 @@ const preferParameterDestructuring = createFlawlessRule({
|
|
|
4327
4487
|
},
|
|
4328
4488
|
fixable: "code",
|
|
4329
4489
|
hasSuggestions: false,
|
|
4330
|
-
messages: messages$
|
|
4490
|
+
messages: messages$5,
|
|
4331
4491
|
schema: [{
|
|
4332
4492
|
additionalProperties: false,
|
|
4333
4493
|
properties: { allowSideEffectReordering: {
|
|
@@ -4609,9 +4769,9 @@ function isReadonlyPropertiesProperty(checker, type, property) {
|
|
|
4609
4769
|
}
|
|
4610
4770
|
//#endregion
|
|
4611
4771
|
//#region src/rules/prefer-read-only-props/rule.ts
|
|
4612
|
-
const RULE_NAME$
|
|
4772
|
+
const RULE_NAME$4 = "prefer-read-only-props";
|
|
4613
4773
|
const DEFAULT_WRAPPER_TYPE = "Readonly";
|
|
4614
|
-
const messages$
|
|
4774
|
+
const messages$4 = { preferReadOnlyProps: "A function component's props should be read-only." };
|
|
4615
4775
|
/** Names of the `React.forwardRef` wrapper whose props are its second type argument. */
|
|
4616
4776
|
const FORWARD_REF_NAMES = /* @__PURE__ */ new Set(["forwardRef"]);
|
|
4617
4777
|
/** Names of the `React.memo` wrapper whose props are its first type argument. */
|
|
@@ -4820,7 +4980,7 @@ function create$2(context) {
|
|
|
4820
4980
|
};
|
|
4821
4981
|
}
|
|
4822
4982
|
const preferReadOnlyProps = createEslintRule({
|
|
4823
|
-
name: RULE_NAME$
|
|
4983
|
+
name: RULE_NAME$4,
|
|
4824
4984
|
create: create$2,
|
|
4825
4985
|
defaultOptions: [{}],
|
|
4826
4986
|
meta: {
|
|
@@ -4832,7 +4992,7 @@ const preferReadOnlyProps = createEslintRule({
|
|
|
4832
4992
|
},
|
|
4833
4993
|
fixable: "code",
|
|
4834
4994
|
hasSuggestions: false,
|
|
4835
|
-
messages: messages$
|
|
4995
|
+
messages: messages$4,
|
|
4836
4996
|
schema: [{
|
|
4837
4997
|
additionalProperties: false,
|
|
4838
4998
|
properties: {
|
|
@@ -4857,7 +5017,7 @@ const preferReadOnlyProps = createEslintRule({
|
|
|
4857
5017
|
});
|
|
4858
5018
|
//#endregion
|
|
4859
5019
|
//#region src/rules/purity/rule.ts
|
|
4860
|
-
const RULE_NAME$
|
|
5020
|
+
const RULE_NAME$3 = "purity";
|
|
4861
5021
|
const MESSAGE_ID$1 = "impureCall";
|
|
4862
5022
|
/**
|
|
4863
5023
|
* Non-deterministic Luau / Roblox calls, written as dotted paths matching how
|
|
@@ -4890,7 +5050,7 @@ const GUARDED_GLOBALS = /* @__PURE__ */ new Set([
|
|
|
4890
5050
|
"tick",
|
|
4891
5051
|
"time"
|
|
4892
5052
|
]);
|
|
4893
|
-
const messages$
|
|
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." };
|
|
4894
5054
|
const schema$1 = [{
|
|
4895
5055
|
additionalProperties: false,
|
|
4896
5056
|
properties: {
|
|
@@ -4990,7 +5150,7 @@ function isRenderContext(reactContext, func) {
|
|
|
4990
5150
|
const { parent } = func;
|
|
4991
5151
|
return parent.type === AST_NODE_TYPES.CallExpression && parent.arguments[0] === func && core.isUseMemoCall(reactContext, parent);
|
|
4992
5152
|
}
|
|
4993
|
-
function createOnce(context) {
|
|
5153
|
+
function createOnce$1(context) {
|
|
4994
5154
|
const reactContext = context;
|
|
4995
5155
|
let signatures;
|
|
4996
5156
|
function handle(node, match, path) {
|
|
@@ -5023,8 +5183,8 @@ function createOnce(context) {
|
|
|
5023
5183
|
};
|
|
5024
5184
|
}
|
|
5025
5185
|
const purity = createFlawlessRule({
|
|
5026
|
-
name: RULE_NAME$
|
|
5027
|
-
createOnce,
|
|
5186
|
+
name: RULE_NAME$3,
|
|
5187
|
+
createOnce: createOnce$1,
|
|
5028
5188
|
defaultOptions: [{}],
|
|
5029
5189
|
meta: {
|
|
5030
5190
|
defaultOptions: [{}],
|
|
@@ -5034,12 +5194,314 @@ const purity = createFlawlessRule({
|
|
|
5034
5194
|
requiresTypeChecking: false
|
|
5035
5195
|
},
|
|
5036
5196
|
hasSuggestions: false,
|
|
5037
|
-
messages: messages$
|
|
5197
|
+
messages: messages$3,
|
|
5038
5198
|
schema: schema$1,
|
|
5039
5199
|
type: "problem"
|
|
5040
5200
|
}
|
|
5041
5201
|
});
|
|
5042
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
|
|
5043
5505
|
//#region src/rules/toml-sort-keys/rule.ts
|
|
5044
5506
|
const RULE_NAME$1 = "toml-sort-keys";
|
|
5045
5507
|
const MESSAGE_ID = "unsorted";
|
|
@@ -5352,6 +5814,7 @@ const plugin = {
|
|
|
5352
5814
|
"prefer-parameter-destructuring": preferParameterDestructuring,
|
|
5353
5815
|
"prefer-read-only-props": preferReadOnlyProps,
|
|
5354
5816
|
"purity": purity,
|
|
5817
|
+
"react-namespace": reactNamespace,
|
|
5355
5818
|
"toml-sort-keys": tomlSortKeys,
|
|
5356
5819
|
"yaml-block-key-blank-lines": yamlBlockKeyBlankLines
|
|
5357
5820
|
}
|