@gost-kit/eslint-plugin 0.0.0-stage → 0.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +52 -2
  4. package/RULES.md +312 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +15 -0
  7. package/dist/lib/boolean-type.d.ts +2 -0
  8. package/dist/lib/boolean-type.js +14 -0
  9. package/dist/lib/common-source.d.ts +2 -0
  10. package/dist/lib/common-source.js +39 -0
  11. package/dist/lib/component-refs.d.ts +5 -0
  12. package/dist/lib/component-refs.js +58 -0
  13. package/dist/lib/dispatch-type.d.ts +2 -0
  14. package/dist/lib/dispatch-type.js +103 -0
  15. package/dist/lib/handler-body.d.ts +2 -0
  16. package/dist/lib/handler-body.js +32 -0
  17. package/dist/lib/member-key.d.ts +4 -0
  18. package/dist/lib/member-key.js +18 -0
  19. package/dist/lib/optional-call.d.ts +2 -0
  20. package/dist/lib/optional-call.js +55 -0
  21. package/dist/lib/raw-state.d.ts +2 -0
  22. package/dist/lib/raw-state.js +27 -0
  23. package/dist/lib/reactive-context.d.ts +6 -0
  24. package/dist/lib/reactive-context.js +48 -0
  25. package/dist/lib/ready-boolean-expression.d.ts +2 -0
  26. package/dist/lib/ready-boolean-expression.js +17 -0
  27. package/dist/lib/rune-callee.d.ts +2 -0
  28. package/dist/lib/rune-callee.js +19 -0
  29. package/dist/lib/selector-fix.d.ts +2 -0
  30. package/dist/lib/selector-fix.js +122 -0
  31. package/dist/lib/store-state.d.ts +2 -0
  32. package/dist/lib/store-state.js +57 -0
  33. package/dist/lib/svelte-scripts.d.ts +5 -0
  34. package/dist/lib/svelte-scripts.js +25 -0
  35. package/dist/lib/ui-handlers.d.ts +10 -0
  36. package/dist/lib/ui-handlers.js +84 -0
  37. package/dist/lib/value-variable.d.ts +2 -0
  38. package/dist/lib/value-variable.js +25 -0
  39. package/dist/rules/no-component-state.d.ts +2 -0
  40. package/dist/rules/no-component-state.js +46 -0
  41. package/dist/rules/ready-boolean-condition.d.ts +2 -0
  42. package/dist/rules/ready-boolean-condition.js +49 -0
  43. package/dist/rules/require-feature-state-selector.d.ts +2 -0
  44. package/dist/rules/require-feature-state-selector.js +58 -0
  45. package/dist/rules/single-intent-dispatch.d.ts +2 -0
  46. package/dist/rules/single-intent-dispatch.js +89 -0
  47. package/package.json +55 -4
