@fictjs/eslint-plugin 0.0.5 → 0.0.8
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 +129 -18
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +128 -18
- package/package.json +8 -2
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
noNestedComponents: () => no_nested_components_default,
|
|
29
29
|
noStateDestructureWrite: () => no_state_destructure_write_default,
|
|
30
30
|
noStateInLoop: () => no_state_in_loop_default,
|
|
31
|
+
noStateOutsideComponent: () => no_state_outside_component_default,
|
|
31
32
|
requireComponentReturn: () => require_component_return_default,
|
|
32
33
|
requireListKey: () => require_list_key_default
|
|
33
34
|
});
|
|
@@ -347,8 +348,8 @@ var rule5 = {
|
|
|
347
348
|
},
|
|
348
349
|
create(context) {
|
|
349
350
|
const componentStack = [];
|
|
350
|
-
const
|
|
351
|
-
const
|
|
351
|
+
const isUpperCaseName2 = (name) => !!name && /^[A-Z]/.test(name);
|
|
352
|
+
const getFunctionName2 = (node) => {
|
|
352
353
|
if (node.id?.name) {
|
|
353
354
|
return node.id.name;
|
|
354
355
|
}
|
|
@@ -358,7 +359,7 @@ var rule5 = {
|
|
|
358
359
|
}
|
|
359
360
|
return void 0;
|
|
360
361
|
};
|
|
361
|
-
const
|
|
362
|
+
const hasJSX2 = (node) => {
|
|
362
363
|
let found = false;
|
|
363
364
|
const visit = (n) => {
|
|
364
365
|
if (found) return;
|
|
@@ -386,23 +387,23 @@ var rule5 = {
|
|
|
386
387
|
visit(node);
|
|
387
388
|
return found;
|
|
388
389
|
};
|
|
389
|
-
const
|
|
390
|
-
const name =
|
|
391
|
-
if (
|
|
390
|
+
const isComponentLike2 = (node) => {
|
|
391
|
+
const name = getFunctionName2(node);
|
|
392
|
+
if (isUpperCaseName2(name)) return true;
|
|
392
393
|
if (node.type === "ArrowFunctionExpression" && node.body) {
|
|
393
394
|
const bodyType = node.body.type;
|
|
394
395
|
if (bodyType === "JSXElement" || bodyType === "JSXFragment") return true;
|
|
395
|
-
if (bodyType === "BlockStatement" &&
|
|
396
|
+
if (bodyType === "BlockStatement" && hasJSX2(node.body)) return true;
|
|
396
397
|
}
|
|
397
|
-
if (node.type !== "ArrowFunctionExpression" && node.body &&
|
|
398
|
+
if (node.type !== "ArrowFunctionExpression" && node.body && hasJSX2(node.body))
|
|
398
399
|
return true;
|
|
399
400
|
return false;
|
|
400
401
|
};
|
|
401
402
|
const enterFunction = (node) => {
|
|
402
403
|
const parentIsComponent = componentStack[componentStack.length - 1] ?? false;
|
|
403
|
-
const currentIsComponent =
|
|
404
|
+
const currentIsComponent = isComponentLike2(node);
|
|
404
405
|
if (parentIsComponent && currentIsComponent) {
|
|
405
|
-
const name =
|
|
406
|
+
const name = getFunctionName2(node) ?? "this component";
|
|
406
407
|
context.report({
|
|
407
408
|
node,
|
|
408
409
|
messageId: "nestedComponent",
|
|
@@ -575,8 +576,115 @@ var rule7 = {
|
|
|
575
576
|
};
|
|
576
577
|
var no_state_destructure_write_default = rule7;
|
|
577
578
|
|
|
578
|
-
// src/rules/
|
|
579
|
+
// src/rules/no-state-outside-component.ts
|
|
580
|
+
var isUpperCaseName = (name) => !!name && /^[A-Z]/.test(name);
|
|
581
|
+
var isHookName = (name) => !!name && /^use[A-Z]/.test(name);
|
|
582
|
+
var getFunctionName = (node) => {
|
|
583
|
+
if (node.id?.type === "Identifier") {
|
|
584
|
+
return node.id.name;
|
|
585
|
+
}
|
|
586
|
+
const parent = node.parent;
|
|
587
|
+
if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier") {
|
|
588
|
+
return parent.id.name;
|
|
589
|
+
}
|
|
590
|
+
return void 0;
|
|
591
|
+
};
|
|
592
|
+
var hasJSX = (node) => {
|
|
593
|
+
let found = false;
|
|
594
|
+
const visit = (n) => {
|
|
595
|
+
if (found) return;
|
|
596
|
+
const type = n.type;
|
|
597
|
+
if (type === "JSXElement" || type === "JSXFragment") {
|
|
598
|
+
found = true;
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (type === "FunctionDeclaration" || type === "FunctionExpression" || type === "ArrowFunctionExpression") {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
for (const key of Object.keys(n)) {
|
|
605
|
+
if (key === "parent") continue;
|
|
606
|
+
const value = n[key];
|
|
607
|
+
if (!value) continue;
|
|
608
|
+
if (Array.isArray(value)) {
|
|
609
|
+
for (const child of value) {
|
|
610
|
+
if (child && typeof child.type === "string") visit(child);
|
|
611
|
+
}
|
|
612
|
+
} else if (value && typeof value.type === "string") {
|
|
613
|
+
visit(value);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
visit(node);
|
|
618
|
+
return found;
|
|
619
|
+
};
|
|
620
|
+
var isComponentLike = (node) => {
|
|
621
|
+
const name = getFunctionName(node);
|
|
622
|
+
if (isHookName(name)) return true;
|
|
623
|
+
if (isUpperCaseName(name)) return true;
|
|
624
|
+
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
625
|
+
const bodyType = node.body.type;
|
|
626
|
+
return bodyType === "JSXElement" || bodyType === "JSXFragment";
|
|
627
|
+
}
|
|
628
|
+
if (node.body && hasJSX(node.body)) return true;
|
|
629
|
+
return false;
|
|
630
|
+
};
|
|
631
|
+
var isInConditional = (ancestors) => ancestors.some(
|
|
632
|
+
(ancestor) => [
|
|
633
|
+
"IfStatement",
|
|
634
|
+
"SwitchStatement",
|
|
635
|
+
"SwitchCase",
|
|
636
|
+
"ConditionalExpression",
|
|
637
|
+
"LogicalExpression"
|
|
638
|
+
].includes(ancestor.type)
|
|
639
|
+
);
|
|
579
640
|
var rule8 = {
|
|
641
|
+
meta: {
|
|
642
|
+
type: "problem",
|
|
643
|
+
docs: {
|
|
644
|
+
description: "Require $state to be declared at the top level of a component or hook function body",
|
|
645
|
+
recommended: true
|
|
646
|
+
},
|
|
647
|
+
messages: {
|
|
648
|
+
moduleScope: "$state must be declared inside a component or hook function body (not at module scope).",
|
|
649
|
+
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)."
|
|
651
|
+
},
|
|
652
|
+
schema: []
|
|
653
|
+
},
|
|
654
|
+
create(context) {
|
|
655
|
+
return {
|
|
656
|
+
CallExpression(node) {
|
|
657
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "$state") return;
|
|
658
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
659
|
+
const functionAncestors = ancestors.filter(
|
|
660
|
+
(ancestor) => ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(
|
|
661
|
+
ancestor.type
|
|
662
|
+
)
|
|
663
|
+
);
|
|
664
|
+
if (functionAncestors.length === 0) {
|
|
665
|
+
context.report({ node, messageId: "moduleScope" });
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const nearestFunction = functionAncestors[functionAncestors.length - 1];
|
|
669
|
+
if (functionAncestors.length > 1) {
|
|
670
|
+
context.report({ node, messageId: "topLevel" });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (!nearestFunction || !isComponentLike(nearestFunction)) {
|
|
674
|
+
context.report({ node, messageId: "componentOnly" });
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (isInConditional(ancestors)) {
|
|
678
|
+
context.report({ node, messageId: "topLevel" });
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
var no_state_outside_component_default = rule8;
|
|
685
|
+
|
|
686
|
+
// src/rules/require-component-return.ts
|
|
687
|
+
var rule9 = {
|
|
580
688
|
meta: {
|
|
581
689
|
type: "problem",
|
|
582
690
|
docs: {
|
|
@@ -589,8 +697,8 @@ var rule8 = {
|
|
|
589
697
|
schema: []
|
|
590
698
|
},
|
|
591
699
|
create(context) {
|
|
592
|
-
const
|
|
593
|
-
const
|
|
700
|
+
const isUpperCaseName2 = (name) => !!name && /^[A-Z]/.test(name);
|
|
701
|
+
const getFunctionName2 = (node) => {
|
|
594
702
|
if (node.id?.name) {
|
|
595
703
|
return node.id.name;
|
|
596
704
|
}
|
|
@@ -643,8 +751,8 @@ var rule8 = {
|
|
|
643
751
|
return visit(node);
|
|
644
752
|
};
|
|
645
753
|
const enter = (node) => {
|
|
646
|
-
const name =
|
|
647
|
-
const isComponent =
|
|
754
|
+
const name = getFunctionName2(node);
|
|
755
|
+
const isComponent = isUpperCaseName2(name);
|
|
648
756
|
if (!isComponent) return;
|
|
649
757
|
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
650
758
|
return;
|
|
@@ -664,10 +772,10 @@ var rule8 = {
|
|
|
664
772
|
};
|
|
665
773
|
}
|
|
666
774
|
};
|
|
667
|
-
var require_component_return_default =
|
|
775
|
+
var require_component_return_default = rule9;
|
|
668
776
|
|
|
669
777
|
// src/rules/require-list-key.ts
|
|
670
|
-
var
|
|
778
|
+
var rule10 = {
|
|
671
779
|
meta: {
|
|
672
780
|
type: "problem",
|
|
673
781
|
docs: {
|
|
@@ -758,7 +866,7 @@ var rule9 = {
|
|
|
758
866
|
};
|
|
759
867
|
}
|
|
760
868
|
};
|
|
761
|
-
var require_list_key_default =
|
|
869
|
+
var require_list_key_default = rule10;
|
|
762
870
|
|
|
763
871
|
// src/index.ts
|
|
764
872
|
var plugin = {
|
|
@@ -772,6 +880,7 @@ var plugin = {
|
|
|
772
880
|
"no-empty-effect": no_empty_effect_default,
|
|
773
881
|
"no-inline-functions": no_inline_functions_default,
|
|
774
882
|
"no-state-destructure-write": no_state_destructure_write_default,
|
|
883
|
+
"no-state-outside-component": no_state_outside_component_default,
|
|
775
884
|
"no-nested-components": no_nested_components_default,
|
|
776
885
|
"require-list-key": require_list_key_default,
|
|
777
886
|
"no-memo-side-effects": no_memo_side_effects_default,
|
|
@@ -788,6 +897,7 @@ var plugin = {
|
|
|
788
897
|
"fict/no-inline-functions": "warn",
|
|
789
898
|
// FICT-X003
|
|
790
899
|
"fict/no-state-destructure-write": "error",
|
|
900
|
+
"fict/no-state-outside-component": "error",
|
|
791
901
|
"fict/no-nested-components": "error",
|
|
792
902
|
// FICT-C003
|
|
793
903
|
"fict/require-list-key": "error",
|
|
@@ -810,6 +920,7 @@ var index_default = plugin;
|
|
|
810
920
|
noNestedComponents,
|
|
811
921
|
noStateDestructureWrite,
|
|
812
922
|
noStateInLoop,
|
|
923
|
+
noStateOutsideComponent,
|
|
813
924
|
requireComponentReturn,
|
|
814
925
|
requireListKey
|
|
815
926
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Rule, ESLint } from 'eslint';
|
|
2
2
|
|
|
3
|
-
declare const rule$
|
|
3
|
+
declare const rule$9: Rule.RuleModule;
|
|
4
4
|
|
|
5
|
-
declare const rule$
|
|
5
|
+
declare const rule$8: Rule.RuleModule;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* ESLint rule to detect inline functions passed to JSX props.
|
|
@@ -10,14 +10,14 @@ declare const rule$7: Rule.RuleModule;
|
|
|
10
10
|
* This integrates with the compiler's validation module (FICT-X003).
|
|
11
11
|
* Inline functions can cause unnecessary re-renders in reactive frameworks.
|
|
12
12
|
*/
|
|
13
|
+
declare const rule$7: Rule.RuleModule;
|
|
14
|
+
|
|
13
15
|
declare const rule$6: Rule.RuleModule;
|
|
14
16
|
|
|
15
17
|
declare const rule$5: Rule.RuleModule;
|
|
16
18
|
|
|
17
19
|
declare const rule$4: Rule.RuleModule;
|
|
18
20
|
|
|
19
|
-
declare const rule$3: Rule.RuleModule;
|
|
20
|
-
|
|
21
21
|
/**
|
|
22
22
|
* Prevent writes to aliases created by destructuring a $state-backed object.
|
|
23
23
|
* Example:
|
|
@@ -25,6 +25,8 @@ declare const rule$3: Rule.RuleModule;
|
|
|
25
25
|
* const { count } = state // allowed (read)
|
|
26
26
|
* count++ // banned – must write via state
|
|
27
27
|
*/
|
|
28
|
+
declare const rule$3: Rule.RuleModule;
|
|
29
|
+
|
|
28
30
|
declare const rule$2: Rule.RuleModule;
|
|
29
31
|
|
|
30
32
|
declare const rule$1: Rule.RuleModule;
|
|
@@ -33,4 +35,4 @@ declare const rule: Rule.RuleModule;
|
|
|
33
35
|
|
|
34
36
|
declare const plugin: ESLint.Plugin;
|
|
35
37
|
|
|
36
|
-
export { plugin as default, rule$
|
|
38
|
+
export { plugin as default, rule$9 as noDirectMutation, rule$8 as noEmptyEffect, rule$7 as noInlineFunctions, rule$6 as noMemoSideEffects, rule$5 as noNestedComponents, rule$3 as noStateDestructureWrite, rule$4 as noStateInLoop, rule$2 as noStateOutsideComponent, rule$1 as requireComponentReturn, rule as requireListKey };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Rule, ESLint } from 'eslint';
|
|
2
2
|
|
|
3
|
-
declare const rule$
|
|
3
|
+
declare const rule$9: Rule.RuleModule;
|
|
4
4
|
|
|
5
|
-
declare const rule$
|
|
5
|
+
declare const rule$8: Rule.RuleModule;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* ESLint rule to detect inline functions passed to JSX props.
|
|
@@ -10,14 +10,14 @@ declare const rule$7: Rule.RuleModule;
|
|
|
10
10
|
* This integrates with the compiler's validation module (FICT-X003).
|
|
11
11
|
* Inline functions can cause unnecessary re-renders in reactive frameworks.
|
|
12
12
|
*/
|
|
13
|
+
declare const rule$7: Rule.RuleModule;
|
|
14
|
+
|
|
13
15
|
declare const rule$6: Rule.RuleModule;
|
|
14
16
|
|
|
15
17
|
declare const rule$5: Rule.RuleModule;
|
|
16
18
|
|
|
17
19
|
declare const rule$4: Rule.RuleModule;
|
|
18
20
|
|
|
19
|
-
declare const rule$3: Rule.RuleModule;
|
|
20
|
-
|
|
21
21
|
/**
|
|
22
22
|
* Prevent writes to aliases created by destructuring a $state-backed object.
|
|
23
23
|
* Example:
|
|
@@ -25,6 +25,8 @@ declare const rule$3: Rule.RuleModule;
|
|
|
25
25
|
* const { count } = state // allowed (read)
|
|
26
26
|
* count++ // banned – must write via state
|
|
27
27
|
*/
|
|
28
|
+
declare const rule$3: Rule.RuleModule;
|
|
29
|
+
|
|
28
30
|
declare const rule$2: Rule.RuleModule;
|
|
29
31
|
|
|
30
32
|
declare const rule$1: Rule.RuleModule;
|
|
@@ -33,4 +35,4 @@ declare const rule: Rule.RuleModule;
|
|
|
33
35
|
|
|
34
36
|
declare const plugin: ESLint.Plugin;
|
|
35
37
|
|
|
36
|
-
export { plugin as default, rule$
|
|
38
|
+
export { plugin as default, rule$9 as noDirectMutation, rule$8 as noEmptyEffect, rule$7 as noInlineFunctions, rule$6 as noMemoSideEffects, rule$5 as noNestedComponents, rule$3 as noStateDestructureWrite, rule$4 as noStateInLoop, rule$2 as noStateOutsideComponent, rule$1 as requireComponentReturn, rule as requireListKey };
|
package/dist/index.js
CHANGED
|
@@ -312,8 +312,8 @@ var rule5 = {
|
|
|
312
312
|
},
|
|
313
313
|
create(context) {
|
|
314
314
|
const componentStack = [];
|
|
315
|
-
const
|
|
316
|
-
const
|
|
315
|
+
const isUpperCaseName2 = (name) => !!name && /^[A-Z]/.test(name);
|
|
316
|
+
const getFunctionName2 = (node) => {
|
|
317
317
|
if (node.id?.name) {
|
|
318
318
|
return node.id.name;
|
|
319
319
|
}
|
|
@@ -323,7 +323,7 @@ var rule5 = {
|
|
|
323
323
|
}
|
|
324
324
|
return void 0;
|
|
325
325
|
};
|
|
326
|
-
const
|
|
326
|
+
const hasJSX2 = (node) => {
|
|
327
327
|
let found = false;
|
|
328
328
|
const visit = (n) => {
|
|
329
329
|
if (found) return;
|
|
@@ -351,23 +351,23 @@ var rule5 = {
|
|
|
351
351
|
visit(node);
|
|
352
352
|
return found;
|
|
353
353
|
};
|
|
354
|
-
const
|
|
355
|
-
const name =
|
|
356
|
-
if (
|
|
354
|
+
const isComponentLike2 = (node) => {
|
|
355
|
+
const name = getFunctionName2(node);
|
|
356
|
+
if (isUpperCaseName2(name)) return true;
|
|
357
357
|
if (node.type === "ArrowFunctionExpression" && node.body) {
|
|
358
358
|
const bodyType = node.body.type;
|
|
359
359
|
if (bodyType === "JSXElement" || bodyType === "JSXFragment") return true;
|
|
360
|
-
if (bodyType === "BlockStatement" &&
|
|
360
|
+
if (bodyType === "BlockStatement" && hasJSX2(node.body)) return true;
|
|
361
361
|
}
|
|
362
|
-
if (node.type !== "ArrowFunctionExpression" && node.body &&
|
|
362
|
+
if (node.type !== "ArrowFunctionExpression" && node.body && hasJSX2(node.body))
|
|
363
363
|
return true;
|
|
364
364
|
return false;
|
|
365
365
|
};
|
|
366
366
|
const enterFunction = (node) => {
|
|
367
367
|
const parentIsComponent = componentStack[componentStack.length - 1] ?? false;
|
|
368
|
-
const currentIsComponent =
|
|
368
|
+
const currentIsComponent = isComponentLike2(node);
|
|
369
369
|
if (parentIsComponent && currentIsComponent) {
|
|
370
|
-
const name =
|
|
370
|
+
const name = getFunctionName2(node) ?? "this component";
|
|
371
371
|
context.report({
|
|
372
372
|
node,
|
|
373
373
|
messageId: "nestedComponent",
|
|
@@ -540,8 +540,115 @@ var rule7 = {
|
|
|
540
540
|
};
|
|
541
541
|
var no_state_destructure_write_default = rule7;
|
|
542
542
|
|
|
543
|
-
// src/rules/
|
|
543
|
+
// src/rules/no-state-outside-component.ts
|
|
544
|
+
var isUpperCaseName = (name) => !!name && /^[A-Z]/.test(name);
|
|
545
|
+
var isHookName = (name) => !!name && /^use[A-Z]/.test(name);
|
|
546
|
+
var getFunctionName = (node) => {
|
|
547
|
+
if (node.id?.type === "Identifier") {
|
|
548
|
+
return node.id.name;
|
|
549
|
+
}
|
|
550
|
+
const parent = node.parent;
|
|
551
|
+
if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier") {
|
|
552
|
+
return parent.id.name;
|
|
553
|
+
}
|
|
554
|
+
return void 0;
|
|
555
|
+
};
|
|
556
|
+
var hasJSX = (node) => {
|
|
557
|
+
let found = false;
|
|
558
|
+
const visit = (n) => {
|
|
559
|
+
if (found) return;
|
|
560
|
+
const type = n.type;
|
|
561
|
+
if (type === "JSXElement" || type === "JSXFragment") {
|
|
562
|
+
found = true;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (type === "FunctionDeclaration" || type === "FunctionExpression" || type === "ArrowFunctionExpression") {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
for (const key of Object.keys(n)) {
|
|
569
|
+
if (key === "parent") continue;
|
|
570
|
+
const value = n[key];
|
|
571
|
+
if (!value) continue;
|
|
572
|
+
if (Array.isArray(value)) {
|
|
573
|
+
for (const child of value) {
|
|
574
|
+
if (child && typeof child.type === "string") visit(child);
|
|
575
|
+
}
|
|
576
|
+
} else if (value && typeof value.type === "string") {
|
|
577
|
+
visit(value);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
visit(node);
|
|
582
|
+
return found;
|
|
583
|
+
};
|
|
584
|
+
var isComponentLike = (node) => {
|
|
585
|
+
const name = getFunctionName(node);
|
|
586
|
+
if (isHookName(name)) return true;
|
|
587
|
+
if (isUpperCaseName(name)) return true;
|
|
588
|
+
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
589
|
+
const bodyType = node.body.type;
|
|
590
|
+
return bodyType === "JSXElement" || bodyType === "JSXFragment";
|
|
591
|
+
}
|
|
592
|
+
if (node.body && hasJSX(node.body)) return true;
|
|
593
|
+
return false;
|
|
594
|
+
};
|
|
595
|
+
var isInConditional = (ancestors) => ancestors.some(
|
|
596
|
+
(ancestor) => [
|
|
597
|
+
"IfStatement",
|
|
598
|
+
"SwitchStatement",
|
|
599
|
+
"SwitchCase",
|
|
600
|
+
"ConditionalExpression",
|
|
601
|
+
"LogicalExpression"
|
|
602
|
+
].includes(ancestor.type)
|
|
603
|
+
);
|
|
544
604
|
var rule8 = {
|
|
605
|
+
meta: {
|
|
606
|
+
type: "problem",
|
|
607
|
+
docs: {
|
|
608
|
+
description: "Require $state to be declared at the top level of a component or hook function body",
|
|
609
|
+
recommended: true
|
|
610
|
+
},
|
|
611
|
+
messages: {
|
|
612
|
+
moduleScope: "$state must be declared inside a component or hook function body (not at module scope).",
|
|
613
|
+
componentOnly: "$state should only be used inside a component function (PascalCase or JSX-returning) or a hook (useX).",
|
|
614
|
+
topLevel: "$state must be at the top level of the component or hook body (not inside conditionals or nested functions)."
|
|
615
|
+
},
|
|
616
|
+
schema: []
|
|
617
|
+
},
|
|
618
|
+
create(context) {
|
|
619
|
+
return {
|
|
620
|
+
CallExpression(node) {
|
|
621
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "$state") return;
|
|
622
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
623
|
+
const functionAncestors = ancestors.filter(
|
|
624
|
+
(ancestor) => ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(
|
|
625
|
+
ancestor.type
|
|
626
|
+
)
|
|
627
|
+
);
|
|
628
|
+
if (functionAncestors.length === 0) {
|
|
629
|
+
context.report({ node, messageId: "moduleScope" });
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const nearestFunction = functionAncestors[functionAncestors.length - 1];
|
|
633
|
+
if (functionAncestors.length > 1) {
|
|
634
|
+
context.report({ node, messageId: "topLevel" });
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (!nearestFunction || !isComponentLike(nearestFunction)) {
|
|
638
|
+
context.report({ node, messageId: "componentOnly" });
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (isInConditional(ancestors)) {
|
|
642
|
+
context.report({ node, messageId: "topLevel" });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
var no_state_outside_component_default = rule8;
|
|
649
|
+
|
|
650
|
+
// src/rules/require-component-return.ts
|
|
651
|
+
var rule9 = {
|
|
545
652
|
meta: {
|
|
546
653
|
type: "problem",
|
|
547
654
|
docs: {
|
|
@@ -554,8 +661,8 @@ var rule8 = {
|
|
|
554
661
|
schema: []
|
|
555
662
|
},
|
|
556
663
|
create(context) {
|
|
557
|
-
const
|
|
558
|
-
const
|
|
664
|
+
const isUpperCaseName2 = (name) => !!name && /^[A-Z]/.test(name);
|
|
665
|
+
const getFunctionName2 = (node) => {
|
|
559
666
|
if (node.id?.name) {
|
|
560
667
|
return node.id.name;
|
|
561
668
|
}
|
|
@@ -608,8 +715,8 @@ var rule8 = {
|
|
|
608
715
|
return visit(node);
|
|
609
716
|
};
|
|
610
717
|
const enter = (node) => {
|
|
611
|
-
const name =
|
|
612
|
-
const isComponent =
|
|
718
|
+
const name = getFunctionName2(node);
|
|
719
|
+
const isComponent = isUpperCaseName2(name);
|
|
613
720
|
if (!isComponent) return;
|
|
614
721
|
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
615
722
|
return;
|
|
@@ -629,10 +736,10 @@ var rule8 = {
|
|
|
629
736
|
};
|
|
630
737
|
}
|
|
631
738
|
};
|
|
632
|
-
var require_component_return_default =
|
|
739
|
+
var require_component_return_default = rule9;
|
|
633
740
|
|
|
634
741
|
// src/rules/require-list-key.ts
|
|
635
|
-
var
|
|
742
|
+
var rule10 = {
|
|
636
743
|
meta: {
|
|
637
744
|
type: "problem",
|
|
638
745
|
docs: {
|
|
@@ -723,7 +830,7 @@ var rule9 = {
|
|
|
723
830
|
};
|
|
724
831
|
}
|
|
725
832
|
};
|
|
726
|
-
var require_list_key_default =
|
|
833
|
+
var require_list_key_default = rule10;
|
|
727
834
|
|
|
728
835
|
// src/index.ts
|
|
729
836
|
var plugin = {
|
|
@@ -737,6 +844,7 @@ var plugin = {
|
|
|
737
844
|
"no-empty-effect": no_empty_effect_default,
|
|
738
845
|
"no-inline-functions": no_inline_functions_default,
|
|
739
846
|
"no-state-destructure-write": no_state_destructure_write_default,
|
|
847
|
+
"no-state-outside-component": no_state_outside_component_default,
|
|
740
848
|
"no-nested-components": no_nested_components_default,
|
|
741
849
|
"require-list-key": require_list_key_default,
|
|
742
850
|
"no-memo-side-effects": no_memo_side_effects_default,
|
|
@@ -753,6 +861,7 @@ var plugin = {
|
|
|
753
861
|
"fict/no-inline-functions": "warn",
|
|
754
862
|
// FICT-X003
|
|
755
863
|
"fict/no-state-destructure-write": "error",
|
|
864
|
+
"fict/no-state-outside-component": "error",
|
|
756
865
|
"fict/no-nested-components": "error",
|
|
757
866
|
// FICT-C003
|
|
758
867
|
"fict/require-list-key": "error",
|
|
@@ -775,6 +884,7 @@ export {
|
|
|
775
884
|
no_nested_components_default as noNestedComponents,
|
|
776
885
|
no_state_destructure_write_default as noStateDestructureWrite,
|
|
777
886
|
no_state_in_loop_default as noStateInLoop,
|
|
887
|
+
no_state_outside_component_default as noStateOutsideComponent,
|
|
778
888
|
require_component_return_default as requireComponentReturn,
|
|
779
889
|
require_list_key_default as requireListKey
|
|
780
890
|
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fictjs/eslint-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "ESLint plugin for Fict",
|
|
5
5
|
"publishConfig": {
|
|
6
|
-
"access": "public"
|
|
6
|
+
"access": "public",
|
|
7
|
+
"provenance": true
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/fictjs/fict.git",
|
|
12
|
+
"directory": "packages/eslint-plugin"
|
|
7
13
|
},
|
|
8
14
|
"type": "module",
|
|
9
15
|
"main": "dist/index.cjs",
|