@fictjs/eslint-plugin 0.0.13 → 0.0.15
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/dist/index.cjs +484 -76
- package/dist/index.d.cts +30 -8
- package/dist/index.d.ts +30 -8
- package/dist/index.js +480 -76
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
default: () => index_default,
|
|
24
|
+
noComputedPropsKey: () => no_computed_props_key_default,
|
|
24
25
|
noDirectMutation: () => no_direct_mutation_default,
|
|
25
26
|
noEmptyEffect: () => no_empty_effect_default,
|
|
26
27
|
noInlineFunctions: () => no_inline_functions_default,
|
|
@@ -29,13 +30,49 @@ __export(index_exports, {
|
|
|
29
30
|
noStateDestructureWrite: () => no_state_destructure_write_default,
|
|
30
31
|
noStateInLoop: () => no_state_in_loop_default,
|
|
31
32
|
noStateOutsideComponent: () => no_state_outside_component_default,
|
|
33
|
+
noThirdPartyPropsSpread: () => no_third_party_props_spread_default,
|
|
34
|
+
noUnsafePropsSpread: () => no_unsafe_props_spread_default,
|
|
35
|
+
noUnsupportedPropsDestructure: () => no_unsupported_props_destructure_default,
|
|
32
36
|
requireComponentReturn: () => require_component_return_default,
|
|
33
37
|
requireListKey: () => require_list_key_default
|
|
34
38
|
});
|
|
35
39
|
module.exports = __toCommonJS(index_exports);
|
|
36
40
|
|
|
37
|
-
// src/rules/no-
|
|
41
|
+
// src/rules/no-computed-props-key.ts
|
|
38
42
|
var rule = {
|
|
43
|
+
meta: {
|
|
44
|
+
type: "suggestion",
|
|
45
|
+
docs: {
|
|
46
|
+
description: "Disallow computed keys in JSX props object spreads",
|
|
47
|
+
recommended: true
|
|
48
|
+
},
|
|
49
|
+
messages: {
|
|
50
|
+
computedKey: "Computed props key may break fine-grained reactivity; use explicit props."
|
|
51
|
+
},
|
|
52
|
+
schema: []
|
|
53
|
+
},
|
|
54
|
+
create(context) {
|
|
55
|
+
return {
|
|
56
|
+
JSXSpreadAttribute(node) {
|
|
57
|
+
const expr = node.argument;
|
|
58
|
+
if (!expr || expr.type !== "ObjectExpression") return;
|
|
59
|
+
for (const prop of expr.properties ?? []) {
|
|
60
|
+
if (prop.type === "Property" && prop.computed) {
|
|
61
|
+
context.report({
|
|
62
|
+
node: prop,
|
|
63
|
+
messageId: "computedKey"
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var no_computed_props_key_default = rule;
|
|
73
|
+
|
|
74
|
+
// src/rules/no-direct-mutation.ts
|
|
75
|
+
var rule2 = {
|
|
39
76
|
meta: {
|
|
40
77
|
type: "suggestion",
|
|
41
78
|
docs: {
|
|
@@ -87,10 +124,10 @@ function isDeepAccess(node) {
|
|
|
87
124
|
}
|
|
88
125
|
return depth > 1;
|
|
89
126
|
}
|
|
90
|
-
var no_direct_mutation_default =
|
|
127
|
+
var no_direct_mutation_default = rule2;
|
|
91
128
|
|
|
92
129
|
// src/rules/no-empty-effect.ts
|
|
93
|
-
var
|
|
130
|
+
var rule3 = {
|
|
94
131
|
meta: {
|
|
95
132
|
type: "suggestion",
|
|
96
133
|
docs: {
|
|
@@ -215,10 +252,10 @@ var rule2 = {
|
|
|
215
252
|
};
|
|
216
253
|
}
|
|
217
254
|
};
|
|
218
|
-
var no_empty_effect_default =
|
|
255
|
+
var no_empty_effect_default = rule3;
|
|
219
256
|
|
|
220
257
|
// src/rules/no-inline-functions.ts
|
|
221
|
-
var
|
|
258
|
+
var rule4 = {
|
|
222
259
|
meta: {
|
|
223
260
|
type: "suggestion",
|
|
224
261
|
docs: {
|
|
@@ -268,10 +305,10 @@ var rule3 = {
|
|
|
268
305
|
};
|
|
269
306
|
}
|
|
270
307
|
};
|
|
271
|
-
var no_inline_functions_default =
|
|
308
|
+
var no_inline_functions_default = rule4;
|
|
272
309
|
|
|
273
310
|
// src/rules/no-memo-side-effects.ts
|
|
274
|
-
var
|
|
311
|
+
var rule5 = {
|
|
275
312
|
meta: {
|
|
276
313
|
type: "problem",
|
|
277
314
|
docs: {
|
|
@@ -331,10 +368,10 @@ var rule4 = {
|
|
|
331
368
|
};
|
|
332
369
|
}
|
|
333
370
|
};
|
|
334
|
-
var no_memo_side_effects_default =
|
|
371
|
+
var no_memo_side_effects_default = rule5;
|
|
335
372
|
|
|
336
373
|
// src/rules/no-nested-components.ts
|
|
337
|
-
var
|
|
374
|
+
var rule6 = {
|
|
338
375
|
meta: {
|
|
339
376
|
type: "problem",
|
|
340
377
|
docs: {
|
|
@@ -425,66 +462,7 @@ var rule5 = {
|
|
|
425
462
|
};
|
|
426
463
|
}
|
|
427
464
|
};
|
|
428
|
-
var no_nested_components_default =
|
|
429
|
-
|
|
430
|
-
// src/rules/no-state-in-loop.ts
|
|
431
|
-
var rule6 = {
|
|
432
|
-
meta: {
|
|
433
|
-
type: "problem",
|
|
434
|
-
docs: {
|
|
435
|
-
description: "Disallow $state declarations inside loops",
|
|
436
|
-
recommended: true
|
|
437
|
-
},
|
|
438
|
-
messages: {
|
|
439
|
-
noStateInLoop: "$state should not be declared inside a loop. Move it outside the loop."
|
|
440
|
-
},
|
|
441
|
-
schema: []
|
|
442
|
-
},
|
|
443
|
-
create(context) {
|
|
444
|
-
let loopDepth = 0;
|
|
445
|
-
return {
|
|
446
|
-
ForStatement() {
|
|
447
|
-
loopDepth++;
|
|
448
|
-
},
|
|
449
|
-
"ForStatement:exit"() {
|
|
450
|
-
loopDepth--;
|
|
451
|
-
},
|
|
452
|
-
ForInStatement() {
|
|
453
|
-
loopDepth++;
|
|
454
|
-
},
|
|
455
|
-
"ForInStatement:exit"() {
|
|
456
|
-
loopDepth--;
|
|
457
|
-
},
|
|
458
|
-
ForOfStatement() {
|
|
459
|
-
loopDepth++;
|
|
460
|
-
},
|
|
461
|
-
"ForOfStatement:exit"() {
|
|
462
|
-
loopDepth--;
|
|
463
|
-
},
|
|
464
|
-
WhileStatement() {
|
|
465
|
-
loopDepth++;
|
|
466
|
-
},
|
|
467
|
-
"WhileStatement:exit"() {
|
|
468
|
-
loopDepth--;
|
|
469
|
-
},
|
|
470
|
-
DoWhileStatement() {
|
|
471
|
-
loopDepth++;
|
|
472
|
-
},
|
|
473
|
-
"DoWhileStatement:exit"() {
|
|
474
|
-
loopDepth--;
|
|
475
|
-
},
|
|
476
|
-
CallExpression(node) {
|
|
477
|
-
if (loopDepth > 0 && node.callee.type === "Identifier" && node.callee.name === "$state") {
|
|
478
|
-
context.report({
|
|
479
|
-
node,
|
|
480
|
-
messageId: "noStateInLoop"
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
var no_state_in_loop_default = rule6;
|
|
465
|
+
var no_nested_components_default = rule6;
|
|
488
466
|
|
|
489
467
|
// src/rules/no-state-destructure-write.ts
|
|
490
468
|
var rule7 = {
|
|
@@ -576,6 +554,65 @@ var rule7 = {
|
|
|
576
554
|
};
|
|
577
555
|
var no_state_destructure_write_default = rule7;
|
|
578
556
|
|
|
557
|
+
// src/rules/no-state-in-loop.ts
|
|
558
|
+
var rule8 = {
|
|
559
|
+
meta: {
|
|
560
|
+
type: "problem",
|
|
561
|
+
docs: {
|
|
562
|
+
description: "Disallow $state declarations inside loops",
|
|
563
|
+
recommended: true
|
|
564
|
+
},
|
|
565
|
+
messages: {
|
|
566
|
+
noStateInLoop: "$state should not be declared inside a loop. Move it outside the loop."
|
|
567
|
+
},
|
|
568
|
+
schema: []
|
|
569
|
+
},
|
|
570
|
+
create(context) {
|
|
571
|
+
let loopDepth = 0;
|
|
572
|
+
return {
|
|
573
|
+
ForStatement() {
|
|
574
|
+
loopDepth++;
|
|
575
|
+
},
|
|
576
|
+
"ForStatement:exit"() {
|
|
577
|
+
loopDepth--;
|
|
578
|
+
},
|
|
579
|
+
ForInStatement() {
|
|
580
|
+
loopDepth++;
|
|
581
|
+
},
|
|
582
|
+
"ForInStatement:exit"() {
|
|
583
|
+
loopDepth--;
|
|
584
|
+
},
|
|
585
|
+
ForOfStatement() {
|
|
586
|
+
loopDepth++;
|
|
587
|
+
},
|
|
588
|
+
"ForOfStatement:exit"() {
|
|
589
|
+
loopDepth--;
|
|
590
|
+
},
|
|
591
|
+
WhileStatement() {
|
|
592
|
+
loopDepth++;
|
|
593
|
+
},
|
|
594
|
+
"WhileStatement:exit"() {
|
|
595
|
+
loopDepth--;
|
|
596
|
+
},
|
|
597
|
+
DoWhileStatement() {
|
|
598
|
+
loopDepth++;
|
|
599
|
+
},
|
|
600
|
+
"DoWhileStatement:exit"() {
|
|
601
|
+
loopDepth--;
|
|
602
|
+
},
|
|
603
|
+
CallExpression(node) {
|
|
604
|
+
if (loopDepth > 0 && node.callee.type === "Identifier" && node.callee.name === "$state") {
|
|
605
|
+
context.report({
|
|
606
|
+
node,
|
|
607
|
+
messageId: "noStateInLoop"
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
var no_state_in_loop_default = rule8;
|
|
615
|
+
|
|
579
616
|
// src/rules/no-state-outside-component.ts
|
|
580
617
|
var isUpperCaseName = (name) => !!name && /^[A-Z]/.test(name);
|
|
581
618
|
var isHookName = (name) => !!name && /^use[A-Z]/.test(name);
|
|
@@ -637,7 +674,13 @@ var isInConditional = (ancestors) => ancestors.some(
|
|
|
637
674
|
"LogicalExpression"
|
|
638
675
|
].includes(ancestor.type)
|
|
639
676
|
);
|
|
640
|
-
var
|
|
677
|
+
var isDirectStateDeclaration = (node, ancestors) => {
|
|
678
|
+
const parent = ancestors[ancestors.length - 1];
|
|
679
|
+
if (!parent || parent.type !== "VariableDeclarator") return false;
|
|
680
|
+
if (parent.init !== node) return false;
|
|
681
|
+
return parent.id?.type === "Identifier";
|
|
682
|
+
};
|
|
683
|
+
var rule9 = {
|
|
641
684
|
meta: {
|
|
642
685
|
type: "problem",
|
|
643
686
|
docs: {
|
|
@@ -647,7 +690,8 @@ var rule8 = {
|
|
|
647
690
|
messages: {
|
|
648
691
|
moduleScope: "$state must be declared inside a component or hook function body (not at module scope).",
|
|
649
692
|
componentOnly: "$state should only be used inside a component function (PascalCase or JSX-returning) or a hook (useX).",
|
|
650
|
-
topLevel: "$state must be at the top level of the component or hook body (not inside conditionals or nested functions)."
|
|
693
|
+
topLevel: "$state must be at the top level of the component or hook body (not inside conditionals or nested functions).",
|
|
694
|
+
declarationOnly: "$state() must be assigned directly to a variable (e.g. let count = $state(0))."
|
|
651
695
|
},
|
|
652
696
|
schema: []
|
|
653
697
|
},
|
|
@@ -656,6 +700,10 @@ var rule8 = {
|
|
|
656
700
|
CallExpression(node) {
|
|
657
701
|
if (node.callee.type !== "Identifier" || node.callee.name !== "$state") return;
|
|
658
702
|
const ancestors = context.sourceCode.getAncestors(node);
|
|
703
|
+
if (!isDirectStateDeclaration(node, ancestors)) {
|
|
704
|
+
context.report({ node, messageId: "declarationOnly" });
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
659
707
|
const functionAncestors = ancestors.filter(
|
|
660
708
|
(ancestor) => ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(
|
|
661
709
|
ancestor.type
|
|
@@ -681,10 +729,358 @@ var rule8 = {
|
|
|
681
729
|
};
|
|
682
730
|
}
|
|
683
731
|
};
|
|
684
|
-
var no_state_outside_component_default =
|
|
732
|
+
var no_state_outside_component_default = rule9;
|
|
733
|
+
|
|
734
|
+
// src/rules/no-third-party-props-spread.ts
|
|
735
|
+
var rule10 = {
|
|
736
|
+
meta: {
|
|
737
|
+
type: "suggestion",
|
|
738
|
+
docs: {
|
|
739
|
+
description: "Warn on third-party object spreads in JSX props",
|
|
740
|
+
recommended: true
|
|
741
|
+
},
|
|
742
|
+
messages: {
|
|
743
|
+
thirdPartySpread: "Spreading third-party objects into props may hide reactive changes; prefer explicit props or map fields."
|
|
744
|
+
},
|
|
745
|
+
schema: [
|
|
746
|
+
{
|
|
747
|
+
type: "object",
|
|
748
|
+
properties: {
|
|
749
|
+
includeCallExpressions: {
|
|
750
|
+
type: "boolean",
|
|
751
|
+
description: "Also warn when JSX spread comes from a third-party call expression."
|
|
752
|
+
},
|
|
753
|
+
allow: {
|
|
754
|
+
type: "array",
|
|
755
|
+
items: { type: "string" },
|
|
756
|
+
description: "Module specifiers to treat as internal."
|
|
757
|
+
},
|
|
758
|
+
internalPrefixes: {
|
|
759
|
+
type: "array",
|
|
760
|
+
items: { type: "string" },
|
|
761
|
+
description: 'Import path prefixes to treat as internal (e.g. "@/", "~/" ).'
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
additionalProperties: false
|
|
765
|
+
}
|
|
766
|
+
]
|
|
767
|
+
},
|
|
768
|
+
create(context) {
|
|
769
|
+
const options = context.options[0] || {};
|
|
770
|
+
const allow = new Set(options.allow ?? []);
|
|
771
|
+
const internalPrefixes = options.internalPrefixes ?? [];
|
|
772
|
+
const includeCallExpressions = options.includeCallExpressions === true;
|
|
773
|
+
const thirdPartyImports = /* @__PURE__ */ new Set();
|
|
774
|
+
const isThirdPartySource = (source) => {
|
|
775
|
+
if (allow.has(source)) return false;
|
|
776
|
+
if (source.startsWith(".") || source.startsWith("/")) return false;
|
|
777
|
+
if (internalPrefixes.some((prefix) => source.startsWith(prefix))) return false;
|
|
778
|
+
return true;
|
|
779
|
+
};
|
|
780
|
+
const isComponentName = (name) => {
|
|
781
|
+
if (name.type === "JSXIdentifier") {
|
|
782
|
+
return /^[A-Z]/.test(name.name);
|
|
783
|
+
}
|
|
784
|
+
if (name.type === "JSXMemberExpression") {
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
return false;
|
|
788
|
+
};
|
|
789
|
+
const unwrapExpression = (expr) => {
|
|
790
|
+
let current = expr;
|
|
791
|
+
while (current) {
|
|
792
|
+
if (current.type === "ChainExpression") {
|
|
793
|
+
current = current.expression;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (current.type === "TSAsExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression") {
|
|
797
|
+
current = current.expression;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
return current;
|
|
803
|
+
};
|
|
804
|
+
const getRootIdentifierName = (expr) => {
|
|
805
|
+
let current = unwrapExpression(expr);
|
|
806
|
+
while (current) {
|
|
807
|
+
if (current.type === "Identifier") return current.name;
|
|
808
|
+
if (current.type === "MemberExpression" || current.type === "OptionalMemberExpression") {
|
|
809
|
+
current = unwrapExpression(current.object);
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (includeCallExpressions && (current.type === "CallExpression" || current.type === "OptionalCallExpression")) {
|
|
813
|
+
current = unwrapExpression(current.callee);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
};
|
|
820
|
+
return {
|
|
821
|
+
ImportDeclaration(node) {
|
|
822
|
+
if (!node.source?.value || typeof node.source.value !== "string") return;
|
|
823
|
+
if (!isThirdPartySource(node.source.value)) return;
|
|
824
|
+
for (const spec of node.specifiers ?? []) {
|
|
825
|
+
if (spec.local?.name) {
|
|
826
|
+
thirdPartyImports.add(spec.local.name);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
JSXOpeningElement(node) {
|
|
831
|
+
if (!isComponentName(node.name)) return;
|
|
832
|
+
for (const attr of node.attributes ?? []) {
|
|
833
|
+
if (attr.type !== "JSXSpreadAttribute") continue;
|
|
834
|
+
const expr = attr.argument;
|
|
835
|
+
if (!expr) continue;
|
|
836
|
+
if (expr.type === "CallExpression" && expr.callee?.type === "Identifier" && expr.callee.name === "mergeProps") {
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const root = getRootIdentifierName(expr);
|
|
840
|
+
if (root && thirdPartyImports.has(root)) {
|
|
841
|
+
context.report({
|
|
842
|
+
node: attr,
|
|
843
|
+
messageId: "thirdPartySpread"
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
var no_third_party_props_spread_default = rule10;
|
|
852
|
+
|
|
853
|
+
// src/rules/no-unsafe-props-spread.ts
|
|
854
|
+
var rule11 = {
|
|
855
|
+
meta: {
|
|
856
|
+
type: "suggestion",
|
|
857
|
+
docs: {
|
|
858
|
+
description: "Warn on JSX spread sources that are too dynamic to keep props reactive",
|
|
859
|
+
recommended: true
|
|
860
|
+
},
|
|
861
|
+
messages: {
|
|
862
|
+
unsafeSpread: "JSX spread source is too dynamic to keep props reactive. Consider passing explicit props or using mergeProps(() => source)."
|
|
863
|
+
},
|
|
864
|
+
schema: [
|
|
865
|
+
{
|
|
866
|
+
type: "object",
|
|
867
|
+
properties: {
|
|
868
|
+
accessorNames: {
|
|
869
|
+
type: "array",
|
|
870
|
+
items: { type: "string" },
|
|
871
|
+
description: "Identifier names to treat as accessor functions."
|
|
872
|
+
},
|
|
873
|
+
accessorModules: {
|
|
874
|
+
type: "array",
|
|
875
|
+
items: { type: "string" },
|
|
876
|
+
description: "Module specifiers whose imports are accessor functions."
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
additionalProperties: false
|
|
880
|
+
}
|
|
881
|
+
]
|
|
882
|
+
},
|
|
883
|
+
create(context) {
|
|
884
|
+
const options = context.options[0] || {};
|
|
885
|
+
const accessorVars = new Set(options.accessorNames ?? []);
|
|
886
|
+
const accessorModules = new Set(options.accessorModules ?? []);
|
|
887
|
+
const isComponentName = (name) => {
|
|
888
|
+
if (name.type === "JSXIdentifier") {
|
|
889
|
+
return /^[A-Z]/.test(name.name);
|
|
890
|
+
}
|
|
891
|
+
if (name.type === "JSXMemberExpression") {
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
return false;
|
|
895
|
+
};
|
|
896
|
+
const recordAccessorVar = (node) => {
|
|
897
|
+
if (!node.init || node.id?.type !== "Identifier") return;
|
|
898
|
+
if (node.init.type !== "CallExpression") return;
|
|
899
|
+
if (node.init.callee?.type !== "Identifier") return;
|
|
900
|
+
const callee = node.init.callee.name;
|
|
901
|
+
if (callee === "$state" || callee === "$memo" || callee === "prop") {
|
|
902
|
+
accessorVars.add(node.id.name);
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
const isSafeAccessorExpr = (expr) => {
|
|
906
|
+
if (expr.type === "Identifier") return true;
|
|
907
|
+
if (expr.type === "CallExpression" && expr.callee?.type === "Identifier" && expr.arguments.length === 0 && accessorVars.has(expr.callee.name)) {
|
|
908
|
+
return true;
|
|
909
|
+
}
|
|
910
|
+
return false;
|
|
911
|
+
};
|
|
912
|
+
const isObviouslyDynamic = (expr) => expr.type === "ConditionalExpression" || expr.type === "LogicalExpression" || expr.type === "SequenceExpression" || expr.type === "AssignmentExpression" || expr.type === "UpdateExpression" || expr.type === "AwaitExpression" || expr.type === "NewExpression" || expr.type === "YieldExpression";
|
|
913
|
+
const hasUnsafeObjectLiteral = (obj) => {
|
|
914
|
+
for (const prop of obj.properties ?? []) {
|
|
915
|
+
if (prop.type === "SpreadElement") return true;
|
|
916
|
+
if (prop.type === "Property") {
|
|
917
|
+
if (prop.computed) return true;
|
|
918
|
+
if (prop.kind === "get" || prop.kind === "set") return true;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return false;
|
|
922
|
+
};
|
|
923
|
+
const shouldWarnForSpreadExpr = (expr) => {
|
|
924
|
+
if (isSafeAccessorExpr(expr)) return false;
|
|
925
|
+
if (expr.type === "CallExpression" && expr.callee?.type === "Identifier" && expr.callee.name === "mergeProps") {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
if (expr.type === "ObjectExpression") {
|
|
929
|
+
return hasUnsafeObjectLiteral(expr);
|
|
930
|
+
}
|
|
931
|
+
if (isObviouslyDynamic(expr)) return true;
|
|
932
|
+
if (expr.type === "CallExpression") return true;
|
|
933
|
+
if (expr.type === "MemberExpression") return true;
|
|
934
|
+
return false;
|
|
935
|
+
};
|
|
936
|
+
return {
|
|
937
|
+
ImportDeclaration(node) {
|
|
938
|
+
if (!node.source?.value || typeof node.source.value !== "string") return;
|
|
939
|
+
if (!accessorModules.has(node.source.value)) return;
|
|
940
|
+
for (const spec of node.specifiers ?? []) {
|
|
941
|
+
if (spec.local?.name) {
|
|
942
|
+
accessorVars.add(spec.local.name);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
VariableDeclarator: recordAccessorVar,
|
|
947
|
+
JSXOpeningElement(node) {
|
|
948
|
+
if (!isComponentName(node.name)) return;
|
|
949
|
+
for (const attr of node.attributes ?? []) {
|
|
950
|
+
if (attr.type !== "JSXSpreadAttribute") continue;
|
|
951
|
+
const expr = attr.argument;
|
|
952
|
+
if (!expr) continue;
|
|
953
|
+
if (shouldWarnForSpreadExpr(expr)) {
|
|
954
|
+
context.report({
|
|
955
|
+
node: attr,
|
|
956
|
+
messageId: "unsafeSpread"
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
var no_unsafe_props_spread_default = rule11;
|
|
965
|
+
|
|
966
|
+
// src/rules/no-unsupported-props-destructure.ts
|
|
967
|
+
var rule12 = {
|
|
968
|
+
meta: {
|
|
969
|
+
type: "suggestion",
|
|
970
|
+
docs: {
|
|
971
|
+
description: "Warn on unsupported props destructuring patterns in components",
|
|
972
|
+
recommended: true
|
|
973
|
+
},
|
|
974
|
+
messages: {
|
|
975
|
+
computedKey: "Computed property in props pattern cannot be made reactive.",
|
|
976
|
+
arrayRest: "Array rest in props destructuring falls back to non-reactive binding.",
|
|
977
|
+
nestedRest: "Nested props rest destructuring falls back to non-reactive binding; access props directly or use prop.",
|
|
978
|
+
nonFirstParam: "Props destructuring is only supported in the first parameter.",
|
|
979
|
+
fallback: "Props destructuring falls back to non-reactive binding."
|
|
980
|
+
},
|
|
981
|
+
schema: []
|
|
982
|
+
},
|
|
983
|
+
create(context) {
|
|
984
|
+
const isComponentName = (name) => /^[A-Z]/.test(name);
|
|
985
|
+
const getPropsPattern = (fnNode) => {
|
|
986
|
+
const params = fnNode?.params ?? [];
|
|
987
|
+
const first = params[0];
|
|
988
|
+
if (params.length > 1) {
|
|
989
|
+
params.slice(1).forEach((param) => {
|
|
990
|
+
if (!param) return;
|
|
991
|
+
const target = param.type === "AssignmentPattern" ? param.left : param;
|
|
992
|
+
if (target?.type === "ObjectPattern" || target?.type === "ArrayPattern") {
|
|
993
|
+
context.report({
|
|
994
|
+
node: target,
|
|
995
|
+
messageId: "nonFirstParam"
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
if (!first) return null;
|
|
1001
|
+
if (first.type === "ObjectPattern") return first;
|
|
1002
|
+
if (first.type === "AssignmentPattern" && first.left?.type === "ObjectPattern") {
|
|
1003
|
+
return first.left;
|
|
1004
|
+
}
|
|
1005
|
+
if (first.type === "ArrayPattern") {
|
|
1006
|
+
context.report({
|
|
1007
|
+
node: first,
|
|
1008
|
+
messageId: "fallback"
|
|
1009
|
+
});
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
return null;
|
|
1013
|
+
};
|
|
1014
|
+
const reportOnce = (node, messageId) => {
|
|
1015
|
+
context.report({
|
|
1016
|
+
node,
|
|
1017
|
+
messageId
|
|
1018
|
+
});
|
|
1019
|
+
};
|
|
1020
|
+
const walkPattern = (pattern, depth) => {
|
|
1021
|
+
if (!pattern) return;
|
|
1022
|
+
if (pattern.type === "ObjectPattern") {
|
|
1023
|
+
for (const prop of pattern.properties ?? []) {
|
|
1024
|
+
if (prop.type === "RestElement") {
|
|
1025
|
+
if (depth > 0) {
|
|
1026
|
+
reportOnce(prop, "nestedRest");
|
|
1027
|
+
}
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
if (prop.type !== "Property") {
|
|
1031
|
+
reportOnce(prop, "fallback");
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (prop.computed) {
|
|
1035
|
+
reportOnce(prop, "computedKey");
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
const value = prop.value;
|
|
1039
|
+
if (value.type === "Identifier") {
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
if (value.type === "AssignmentPattern" && value.left?.type === "Identifier") {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (value.type === "ObjectPattern") {
|
|
1046
|
+
walkPattern(value, depth + 1);
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (value.type === "ArrayPattern") {
|
|
1050
|
+
const hasRest = (value.elements ?? []).some((el) => el?.type === "RestElement");
|
|
1051
|
+
reportOnce(value, hasRest ? "arrayRest" : "fallback");
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
reportOnce(value, "fallback");
|
|
1055
|
+
}
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (pattern.type === "ArrayPattern") {
|
|
1059
|
+
reportOnce(pattern, "fallback");
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
return {
|
|
1063
|
+
FunctionDeclaration(node) {
|
|
1064
|
+
if (!node.id?.name || !isComponentName(node.id.name)) return;
|
|
1065
|
+
const pattern = getPropsPattern(node);
|
|
1066
|
+
if (pattern) walkPattern(pattern, 0);
|
|
1067
|
+
},
|
|
1068
|
+
VariableDeclarator(node) {
|
|
1069
|
+
if (!node.id || node.id.type !== "Identifier" || !isComponentName(node.id.name)) return;
|
|
1070
|
+
if (!node.init) return;
|
|
1071
|
+
if (node.init.type !== "ArrowFunctionExpression" && node.init.type !== "FunctionExpression") {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const pattern = getPropsPattern(node.init);
|
|
1075
|
+
if (pattern) walkPattern(pattern, 0);
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
var no_unsupported_props_destructure_default = rule12;
|
|
685
1081
|
|
|
686
1082
|
// src/rules/require-component-return.ts
|
|
687
|
-
var
|
|
1083
|
+
var rule13 = {
|
|
688
1084
|
meta: {
|
|
689
1085
|
type: "problem",
|
|
690
1086
|
docs: {
|
|
@@ -772,10 +1168,10 @@ var rule9 = {
|
|
|
772
1168
|
};
|
|
773
1169
|
}
|
|
774
1170
|
};
|
|
775
|
-
var require_component_return_default =
|
|
1171
|
+
var require_component_return_default = rule13;
|
|
776
1172
|
|
|
777
1173
|
// src/rules/require-list-key.ts
|
|
778
|
-
var
|
|
1174
|
+
var rule14 = {
|
|
779
1175
|
meta: {
|
|
780
1176
|
type: "problem",
|
|
781
1177
|
docs: {
|
|
@@ -866,7 +1262,7 @@ var rule10 = {
|
|
|
866
1262
|
};
|
|
867
1263
|
}
|
|
868
1264
|
};
|
|
869
|
-
var require_list_key_default =
|
|
1265
|
+
var require_list_key_default = rule14;
|
|
870
1266
|
|
|
871
1267
|
// src/index.ts
|
|
872
1268
|
var plugin = {
|
|
@@ -878,10 +1274,14 @@ var plugin = {
|
|
|
878
1274
|
"no-state-in-loop": no_state_in_loop_default,
|
|
879
1275
|
"no-direct-mutation": no_direct_mutation_default,
|
|
880
1276
|
"no-empty-effect": no_empty_effect_default,
|
|
1277
|
+
"no-computed-props-key": no_computed_props_key_default,
|
|
881
1278
|
"no-inline-functions": no_inline_functions_default,
|
|
882
1279
|
"no-state-destructure-write": no_state_destructure_write_default,
|
|
883
1280
|
"no-state-outside-component": no_state_outside_component_default,
|
|
884
1281
|
"no-nested-components": no_nested_components_default,
|
|
1282
|
+
"no-third-party-props-spread": no_third_party_props_spread_default,
|
|
1283
|
+
"no-unsafe-props-spread": no_unsafe_props_spread_default,
|
|
1284
|
+
"no-unsupported-props-destructure": no_unsupported_props_destructure_default,
|
|
885
1285
|
"require-list-key": require_list_key_default,
|
|
886
1286
|
"no-memo-side-effects": no_memo_side_effects_default,
|
|
887
1287
|
"require-component-return": require_component_return_default
|
|
@@ -894,12 +1294,16 @@ var plugin = {
|
|
|
894
1294
|
"fict/no-direct-mutation": "warn",
|
|
895
1295
|
"fict/no-empty-effect": "warn",
|
|
896
1296
|
// FICT-E001
|
|
1297
|
+
"fict/no-computed-props-key": "warn",
|
|
897
1298
|
"fict/no-inline-functions": "warn",
|
|
898
1299
|
// FICT-X003
|
|
899
1300
|
"fict/no-state-destructure-write": "error",
|
|
900
1301
|
"fict/no-state-outside-component": "error",
|
|
901
1302
|
"fict/no-nested-components": "error",
|
|
902
1303
|
// FICT-C003
|
|
1304
|
+
"fict/no-third-party-props-spread": "warn",
|
|
1305
|
+
"fict/no-unsafe-props-spread": "warn",
|
|
1306
|
+
"fict/no-unsupported-props-destructure": "warn",
|
|
903
1307
|
"fict/require-list-key": "error",
|
|
904
1308
|
// FICT-J002
|
|
905
1309
|
"fict/no-memo-side-effects": "warn",
|
|
@@ -913,6 +1317,7 @@ var plugin = {
|
|
|
913
1317
|
var index_default = plugin;
|
|
914
1318
|
// Annotate the CommonJS export names for ESM import in node:
|
|
915
1319
|
0 && (module.exports = {
|
|
1320
|
+
noComputedPropsKey,
|
|
916
1321
|
noDirectMutation,
|
|
917
1322
|
noEmptyEffect,
|
|
918
1323
|
noInlineFunctions,
|
|
@@ -921,6 +1326,9 @@ var index_default = plugin;
|
|
|
921
1326
|
noStateDestructureWrite,
|
|
922
1327
|
noStateInLoop,
|
|
923
1328
|
noStateOutsideComponent,
|
|
1329
|
+
noThirdPartyPropsSpread,
|
|
1330
|
+
noUnsafePropsSpread,
|
|
1331
|
+
noUnsupportedPropsDestructure,
|
|
924
1332
|
requireComponentReturn,
|
|
925
1333
|
requireListKey
|
|
926
1334
|
});
|