@@ -0,0 +1,103 @@
1
+ import ts from 'typescript';
2
+ import { createCommonSourceCheck } from './common-source.js';
3
+ export const createDispatchCheck = (services) => {
4
+ const checker = services.program.getTypeChecker();
5
+ const isCommonSource = createCommonSourceCheck();
6
+ const signatures = new Set();
7
+ const symbols = new Map();
8
+ const isPublicDispatch = (symbol) => {
9
+ const cached = symbols.get(symbol);
10
+ if (cached !== undefined) {
11
+ return cached;
12
+ }
13
+ const matches = symbol.getDeclarations()?.some((declaration) => {
14
+ const source = declaration.getSourceFile();
15
+ if (!isCommonSource(source)) {
16
+ return false;
17
+ }
18
+ const module = checker.getSymbolAtLocation(source);
19
+ const exported = module &&
20
+ checker.getExportsOfModule(module).find((item) => {
21
+ return item.name === 'Dispatch';
22
+ });
23
+ const target = exported &&
24
+ (exported.flags & ts.SymbolFlags.Alias
25
+ ? checker.getAliasedSymbol(exported)
26
+ : exported);
27
+ if (target !== symbol) {
28
+ return false;
29
+ }
30
+ checker
31
+ .getDeclaredTypeOfSymbol(target)
32
+ .getCallSignatures()
33
+ .forEach((signature) => {
34
+ if (signature.declaration) {
35
+ signatures.add(signature.declaration);
36
+ }
37
+ });
38
+ return true;
39
+ }) ?? false;
40
+ symbols.set(symbol, matches);
41
+ return matches;
42
+ };
43
+ const hasDispatchAlias = (original, seen) => {
44
+ const symbol = original.flags & ts.SymbolFlags.Alias
45
+ ? checker.getAliasedSymbol(original)
46
+ : original;
47
+ if (seen.has(symbol)) {
48
+ return false;
49
+ }
50
+ if (isPublicDispatch(symbol)) {
51
+ return true;
52
+ }
53
+ const next = new Set(seen).add(symbol);
54
+ return (symbol.getDeclarations()?.some((declaration) => {
55
+ if (!ts.isTypeAliasDeclaration(declaration)) {
56
+ return false;
57
+ }
58
+ let target = declaration.type;
59
+ while (ts.isParenthesizedTypeNode(target)) {
60
+ target = target.type;
61
+ }
62
+ if (!ts.isTypeReferenceNode(target)) {
63
+ return false;
64
+ }
65
+ const referenced = checker.getSymbolAtLocation(target.typeName);
66
+ return !!referenced && hasDispatchAlias(referenced, next);
67
+ }) ?? false);
68
+ };
69
+ const isDispatch = (type, seen) => {
70
+ if (seen.has(type) ||
71
+ type.flags &
72
+ (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) {
73
+ return false;
74
+ }
75
+ const next = new Set(seen).add(type);
76
+ if (type.isUnion()) {
77
+ const members = type.types.filter((member) => {
78
+ return !(member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined));
79
+ });
80
+ return (members.length > 0 &&
81
+ members.every((member) => {
82
+ return isDispatch(member, next);
83
+ }));
84
+ }
85
+ const symbol = type.aliasSymbol;
86
+ if (!symbol) {
87
+ return false;
88
+ }
89
+ if (!hasDispatchAlias(symbol, new Set())) {
90
+ return false;
91
+ }
92
+ // TS may intern structurally equal aliases. Their signature declarations
93
+ // still distinguish a local callback from the public Dispatch contract.
94
+ const calls = type.getCallSignatures();
95
+ return (calls.length > 0 &&
96
+ calls.every((signature) => {
97
+ return !!signature.declaration && signatures.has(signature.declaration);
98
+ }));
99
+ };
100
+ return (node) => {
101
+ return isDispatch(services.getTypeAtLocation(node), new Set());
102
+ };
103
+ };
@@ -0,0 +1,2 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const visitHandlerBody: (root: TSESTree.Node, source: TSESLint.SourceCode, visit: (node: TSESTree.Node) => void) => void;
@@ -0,0 +1,32 @@
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ // Visit only the handler's own evaluation. Deferred function bodies are separate callbacks.
3
+ export const visitHandlerBody = (root, source, visit) => {
4
+ const walk = (node) => {
5
+ if (node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
6
+ node.type === AST_NODE_TYPES.FunctionExpression ||
7
+ node.type === AST_NODE_TYPES.FunctionDeclaration) {
8
+ return;
9
+ }
10
+ visit(node);
11
+ (source.visitorKeys[node.type] ?? []).forEach((key) => {
12
+ if ((node.type === AST_NODE_TYPES.PropertyDefinition ||
13
+ node.type === AST_NODE_TYPES.AccessorProperty) &&
14
+ !node.static &&
15
+ key === 'value') {
16
+ return;
17
+ }
18
+ const value = node[key];
19
+ if (Array.isArray(value)) {
20
+ value.forEach((child) => {
21
+ if (child) {
22
+ walk(child);
23
+ }
24
+ });
25
+ }
26
+ else if (value) {
27
+ walk(value);
28
+ }
29
+ });
30
+ };
31
+ walk(root);
32
+ };
@@ -0,0 +1,4 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const memberKey: (node: TSESTree.MemberExpression, { numeric }?: {
3
+ numeric?: boolean;
4
+ }) => string | undefined;
@@ -0,0 +1,18 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ export const memberKey = (node, { numeric = false } = {}) => {
3
+ if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
4
+ return node.property.name;
5
+ }
6
+ if (node.computed && node.property.type === AST_NODE_TYPES.Literal) {
7
+ if (node.property.raw.startsWith("'") ||
8
+ node.property.raw.startsWith('"')) {
9
+ return String(node.property.value);
10
+ }
11
+ // Match numeric AST literals, including values such as 1e999; finiteness
12
+ // would change syntax classification. Existing callers remain names-only.
13
+ if (numeric && typeof node.property.value === 'number') {
14
+ return String(node.property.value);
15
+ }
16
+ }
17
+ return undefined;
18
+ };
@@ -0,0 +1,2 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const isOptionalCall: (call: TSESTree.CallExpression) => boolean;
@@ -0,0 +1,55 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ const hasOptionalAccess = (node) => {
3
+ let current = node;
4
+ while (true) {
5
+ if (current.type === AST_NODE_TYPES.CallExpression ||
6
+ current.type === AST_NODE_TYPES.MemberExpression) {
7
+ if (current.optional) {
8
+ return true;
9
+ }
10
+ current =
11
+ current.type === AST_NODE_TYPES.CallExpression
12
+ ? current.callee
13
+ : current.object;
14
+ }
15
+ else if (current.type === AST_NODE_TYPES.TSAsExpression ||
16
+ current.type === AST_NODE_TYPES.TSSatisfiesExpression ||
17
+ current.type === AST_NODE_TYPES.TSNonNullExpression ||
18
+ current.type === AST_NODE_TYPES.TSTypeAssertion ||
19
+ current.type === AST_NODE_TYPES.TSInstantiationExpression) {
20
+ current = current.expression;
21
+ }
22
+ else {
23
+ // A nested ChainExpression ends an earlier chain (e.g. `(store?.dispatch)()`).
24
+ // Arguments are not part of this path; enclosing argument/key gates are checked below.
25
+ return false;
26
+ }
27
+ }
28
+ };
29
+ export const isOptionalCall = (call) => {
30
+ if (hasOptionalAccess(call)) {
31
+ return true;
32
+ }
33
+ let current = call;
34
+ while (current.parent) {
35
+ const parent = current.parent;
36
+ if (parent.type === AST_NODE_TYPES.ArrowFunctionExpression ||
37
+ parent.type === AST_NODE_TYPES.FunctionExpression ||
38
+ parent.type === AST_NODE_TYPES.FunctionDeclaration) {
39
+ return false;
40
+ }
41
+ // Optional calls/accesses skip their arguments and computed keys too.
42
+ if (((parent.type === AST_NODE_TYPES.CallExpression &&
43
+ parent.arguments.some((argument) => {
44
+ return argument === current;
45
+ })) ||
46
+ (parent.type === AST_NODE_TYPES.MemberExpression &&
47
+ parent.computed &&
48
+ parent.property === current)) &&
49
+ hasOptionalAccess(parent)) {
50
+ return true;
51
+ }
52
+ current = parent;
53
+ }
54
+ return false;
55
+ };
@@ -0,0 +1,2 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const createRawStateCheck: (source: TSESLint.SourceCode) => (node: TSESTree.Node) => boolean;
@@ -0,0 +1,27 @@
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ import { runeCallee } from './rune-callee.js';
3
+ import { findValueVariable } from './value-variable.js';
4
+ // Inspect the original scope: Svelte's typed parser synthesizes rune declarations
5
+ // in a virtual TS file. Plain aliases of raw state capture a nonreactive snapshot.
6
+ export const createRawStateCheck = (source) => {
7
+ return (node) => {
8
+ if (node.type !== AST_NODE_TYPES.Identifier) {
9
+ return false;
10
+ }
11
+ const variable = findValueVariable(source.getScope(node), node.name);
12
+ return (variable?.defs.some((definition) => {
13
+ if (definition.type !== 'Variable' ||
14
+ definition.node.id.type !== AST_NODE_TYPES.Identifier ||
15
+ definition.node.id.name !== node.name) {
16
+ return false;
17
+ }
18
+ const init = definition.node.init;
19
+ if (init?.type !== AST_NODE_TYPES.CallExpression ||
20
+ init.callee.type !== AST_NODE_TYPES.MemberExpression ||
21
+ !runeCallee(init.callee, source, '$state', 'raw')) {
22
+ return false;
23
+ }
24
+ return true;
25
+ }) ?? false);
26
+ };
27
+ };
@@ -0,0 +1,6 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const isDerived: (node: TSESTree.CallExpression, source: TSESLint.SourceCode) => boolean;
3
+ export declare const reactiveContext: (node: TSESTree.Node, source: TSESLint.SourceCode) => {
4
+ derived: TSESTree.CallExpression;
5
+ call: TSESTree.CallExpression | TSESTree.NewExpression | TSESTree.TaggedTemplateExpression;
6
+ } | undefined;
@@ -0,0 +1,48 @@
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ import { runeCallee } from './rune-callee.js';
3
+ import { memberKey } from './member-key.js';
4
+ export const isDerived = (node, source) => {
5
+ return !!runeCallee(node.callee, source, '$derived', 'by');
6
+ };
7
+ export const reactiveContext = (node, source) => {
8
+ let current = node;
9
+ let call;
10
+ while (current.parent) {
11
+ const parent = current.parent;
12
+ if (parent.type === AST_NODE_TYPES.CallExpression) {
13
+ if (isDerived(parent, source)) {
14
+ return call ? { derived: parent, call } : undefined;
15
+ }
16
+ }
17
+ if (parent.type === AST_NODE_TYPES.CallExpression ||
18
+ parent.type === AST_NODE_TYPES.NewExpression ||
19
+ parent.type === AST_NODE_TYPES.TaggedTemplateExpression) {
20
+ call ??= parent;
21
+ }
22
+ // Instance fields execute only when an instance is created. Static fields
23
+ // and computed keys execute while the class expression itself is evaluated.
24
+ if ((parent.type === AST_NODE_TYPES.PropertyDefinition ||
25
+ parent.type === AST_NODE_TYPES.AccessorProperty) &&
26
+ !parent.static &&
27
+ parent.value === current) {
28
+ return undefined;
29
+ }
30
+ if (parent.type === AST_NODE_TYPES.ArrowFunctionExpression ||
31
+ parent.type === AST_NODE_TYPES.FunctionExpression ||
32
+ parent.type === AST_NODE_TYPES.FunctionDeclaration) {
33
+ const owner = parent.parent;
34
+ if (owner?.type === AST_NODE_TYPES.CallExpression &&
35
+ owner.callee.type === AST_NODE_TYPES.MemberExpression &&
36
+ memberKey(owner.callee) === 'by' &&
37
+ owner.arguments[0] === parent &&
38
+ !parent.async &&
39
+ !parent.generator &&
40
+ isDerived(owner, source)) {
41
+ return call ? { derived: owner, call } : undefined;
42
+ }
43
+ return undefined;
44
+ }
45
+ current = parent;
46
+ }
47
+ return undefined;
48
+ };
@@ -0,0 +1,2 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const readyBooleanExpression: (node: TSESTree.Expression) => boolean;
@@ -0,0 +1,17 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { memberKey } from './member-key.js';
3
+ export const readyBooleanExpression = (node) => {
4
+ if (node.type === AST_NODE_TYPES.Identifier) {
5
+ return true;
6
+ }
7
+ if (node.type === AST_NODE_TYPES.ChainExpression) {
8
+ return readyBooleanExpression(node.expression);
9
+ }
10
+ if (node.type !== AST_NODE_TYPES.MemberExpression) {
11
+ return false;
12
+ }
13
+ const staticKey = memberKey(node, { numeric: true }) !== undefined;
14
+ return (staticKey &&
15
+ node.object.type !== AST_NODE_TYPES.Super &&
16
+ readyBooleanExpression(node.object));
17
+ };
@@ -0,0 +1,2 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const runeCallee: (callee: TSESTree.CallExpression["callee"], source: TSESLint.SourceCode, name: string, key?: string) => TSESTree.Identifier | undefined;
@@ -0,0 +1,19 @@
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ import { memberKey } from './member-key.js';
3
+ import { findValueVariable } from './value-variable.js';
4
+ // Return the intrinsic identifier, preserving static member-key support.
5
+ export const runeCallee = (callee, source, name, key) => {
6
+ const identifier = callee.type === AST_NODE_TYPES.Identifier
7
+ ? callee
8
+ : key &&
9
+ callee.type === AST_NODE_TYPES.MemberExpression &&
10
+ memberKey(callee) === key &&
11
+ callee.object.type === AST_NODE_TYPES.Identifier
12
+ ? callee.object
13
+ : undefined;
14
+ if (identifier?.name !== name) {
15
+ return undefined;
16
+ }
17
+ const variable = findValueVariable(source.getScope(identifier), name);
18
+ return variable && variable.defs.length > 0 ? undefined : identifier;
19
+ };
@@ -0,0 +1,2 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ export declare const selectorFix: (state: TSESTree.Expression, derived: TSESTree.CallExpression, call: TSESTree.CallExpression, source: TSESLint.SourceCode) => ((fixer: TSESLint.RuleFixer) => TSESLint.RuleFix[]) | undefined;
@@ -0,0 +1,122 @@
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ import { memberKey } from './member-key.js';
3
+ const reserved = new Set([
4
+ 'arguments',
5
+ 'eval',
6
+ 'await',
7
+ 'break',
8
+ 'case',
9
+ 'catch',
10
+ 'class',
11
+ 'const',
12
+ 'continue',
13
+ 'debugger',
14
+ 'default',
15
+ 'delete',
16
+ 'do',
17
+ 'else',
18
+ 'enum',
19
+ 'export',
20
+ 'extends',
21
+ 'false',
22
+ 'finally',
23
+ 'for',
24
+ 'function',
25
+ 'if',
26
+ 'implements',
27
+ 'import',
28
+ 'in',
29
+ 'instanceof',
30
+ 'interface',
31
+ 'let',
32
+ 'new',
33
+ 'null',
34
+ 'package',
35
+ 'private',
36
+ 'protected',
37
+ 'public',
38
+ 'return',
39
+ 'static',
40
+ 'super',
41
+ 'switch',
42
+ 'this',
43
+ 'throw',
44
+ 'true',
45
+ 'try',
46
+ 'typeof',
47
+ 'var',
48
+ 'void',
49
+ 'while',
50
+ 'with',
51
+ 'yield',
52
+ ]);
53
+ // A deliberately small syntax boundary: a standalone derived with one direct call.
54
+ export const selectorFix = (state, derived, call, source) => {
55
+ if (derived.callee.type !== AST_NODE_TYPES.Identifier ||
56
+ derived.arguments[0] !== call ||
57
+ call.optional ||
58
+ call.callee.type !== AST_NODE_TYPES.Identifier ||
59
+ call.arguments.length !== 1) {
60
+ return undefined;
61
+ }
62
+ const selected = call.arguments[0];
63
+ const declarator = derived.parent;
64
+ const declaration = declarator?.parent;
65
+ if (selected.type !== AST_NODE_TYPES.MemberExpression ||
66
+ declarator?.type !== AST_NODE_TYPES.VariableDeclarator ||
67
+ declarator.init !== derived ||
68
+ declaration?.type !== AST_NODE_TYPES.VariableDeclaration ||
69
+ declaration.declarations.length !== 1 ||
70
+ declaration.parent.type === AST_NODE_TYPES.ExportNamedDeclaration ||
71
+ declaration.parent.type === AST_NODE_TYPES.ForStatement ||
72
+ declaration.parent.type === AST_NODE_TYPES.ForOfStatement ||
73
+ declaration.parent.type === AST_NODE_TYPES.ForInStatement) {
74
+ return undefined;
75
+ }
76
+ // Accept one immediate feature property only; deeper user-defined getters
77
+ // and accesses through containers have no static purity guarantee.
78
+ if (selected.object !== state ||
79
+ (state.type !== AST_NODE_TYPES.Identifier &&
80
+ (state.type !== AST_NODE_TYPES.MemberExpression ||
81
+ state.object.type !== AST_NODE_TYPES.Identifier ||
82
+ state.optional)) ||
83
+ selected.optional) {
84
+ return undefined;
85
+ }
86
+ const base = memberKey(selected);
87
+ if (!base || base.startsWith('$') || !/^[A-Z_a-z][$\w]*$/u.test(base)) {
88
+ return undefined;
89
+ }
90
+ const occupied = new Set();
91
+ let scope = source.getScope(declaration);
92
+ while (scope) {
93
+ scope.variables.forEach((variable) => {
94
+ occupied.add(variable.name);
95
+ });
96
+ scope.through.forEach((reference) => {
97
+ occupied.add(reference.identifier.name);
98
+ });
99
+ scope = scope.upper;
100
+ }
101
+ let name = base;
102
+ let version = 2;
103
+ while (occupied.has(name) || reserved.has(name)) {
104
+ name = `${base}V${version}`;
105
+ version += 1;
106
+ }
107
+ // Svelte adds script elements to ESTree; its parser supplies this parent.
108
+ const parent = declaration.parent;
109
+ if (parent.type !== AST_NODE_TYPES.Program &&
110
+ parent.type !== 'SvelteScriptElement') {
111
+ return undefined;
112
+ }
113
+ const indentation = source.lines[declaration.loc.start.line - 1].slice(0, declaration.loc.start.column);
114
+ const leading = /^\s*$/u.test(indentation) ? indentation : '';
115
+ const lineEnd = source.text.includes('\r\n') ? '\r\n' : '\n';
116
+ return (fixer) => {
117
+ return [
118
+ fixer.insertTextBefore(declaration, `const ${name} = $derived(${source.getText(selected)});${lineEnd}${leading}`),
119
+ fixer.replaceText(selected, name),
120
+ ];
121
+ };
122
+ };
@@ -0,0 +1,2 @@
1
+ import type { ParserServicesWithTypeInformation, TSESTree } from '@typescript-eslint/utils';
2
+ export declare const createStoreStateCheck: (services: ParserServicesWithTypeInformation) => (node: TSESTree.Node) => boolean;
@@ -0,0 +1,57 @@
1
+ import ts from 'typescript';
2
+ import { createCommonSourceCheck } from './common-source.js';
3
+ export const createStoreStateCheck = (services) => {
4
+ const program = services.program;
5
+ const checker = program.getTypeChecker();
6
+ const isCommonSource = createCommonSourceCheck();
7
+ const declarations = new Map();
8
+ const isStoreDeclaration = (declaration) => {
9
+ const cached = declarations.get(declaration);
10
+ if (cached !== undefined) {
11
+ return cached;
12
+ }
13
+ const source = declaration.getSourceFile();
14
+ let matches = false;
15
+ if (isCommonSource(source)) {
16
+ const module = checker.getSymbolAtLocation(source);
17
+ const exported = module &&
18
+ checker.getExportsOfModule(module).find((symbol) => {
19
+ return symbol.name === 'Store';
20
+ });
21
+ const store = exported &&
22
+ (exported.flags & ts.SymbolFlags.Alias
23
+ ? checker.getAliasedSymbol(exported)
24
+ : exported);
25
+ matches = store
26
+ ? (checker
27
+ .getDeclaredTypeOfSymbol(store)
28
+ .getProperty('state')
29
+ ?.getDeclarations()
30
+ ?.includes(declaration) ?? false)
31
+ : false;
32
+ }
33
+ declarations.set(declaration, matches);
34
+ return matches;
35
+ };
36
+ const isStore = (type) => {
37
+ if (type.flags &
38
+ (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) {
39
+ return false;
40
+ }
41
+ if (type.isUnion()) {
42
+ const members = type.types.filter((member) => {
43
+ return !(member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined));
44
+ });
45
+ return members.length > 0 && members.every(isStore);
46
+ }
47
+ return (type
48
+ .getProperty('state')
49
+ ?.getDeclarations()
50
+ ?.some((declaration) => {
51
+ return isStoreDeclaration(declaration);
52
+ }) ?? false);
53
+ };
54
+ return (node) => {
55
+ return isStore(services.getTypeAtLocation(node));
56
+ };
57
+ };
@@ -0,0 +1,5 @@
1
+ import type { AST } from 'svelte-eslint-parser';
2
+ import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
3
+ export declare const svelteScripts: (source: TSESLint.SourceCode) => AST.SvelteScriptElement[] | undefined;
4
+ export declare const scriptHasAttribute: (script: AST.SvelteScriptElement, name: string, value?: string) => boolean;
5
+ export declare const inScript: (node: TSESTree.Node, scripts: readonly AST.SvelteScriptElement[]) => boolean;
@@ -0,0 +1,25 @@
1
+ export const svelteScripts = (source) => {
2
+ const services = source.parserServices;
3
+ if (!services || !('isSvelte' in services) || services.isSvelte !== true) {
4
+ return undefined;
5
+ }
6
+ // Only the Svelte parser supplies the extended ESTree script elements.
7
+ return source.ast.body.filter((node) => {
8
+ return node.type === 'SvelteScriptElement';
9
+ });
10
+ };
11
+ export const scriptHasAttribute = (script, name, value) => {
12
+ return script.startTag.attributes.some((attribute) => {
13
+ return (attribute.type === 'SvelteAttribute' &&
14
+ attribute.key.name === name &&
15
+ (value === undefined ||
16
+ attribute.value.some((item) => {
17
+ return item.type === 'SvelteLiteral' && item.value === value;
18
+ })));
19
+ });
20
+ };
21
+ export const inScript = (node, scripts) => {
22
+ return scripts.some((script) => {
23
+ return node.range[0] >= script.range[0] && node.range[1] <= script.range[1];
24
+ });
25
+ };
@@ -0,0 +1,10 @@
1
+ import type { AST } from 'svelte-eslint-parser';
2
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
3
+ type Handler = TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | TSESTree.FunctionDeclaration;
4
+ export declare const createUiHandlerCollector: (source: TSESLint.SourceCode, scripts: readonly AST.SvelteScriptElement[]) => {
5
+ handlers: Set<Handler>;
6
+ attribute(node: TSESTree.Node): void;
7
+ shorthand(node: TSESTree.Node): void;
8
+ directive(node: TSESTree.Node): void;
9
+ };
10
+ export {};