@qq99/eslint-plugin-solid 0.18.0-no-reactive-read-in-effect-callback.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/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/chunk-4UZNJ5MS.mjs +32 -0
- package/dist/chunk-4UZNJ5MS.mjs.map +1 -0
- package/dist/chunk-EBU4C5NV.mjs +77 -0
- package/dist/chunk-EBU4C5NV.mjs.map +1 -0
- package/dist/chunk-LE6SW2M3.mjs +6231 -0
- package/dist/chunk-LE6SW2M3.mjs.map +1 -0
- package/dist/chunk-MHDQJMLE.mjs +39 -0
- package/dist/chunk-MHDQJMLE.mjs.map +1 -0
- package/dist/configs/recommended.d.mts +109 -0
- package/dist/configs/recommended.d.ts +109 -0
- package/dist/configs/recommended.js +5877 -0
- package/dist/configs/recommended.js.map +1 -0
- package/dist/configs/recommended.mjs +5 -0
- package/dist/configs/recommended.mjs.map +1 -0
- package/dist/configs/typescript.d.mts +103 -0
- package/dist/configs/typescript.d.ts +103 -0
- package/dist/configs/typescript.js +6246 -0
- package/dist/configs/typescript.js.map +1 -0
- package/dist/configs/typescript.mjs +6 -0
- package/dist/configs/typescript.mjs.map +1 -0
- package/dist/configs/v2-strict.d.mts +130 -0
- package/dist/configs/v2-strict.d.ts +130 -0
- package/dist/configs/v2-strict.js +6317 -0
- package/dist/configs/v2-strict.js.map +1 -0
- package/dist/configs/v2-strict.mjs +7 -0
- package/dist/configs/v2-strict.mjs.map +1 -0
- package/dist/configs/v2.d.mts +127 -0
- package/dist/configs/v2.d.ts +127 -0
- package/dist/configs/v2.js +6289 -0
- package/dist/configs/v2.js.map +1 -0
- package/dist/configs/v2.mjs +6 -0
- package/dist/configs/v2.mjs.map +1 -0
- package/dist/index.d.mts +740 -0
- package/dist/index.d.ts +740 -0
- package/dist/index.js +6365 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +42 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +109 -0
- package/src/compat.ts +53 -0
- package/src/configs/recommended.ts +48 -0
- package/src/configs/typescript.ts +21 -0
- package/src/configs/v2-strict.ts +28 -0
- package/src/configs/v2.ts +68 -0
- package/src/deps.d.ts +1 -0
- package/src/index.ts +29 -0
- package/src/plugin.ts +92 -0
- package/src/rules/components-return-once.ts +209 -0
- package/src/rules/event-handlers.ts +397 -0
- package/src/rules/imports.ts +338 -0
- package/src/rules/jsx-no-duplicate-props.ts +110 -0
- package/src/rules/jsx-no-script-url.ts +61 -0
- package/src/rules/jsx-no-undef.ts +230 -0
- package/src/rules/jsx-uses-vars.ts +66 -0
- package/src/rules/no-accessor-as-prop.ts +147 -0
- package/src/rules/no-array-handlers.ts +57 -0
- package/src/rules/no-boolean-enumerated-attribute.ts +209 -0
- package/src/rules/no-browser-globals-in-server-function.ts +108 -0
- package/src/rules/no-destructure.ts +245 -0
- package/src/rules/no-innerhtml.ts +143 -0
- package/src/rules/no-invalid-server-capture.ts +104 -0
- package/src/rules/no-module-scope-reactive-primitive.ts +69 -0
- package/src/rules/no-proxy-apis.ts +96 -0
- package/src/rules/no-react-deps.ts +68 -0
- package/src/rules/no-react-specific-props.ts +60 -0
- package/src/rules/no-reactive-read-in-effect-callback.ts +570 -0
- package/src/rules/no-restated-default-options.ts +96 -0
- package/src/rules/no-single-arg-create-effect.ts +56 -0
- package/src/rules/no-store-mutation-outside-setter.ts +137 -0
- package/src/rules/no-unknown-namespaces.ts +140 -0
- package/src/rules/no-unused-signal.ts +119 -0
- package/src/rules/no-write-in-pure-computation.ts +231 -0
- package/src/rules/prefer-classlist.ts +93 -0
- package/src/rules/prefer-for.ts +93 -0
- package/src/rules/prefer-onSettled-for-side-effects.ts +101 -0
- package/src/rules/prefer-show.ts +102 -0
- package/src/rules/prefer-structured-class.ts +117 -0
- package/src/rules/reactivity.ts +1730 -0
- package/src/rules/removed-api.ts +212 -0
- package/src/rules/require-async-server-function.ts +122 -0
- package/src/rules/self-closing-comp.ts +150 -0
- package/src/rules/style-prop.ts +140 -0
- package/src/rules/valid-use-server.ts +192 -0
- package/src/rules/validate-jsx-nesting.ts +1 -0
- package/src/utils/reactive-imports.ts +382 -0
- package/src/utils/reactive-types.ts +272 -0
- package/src/utils/reactive-values.ts +162 -0
- package/src/utils.ts +442 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIXME: remove this comments and import when below issue is fixed.
|
|
3
|
+
* This import is necessary for type generation due to a bug in the TypeScript compiler.
|
|
4
|
+
* See: https://github.com/microsoft/TypeScript/issues/42873
|
|
5
|
+
*/
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
|
+
import type { TSESLint } from "@typescript-eslint/utils";
|
|
8
|
+
|
|
9
|
+
import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
|
|
10
|
+
import {
|
|
11
|
+
findParent,
|
|
12
|
+
isFunctionNode,
|
|
13
|
+
trackImports,
|
|
14
|
+
createNameMatcher,
|
|
15
|
+
getDirectivePrologue,
|
|
16
|
+
getUseServerEligibility,
|
|
17
|
+
isInsideUseServerFunction,
|
|
18
|
+
programHasUseServerDirective,
|
|
19
|
+
} from "../utils";
|
|
20
|
+
|
|
21
|
+
const createRule = ESLintUtils.RuleCreator.withoutDocs;
|
|
22
|
+
|
|
23
|
+
type MessageIds =
|
|
24
|
+
| "misplacedDirective"
|
|
25
|
+
| "templateDirective"
|
|
26
|
+
| "ineligiblePosition"
|
|
27
|
+
| "nonFunctionExport"
|
|
28
|
+
| "clientWrapper";
|
|
29
|
+
type Options = [{ clientWrappers: string[] }];
|
|
30
|
+
|
|
31
|
+
const POSITION_LABELS = {
|
|
32
|
+
objectMethod: "an object method",
|
|
33
|
+
accessor: "a getter or setter",
|
|
34
|
+
classMethod: "a class method",
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Declaration wrappers whose behavior lives in the client bundle. In a
|
|
39
|
+
* module-level "use server" file the client build replaces every export with
|
|
40
|
+
* a bare server reference, so these wrapper calls are compiled out of the
|
|
41
|
+
* client entirely: `GET`/`withMeta` metadata never reaches the client proxy
|
|
42
|
+
* (calls silently go over POST), `live` sources never get their live
|
|
43
|
+
* transport, and the router's `query`/`action`/`liveQuery` integration
|
|
44
|
+
* (caching, dedup, submission tracking) never happens.
|
|
45
|
+
*/
|
|
46
|
+
const CLIENT_WRAPPERS = ["GET", "live", "withMeta", "query", "action", "liveQuery"];
|
|
47
|
+
const WRAPPER_SOURCES = /^(?:solid-js|@solidjs\/(?:web|router|start))(?:\/|$)/;
|
|
48
|
+
|
|
49
|
+
const isDefinitelyNotFunction = (node: T.Node): boolean =>
|
|
50
|
+
node.type === "Literal" ||
|
|
51
|
+
node.type === "TemplateLiteral" ||
|
|
52
|
+
node.type === "ArrayExpression" ||
|
|
53
|
+
node.type === "ObjectExpression";
|
|
54
|
+
|
|
55
|
+
export default createRule<Options, MessageIds>({
|
|
56
|
+
meta: {
|
|
57
|
+
type: "problem",
|
|
58
|
+
docs: {
|
|
59
|
+
description:
|
|
60
|
+
'Enforce that "use server" directives are placed where the compiler honors them, and that module-level directive files export working server functions.',
|
|
61
|
+
url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/valid-use-server.md",
|
|
62
|
+
},
|
|
63
|
+
schema: [
|
|
64
|
+
{
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
clientWrappers: {
|
|
68
|
+
description:
|
|
69
|
+
"Additional function names treated as client-side declaration wrappers that must not be called inside a module-level \"use server\" file. Supports exact names, '*' wildcards, and regexes given as '/pattern/' strings. GET, live, withMeta, query, action, and liveQuery are always included.",
|
|
70
|
+
type: "array",
|
|
71
|
+
items: { type: "string" },
|
|
72
|
+
default: [],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
messages: {
|
|
79
|
+
misplacedDirective:
|
|
80
|
+
'The "use server" directive only takes effect as the first statement of a function body or module; here it is an ordinary expression statement and the code stays on the client.',
|
|
81
|
+
templateDirective:
|
|
82
|
+
'A template literal is never a directive; this code stays on the client. Use a plain string: "use server".',
|
|
83
|
+
ineligiblePosition:
|
|
84
|
+
'The "use server" directive in {{position}} is silently ignored — the function is never extracted to the server. Use a standalone function, a function-valued property, or a block-bodied arrow instead.',
|
|
85
|
+
nonFunctionExport:
|
|
86
|
+
"Every export of a module-level \"use server\" file is registered as a server function; '{{name}}' is not a function and will fail the server at boot. Move non-function exports to another module.",
|
|
87
|
+
clientWrapper:
|
|
88
|
+
"In a module-level \"use server\" file the client receives bare server references, so '{{name}}' never runs on the client and its behavior is silently lost. Wrap the imported server function in a shared module instead, or move the directive inside the wrapped function.",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
defaultOptions: [{ clientWrappers: [] }],
|
|
92
|
+
create(context, [options]) {
|
|
93
|
+
const { matchImport, handleImportDeclaration } = trackImports(WRAPPER_SOURCES);
|
|
94
|
+
const matchesCustomWrapper = createNameMatcher(options.clientWrappers);
|
|
95
|
+
let moduleLevel = false;
|
|
96
|
+
|
|
97
|
+
const checkExportedValue = (node: T.Node, name: string) => {
|
|
98
|
+
if (isDefinitelyNotFunction(node)) {
|
|
99
|
+
context.report({ node, messageId: "nonFunctionExport", data: { name } });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
Program(node) {
|
|
105
|
+
moduleLevel = programHasUseServerDirective(node);
|
|
106
|
+
},
|
|
107
|
+
ImportDeclaration: handleImportDeclaration,
|
|
108
|
+
ExpressionStatement(node) {
|
|
109
|
+
const { expression } = node;
|
|
110
|
+
const isTemplate =
|
|
111
|
+
expression.type === "TemplateLiteral" &&
|
|
112
|
+
expression.expressions.length === 0 &&
|
|
113
|
+
expression.quasis.length === 1 &&
|
|
114
|
+
expression.quasis[0].value.cooked === "use server";
|
|
115
|
+
const isString = expression.type === "Literal" && expression.value === "use server";
|
|
116
|
+
if (!isTemplate && !isString) return;
|
|
117
|
+
|
|
118
|
+
if (isTemplate) {
|
|
119
|
+
context.report({ node, messageId: "templateDirective" });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// In a module-level directive file, or nested inside an extracted
|
|
124
|
+
// server function, the whole scope already runs on the server —
|
|
125
|
+
// stray inner directives are ignored by the transform and harmless.
|
|
126
|
+
if (moduleLevel) return;
|
|
127
|
+
const nearestFunction = findParent(node, isFunctionNode);
|
|
128
|
+
if (
|
|
129
|
+
nearestFunction &&
|
|
130
|
+
isFunctionNode(nearestFunction) &&
|
|
131
|
+
isInsideUseServerFunction(nearestFunction)
|
|
132
|
+
) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const parent = node.parent;
|
|
137
|
+
const enclosingFunction =
|
|
138
|
+
parent?.type === "BlockStatement" && isFunctionNode(parent.parent) ? parent.parent : null;
|
|
139
|
+
if (
|
|
140
|
+
parent?.type !== "Program" &&
|
|
141
|
+
(parent?.type !== "BlockStatement" || !enclosingFunction)
|
|
142
|
+
) {
|
|
143
|
+
// A string statement in a plain block (if/for body, bare block):
|
|
144
|
+
// blocks have no directive prologue.
|
|
145
|
+
context.report({ node, messageId: "misplacedDirective" });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const body = parent.body;
|
|
150
|
+
if (!getDirectivePrologue(body).includes(node)) {
|
|
151
|
+
context.report({ node, messageId: "misplacedDirective" });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (enclosingFunction && !isInsideUseServerFunction(enclosingFunction)) {
|
|
156
|
+
const eligibility = getUseServerEligibility(enclosingFunction);
|
|
157
|
+
if (eligibility !== "eligible") {
|
|
158
|
+
context.report({
|
|
159
|
+
node,
|
|
160
|
+
messageId: "ineligiblePosition",
|
|
161
|
+
data: { position: POSITION_LABELS[eligibility] },
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
ExportNamedDeclaration(node) {
|
|
167
|
+
if (!moduleLevel || !node.declaration) return;
|
|
168
|
+
if (node.declaration.type === "VariableDeclaration") {
|
|
169
|
+
for (const declarator of node.declaration.declarations) {
|
|
170
|
+
if (declarator.init && declarator.id.type === "Identifier") {
|
|
171
|
+
checkExportedValue(declarator.init, declarator.id.name);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
ExportDefaultDeclaration(node) {
|
|
177
|
+
if (!moduleLevel) return;
|
|
178
|
+
checkExportedValue(node.declaration, "default");
|
|
179
|
+
},
|
|
180
|
+
CallExpression(node) {
|
|
181
|
+
if (!moduleLevel || node.callee.type !== "Identifier") return;
|
|
182
|
+
// Only module-scope calls: wrapper calls inside functions are just
|
|
183
|
+
// server-side code, which is the caller's business.
|
|
184
|
+
if (findParent(node, isFunctionNode) != null) return;
|
|
185
|
+
const name = node.callee.name;
|
|
186
|
+
if (matchImport(CLIENT_WRAPPERS, name) || matchesCustomWrapper(name)) {
|
|
187
|
+
context.report({ node, messageId: "clientWrapper", data: { name } });
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { createReactiveTypeResolver } from "./reactive-types";
|
|
3
|
+
import {
|
|
4
|
+
Value,
|
|
5
|
+
object,
|
|
6
|
+
scalar,
|
|
7
|
+
setter,
|
|
8
|
+
container,
|
|
9
|
+
asStore,
|
|
10
|
+
propertyValue,
|
|
11
|
+
mergeValues,
|
|
12
|
+
awaitedValue,
|
|
13
|
+
promiseValue,
|
|
14
|
+
callResult,
|
|
15
|
+
propertiesOf,
|
|
16
|
+
iteratorValue,
|
|
17
|
+
iterationValue,
|
|
18
|
+
iterationReturn,
|
|
19
|
+
} from "./reactive-values";
|
|
20
|
+
|
|
21
|
+
interface Module {
|
|
22
|
+
source: ts.SourceFile;
|
|
23
|
+
checker: ts.TypeChecker;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Follow return values in local source modules. Each module is bound on its
|
|
28
|
+
* own, without libraries or dependencies, solely to resolve lexical bindings.
|
|
29
|
+
* This does not type-check a project or execute the imported code, and works
|
|
30
|
+
* in Oxlint without typescript-eslint's parser services.
|
|
31
|
+
*
|
|
32
|
+
* Caches live for one linted file so editor runs cannot retain stale sources.
|
|
33
|
+
* File/step limits bound work on cyclic or unusually large module graphs.
|
|
34
|
+
*/
|
|
35
|
+
export function createReactiveImportResolver(filename: string, solidSource: RegExp, text: string) {
|
|
36
|
+
const modules = new Map<string, Module | null>();
|
|
37
|
+
let resolutionOptions: ts.CompilerOptions | undefined;
|
|
38
|
+
let steps = 0;
|
|
39
|
+
|
|
40
|
+
const load = (file: string): Module | null => {
|
|
41
|
+
if (modules.has(file)) return modules.get(file)!;
|
|
42
|
+
if (modules.size >= 32) return null;
|
|
43
|
+
modules.set(file, null);
|
|
44
|
+
const options: ts.CompilerOptions = {
|
|
45
|
+
allowJs: true,
|
|
46
|
+
noLib: true,
|
|
47
|
+
noResolve: true,
|
|
48
|
+
types: [],
|
|
49
|
+
target: ts.ScriptTarget.Latest,
|
|
50
|
+
module: ts.ModuleKind.ESNext,
|
|
51
|
+
};
|
|
52
|
+
const host = ts.createCompilerHost(options);
|
|
53
|
+
// No full program graph: the checker is only used for this file's symbols.
|
|
54
|
+
const getSourceFile = host.getSourceFile.bind(host);
|
|
55
|
+
host.getSourceFile = (name, languageVersion) =>
|
|
56
|
+
name === file ? getSourceFile(name, languageVersion) : undefined;
|
|
57
|
+
const program = ts.createProgram([file], options, host);
|
|
58
|
+
const source = program.getSourceFile(file);
|
|
59
|
+
if (!source || program.getSyntacticDiagnostics(source).length) return null;
|
|
60
|
+
const module = { source, checker: program.getTypeChecker() };
|
|
61
|
+
modules.set(file, module);
|
|
62
|
+
return module;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const resolveFile = (specifier: string, from: string): string | undefined => {
|
|
66
|
+
// The browser standalone build and virtual lint inputs have no file graph.
|
|
67
|
+
if (!ts.sys?.fileExists || !filename || !ts.sys.fileExists(filename)) return undefined;
|
|
68
|
+
if (!resolutionOptions) {
|
|
69
|
+
const config = ts.findConfigFile(filename, ts.sys.fileExists);
|
|
70
|
+
resolutionOptions = config
|
|
71
|
+
? ts.getParsedCommandLineOfConfigFile(
|
|
72
|
+
config,
|
|
73
|
+
{},
|
|
74
|
+
{
|
|
75
|
+
...ts.sys,
|
|
76
|
+
onUnRecoverableConfigFileDiagnostic() {},
|
|
77
|
+
}
|
|
78
|
+
)?.options
|
|
79
|
+
: undefined;
|
|
80
|
+
resolutionOptions ??= { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true };
|
|
81
|
+
}
|
|
82
|
+
const resolved = ts.resolveModuleName(
|
|
83
|
+
specifier,
|
|
84
|
+
from,
|
|
85
|
+
resolutionOptions,
|
|
86
|
+
ts.sys
|
|
87
|
+
).resolvedModule;
|
|
88
|
+
// Library implementations are an explicit knowledge boundary. Do not crawl
|
|
89
|
+
// node_modules or mistake declarations for an implementation.
|
|
90
|
+
if (
|
|
91
|
+
!resolved ||
|
|
92
|
+
resolved.isExternalLibraryImport ||
|
|
93
|
+
/(?:^|[/\\])node_modules[/\\]|\.d\.[cm]?ts$/.test(resolved.resolvedFileName)
|
|
94
|
+
) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return resolved.resolvedFileName;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const importSource = (node: ts.Node): string | null => {
|
|
101
|
+
for (let parent: ts.Node | undefined = node; parent; parent = parent.parent) {
|
|
102
|
+
if (ts.isImportDeclaration(parent) || ts.isExportDeclaration(parent)) {
|
|
103
|
+
return parent.moduleSpecifier && ts.isStringLiteral(parent.moduleSpecifier)
|
|
104
|
+
? parent.moduleSpecifier.text
|
|
105
|
+
: null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const imported = (
|
|
112
|
+
source: string,
|
|
113
|
+
name: string,
|
|
114
|
+
from: string,
|
|
115
|
+
args: Value[] | undefined,
|
|
116
|
+
seen: Set<ts.Node>
|
|
117
|
+
): Value => {
|
|
118
|
+
if (++steps > 1000) return null;
|
|
119
|
+
if (
|
|
120
|
+
args &&
|
|
121
|
+
source === "@tanstack/solid-query" &&
|
|
122
|
+
(name === "useQuery" || name === "useInfiniteQuery")
|
|
123
|
+
)
|
|
124
|
+
return object;
|
|
125
|
+
if (args && solidSource.test(source)) {
|
|
126
|
+
if (name === "createSignal" || name === "createOptimistic") {
|
|
127
|
+
return container([
|
|
128
|
+
["0", { kind: "accessor", result: args[0] ?? null }],
|
|
129
|
+
["1", setter],
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
if (name === "createMemo") return { kind: "accessor", result: null };
|
|
133
|
+
if (name === "createStore" || name === "createOptimisticStore") {
|
|
134
|
+
return container([
|
|
135
|
+
["0", asStore(args[0] ?? args[1] ?? null)],
|
|
136
|
+
["1", setter],
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
if (name === "createProjection") return asStore(args[1] ?? null);
|
|
140
|
+
if (name === "omit") return args[0] ?? null;
|
|
141
|
+
if (name === "merge") return args.reduce(mergeValues, null);
|
|
142
|
+
}
|
|
143
|
+
const file = resolveFile(source, from);
|
|
144
|
+
const module = file ? load(file) : null;
|
|
145
|
+
if (!module) return null;
|
|
146
|
+
const symbol = module.checker.getSymbolAtLocation(module.source);
|
|
147
|
+
const exported =
|
|
148
|
+
symbol && module.checker.getExportsOfModule(symbol).find((s) => s.name === name);
|
|
149
|
+
if (exported) return evaluateSymbol(module, exported, args, new Map(), seen);
|
|
150
|
+
// The single-file binder cannot expand export-star declarations itself.
|
|
151
|
+
if (name !== "default") {
|
|
152
|
+
for (const statement of module.source.statements) {
|
|
153
|
+
if (ts.isExportDeclaration(statement) && !statement.exportClause && !statement.isTypeOnly) {
|
|
154
|
+
const source = importSource(statement);
|
|
155
|
+
if (!source || seen.has(statement)) continue;
|
|
156
|
+
const value = imported(
|
|
157
|
+
source,
|
|
158
|
+
name,
|
|
159
|
+
module.source.fileName,
|
|
160
|
+
args,
|
|
161
|
+
new Set(seen).add(statement)
|
|
162
|
+
);
|
|
163
|
+
if (value) return value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const evaluateSymbol = (
|
|
171
|
+
module: Module,
|
|
172
|
+
symbol: ts.Symbol | undefined,
|
|
173
|
+
args: Value[] | undefined,
|
|
174
|
+
env: Map<ts.Symbol, Value>,
|
|
175
|
+
seen: Set<ts.Node>
|
|
176
|
+
): Value => {
|
|
177
|
+
if (!symbol) return null;
|
|
178
|
+
if (env.has(symbol)) {
|
|
179
|
+
const value = env.get(symbol)!;
|
|
180
|
+
return args ? callResult(value) : value;
|
|
181
|
+
}
|
|
182
|
+
const declarations = symbol.declarations ?? [];
|
|
183
|
+
const declaration =
|
|
184
|
+
declarations.find((d) => ts.isFunctionDeclaration(d) && d.body) ?? declarations[0];
|
|
185
|
+
return declaration ? evaluate(module, declaration, args, env, seen) : null;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const evaluate = (
|
|
189
|
+
module: Module,
|
|
190
|
+
node: ts.Node,
|
|
191
|
+
args: Value[] | undefined,
|
|
192
|
+
env: Map<ts.Symbol, Value>,
|
|
193
|
+
seen: Set<ts.Node>
|
|
194
|
+
): Value => {
|
|
195
|
+
if (++steps > 1000 || seen.has(node)) return null;
|
|
196
|
+
const next = new Set(seen).add(node);
|
|
197
|
+
const valueOf = (node: ts.Node): Value => evaluate(module, node, undefined, env, next);
|
|
198
|
+
if (ts.isAwaitExpression(node)) {
|
|
199
|
+
const value = awaitedValue(valueOf(node.expression));
|
|
200
|
+
return args ? callResult(value) : value;
|
|
201
|
+
}
|
|
202
|
+
if (ts.isYieldExpression(node)) {
|
|
203
|
+
let value =
|
|
204
|
+
node.asteriskToken && node.expression ? iterationReturn(valueOf(node.expression)) : null;
|
|
205
|
+
let parent: ts.Node | undefined = node.parent;
|
|
206
|
+
while (parent && !ts.isFunctionLike(parent)) parent = parent.parent;
|
|
207
|
+
if (parent && ts.getCombinedModifierFlags(parent as ts.Declaration) & ts.ModifierFlags.Async)
|
|
208
|
+
value = awaitedValue(value);
|
|
209
|
+
return args ? callResult(value) : value;
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
ts.isParenthesizedExpression(node) ||
|
|
213
|
+
ts.isAsExpression(node) ||
|
|
214
|
+
ts.isTypeAssertionExpression(node) ||
|
|
215
|
+
ts.isNonNullExpression(node) ||
|
|
216
|
+
ts.isSatisfiesExpression?.(node)
|
|
217
|
+
) {
|
|
218
|
+
return evaluate(module, node.expression, args, env, next);
|
|
219
|
+
}
|
|
220
|
+
if (ts.isIdentifier(node))
|
|
221
|
+
return evaluateSymbol(module, module.checker.getSymbolAtLocation(node), args, env, next);
|
|
222
|
+
if (ts.isImportSpecifier(node) || ts.isImportClause(node) || ts.isExportSpecifier(node)) {
|
|
223
|
+
if (node.isTypeOnly) return null;
|
|
224
|
+
const source = importSource(node);
|
|
225
|
+
if (source) {
|
|
226
|
+
const name = ts.isImportClause(node) ? "default" : (node.propertyName ?? node.name).text;
|
|
227
|
+
return imported(source, name, module.source.fileName, args, next);
|
|
228
|
+
}
|
|
229
|
+
if (ts.isExportSpecifier(node)) {
|
|
230
|
+
return evaluateSymbol(
|
|
231
|
+
module,
|
|
232
|
+
module.checker.getExportSpecifierLocalTargetSymbol(node),
|
|
233
|
+
args,
|
|
234
|
+
env,
|
|
235
|
+
next
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (ts.isExportAssignment(node)) return evaluate(module, node.expression, args, env, next);
|
|
240
|
+
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
241
|
+
if (!(node.parent.flags & ts.NodeFlags.Const)) return null;
|
|
242
|
+
return evaluate(module, node.initializer, args, env, next);
|
|
243
|
+
}
|
|
244
|
+
if (
|
|
245
|
+
ts.isBindingElement(node) &&
|
|
246
|
+
ts.isArrayBindingPattern(node.parent) &&
|
|
247
|
+
ts.isVariableDeclaration(node.parent.parent)
|
|
248
|
+
) {
|
|
249
|
+
const tuple = valueOf(node.parent.parent);
|
|
250
|
+
const value = propertyValue(tuple, String(node.parent.elements.indexOf(node)));
|
|
251
|
+
return args ? callResult(value) : value;
|
|
252
|
+
}
|
|
253
|
+
if (
|
|
254
|
+
ts.isArrowFunction(node) ||
|
|
255
|
+
ts.isFunctionExpression(node) ||
|
|
256
|
+
ts.isFunctionDeclaration(node)
|
|
257
|
+
) {
|
|
258
|
+
if (!args || !node.body) return null;
|
|
259
|
+
const async = Boolean(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Async);
|
|
260
|
+
const wrap = (value: Value): Value => (async ? promiseValue(value) : value);
|
|
261
|
+
const local = new Map(env);
|
|
262
|
+
node.parameters.forEach((param, index) => {
|
|
263
|
+
if (ts.isIdentifier(param.name)) {
|
|
264
|
+
const symbol = module.checker.getSymbolAtLocation(param.name);
|
|
265
|
+
if (symbol)
|
|
266
|
+
local.set(
|
|
267
|
+
symbol,
|
|
268
|
+
args[index] ?? (param.initializer ? valueOf(param.initializer) : null)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
if (!ts.isBlock(node.body)) return wrap(evaluate(module, node.body, undefined, local, next));
|
|
273
|
+
let result: Value = null;
|
|
274
|
+
let yielded: Value = null;
|
|
275
|
+
const visit = (child: ts.Node): void => {
|
|
276
|
+
if (ts.isFunctionLike(child) || ts.isClassLike(child)) return;
|
|
277
|
+
if (ts.isReturnStatement(child) && child.expression) {
|
|
278
|
+
result = mergeValues(result, evaluate(module, child.expression, undefined, local, next));
|
|
279
|
+
} else if (node.asteriskToken && ts.isYieldExpression(child) && child.expression) {
|
|
280
|
+
const value = evaluate(module, child.expression, undefined, local, next);
|
|
281
|
+
yielded = mergeValues(yielded, child.asteriskToken ? iterationValue(value) : value);
|
|
282
|
+
}
|
|
283
|
+
// A return expression may itself contain yield or yield*.
|
|
284
|
+
ts.forEachChild(child, visit);
|
|
285
|
+
};
|
|
286
|
+
visit(node.body);
|
|
287
|
+
return node.asteriskToken ? iteratorValue(async, yielded, result) : wrap(result);
|
|
288
|
+
}
|
|
289
|
+
if (ts.isCallExpression(node)) {
|
|
290
|
+
if (
|
|
291
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
292
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
293
|
+
node.expression.expression.text === "Promise" &&
|
|
294
|
+
node.expression.name.text === "resolve" &&
|
|
295
|
+
!module.checker.getSymbolAtLocation(node.expression.expression)?.declarations?.length
|
|
296
|
+
) {
|
|
297
|
+
return args ? null : promiseValue(node.arguments[0] ? valueOf(node.arguments[0]) : null);
|
|
298
|
+
}
|
|
299
|
+
const value = evaluate(module, node.expression, node.arguments.map(valueOf), env, next);
|
|
300
|
+
return args ? callResult(value) : value;
|
|
301
|
+
}
|
|
302
|
+
if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
|
|
303
|
+
const key = ts.isPropertyAccessExpression(node)
|
|
304
|
+
? node.name.text
|
|
305
|
+
: ts.isStringLiteralLike(node.argumentExpression) ||
|
|
306
|
+
ts.isNumericLiteral(node.argumentExpression)
|
|
307
|
+
? node.argumentExpression.text
|
|
308
|
+
: null;
|
|
309
|
+
if (key !== null && ts.isIdentifier(node.expression)) {
|
|
310
|
+
const declaration = module.checker.getSymbolAtLocation(node.expression)?.declarations?.[0];
|
|
311
|
+
if (declaration && ts.isNamespaceImport(declaration)) {
|
|
312
|
+
const source = importSource(declaration);
|
|
313
|
+
if (source) return imported(source, key, module.source.fileName, args, next);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const value = propertyValue(valueOf(node.expression), key);
|
|
317
|
+
return args ? callResult(value) : value;
|
|
318
|
+
}
|
|
319
|
+
if (args) return null;
|
|
320
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
321
|
+
const properties = new Map<string, Value>();
|
|
322
|
+
for (const property of node.properties) {
|
|
323
|
+
if (ts.isSpreadAssignment(property)) {
|
|
324
|
+
const value = valueOf(property.expression);
|
|
325
|
+
for (const [key, entry] of propertiesOf(value)) properties.set(key, entry);
|
|
326
|
+
} else if (ts.isPropertyAssignment(property) && !ts.isComputedPropertyName(property.name)) {
|
|
327
|
+
properties.set(property.name.text, valueOf(property.initializer));
|
|
328
|
+
} else if (ts.isShorthandPropertyAssignment(property)) {
|
|
329
|
+
properties.set(
|
|
330
|
+
property.name.text,
|
|
331
|
+
evaluateSymbol(
|
|
332
|
+
module,
|
|
333
|
+
module.checker.getShorthandAssignmentValueSymbol(property),
|
|
334
|
+
undefined,
|
|
335
|
+
env,
|
|
336
|
+
next
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return { kind: "plain", properties };
|
|
342
|
+
}
|
|
343
|
+
if (ts.isArrayLiteralExpression(node))
|
|
344
|
+
return container(node.elements.map((element, i) => [String(i), valueOf(element)]));
|
|
345
|
+
if (ts.isConditionalExpression(node))
|
|
346
|
+
return mergeValues(valueOf(node.whenTrue), valueOf(node.whenFalse));
|
|
347
|
+
if (ts.isBinaryExpression(node)) {
|
|
348
|
+
return [
|
|
349
|
+
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
350
|
+
ts.SyntaxKind.BarBarToken,
|
|
351
|
+
ts.SyntaxKind.QuestionQuestionToken,
|
|
352
|
+
].includes(node.operatorToken.kind)
|
|
353
|
+
? mergeValues(valueOf(node.left), valueOf(node.right))
|
|
354
|
+
: scalar;
|
|
355
|
+
}
|
|
356
|
+
if (
|
|
357
|
+
ts.isLiteralExpression(node) ||
|
|
358
|
+
ts.isTemplateExpression(node) ||
|
|
359
|
+
ts.isPrefixUnaryExpression(node) ||
|
|
360
|
+
node.kind === ts.SyntaxKind.TrueKeyword ||
|
|
361
|
+
node.kind === ts.SyntaxKind.FalseKeyword
|
|
362
|
+
)
|
|
363
|
+
return scalar;
|
|
364
|
+
return null;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
value(source: string, name: string, args?: Value[]): Value {
|
|
369
|
+
steps = 0;
|
|
370
|
+
return imported(source, name, filename, args, new Set());
|
|
371
|
+
},
|
|
372
|
+
type: createReactiveTypeResolver(
|
|
373
|
+
filename,
|
|
374
|
+
text,
|
|
375
|
+
(source, from) => {
|
|
376
|
+
const file = resolveFile(source, from);
|
|
377
|
+
return file ? load(file) : null;
|
|
378
|
+
},
|
|
379
|
+
solidSource
|
|
380
|
+
),
|
|
381
|
+
};
|
|
382
|
+
}
|