Skip to content

Commit 17855d3

Browse files
committed
test: add test cases
1 parent c47043a commit 17855d3

File tree

3 files changed

+336
-279
lines changed

3 files changed

+336
-279
lines changed

src/parser/typescript/analyze/index.ts

Lines changed: 97 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,18 @@ import type ESTree from "estree";
1818

1919
const RESERVED_NAMES = new Set<string>(["$$props", "$$restProps", "$$slots"]);
2020
/**
21-
* Analyze <script> block script
22-
* Modify the source code to provide correct type information for svelte special variables and scopes.
21+
* Analyze TypeScript source code.
22+
* Generate virtual code to provide correct type information for Svelte store reference namess and scopes.
2323
* See https://github.com/ota-meshi/svelte-eslint-parser/blob/main/docs/internal-mechanism.md#scope-types
2424
*/
25-
export function analyzeScript(
25+
export function analyzeTypeScript(
2626
code: { script: string; render: string },
2727
attrs: Record<string, string | undefined>,
2828
parserOptions: any
2929
): VirtualTypeScriptContext {
3030
const ctx = new VirtualTypeScriptContext(code.script + code.render);
3131
ctx.appendOriginal(/^\s*/u.exec(code.script)![0].length);
3232

33-
// We will need to parse TypeScript once to extract the reactive variables.
3433
const result = parseScriptWithoutAnalyzeScope(
3534
code.script + code.render,
3635
attrs,
@@ -43,56 +42,34 @@ export function analyzeScript(
4342

4443
ctx._beforeResult = result;
4544

46-
const throughIds = analyzeStoreReferenceNamesAndExtractThroughIdentifiers(
47-
result,
48-
ctx
49-
);
45+
analyzeStoreReferenceNames(result, ctx);
5046

51-
analyzeReactiveScopes(result, throughIds, ctx);
47+
analyzeReactiveScopes(result, ctx);
5248

53-
ctx.appendOriginal(code.script.length);
54-
const renderFunctionName = ctx.generateUniqueId("render");
55-
ctx.appendScript(`function ${renderFunctionName}(){`);
56-
ctx.appendOriginalToEnd();
57-
ctx.appendScript(`}`);
58-
ctx.restoreContext.addRestoreStatementProcess((node, result) => {
59-
if (
60-
node.type !== "FunctionDeclaration" ||
61-
node.id.name !== renderFunctionName
62-
) {
63-
return false;
64-
}
65-
const program = result.ast;
66-
program.body.splice(program.body.indexOf(node), 1, ...node.body.body);
67-
for (const body of node.body.body) {
68-
body.parent = program;
69-
}
70-
71-
const scopeManager = result.scopeManager as ScopeManager;
72-
removeFunctionScope(node, scopeManager);
73-
return true;
74-
});
49+
analyzeRenderScopes(code, ctx);
7550

7651
return ctx;
7752
}
7853

7954
/**
80-
* Analyze the store reference names and extract through identifiers.
55+
* Analyze the store reference names.
56+
* Insert type definitions code to provide correct type information for variables that begin with `$`.
8157
*/
82-
function analyzeStoreReferenceNamesAndExtractThroughIdentifiers(
58+
function analyzeStoreReferenceNames(
8359
result: TSESParseForESLintResult,
8460
ctx: VirtualTypeScriptContext
8561
) {
8662
const scopeManager = result.scopeManager;
8763
const programScope = getProgramScope(scopeManager as ScopeManager);
88-
const throughIds: (TSESTree.Identifier | TSESTree.JSXIdentifier)[] = [];
8964
const maybeStoreRefNames = new Set<string>();
9065

9166
for (const reference of scopeManager.globalScope!.through) {
92-
throughIds.push(reference.identifier);
9367
if (
68+
// Begin with `$`.
9469
reference.identifier.name.startsWith("$") &&
70+
// Ignore it is a reserved variable.
9571
!RESERVED_NAMES.has(reference.identifier.name) &&
72+
// Ignore if it is already defined.
9673
!programScope.set.has(reference.identifier.name)
9774
) {
9875
maybeStoreRefNames.add(reference.identifier.name);
@@ -159,18 +136,20 @@ function analyzeStoreReferenceNamesAndExtractThroughIdentifiers(
159136
});
160137
}
161138
}
162-
163-
return throughIds;
164139
}
165140

166141
/**
167142
* Analyze the reactive scopes.
143+
* Transform source code to provide the correct type information in the `$:` statements.
168144
*/
169145
function analyzeReactiveScopes(
170146
result: TSESParseForESLintResult,
171-
throughIds: (TSESTree.Identifier | TSESTree.JSXIdentifier)[],
172147
ctx: VirtualTypeScriptContext
173148
) {
149+
const scopeManager = result.scopeManager;
150+
const throughIds = scopeManager.globalScope!.through.map(
151+
(reference) => reference.identifier
152+
);
174153
for (const statement of result.ast.body) {
175154
if (statement.type === "LabeledStatement" && statement.label.name === "$") {
176155
if (
@@ -204,6 +183,38 @@ function analyzeReactiveScopes(
204183
}
205184
}
206185

186+
/**
187+
* Analyze the render scopes.
188+
* Transform source code to provide the correct type information in the HTML templates.
189+
*/
190+
function analyzeRenderScopes(
191+
code: { script: string; render: string },
192+
ctx: VirtualTypeScriptContext
193+
) {
194+
ctx.appendOriginal(code.script.length);
195+
const renderFunctionName = ctx.generateUniqueId("render");
196+
ctx.appendScript(`function ${renderFunctionName}(){`);
197+
ctx.appendOriginalToEnd();
198+
ctx.appendScript(`}`);
199+
ctx.restoreContext.addRestoreStatementProcess((node, result) => {
200+
if (
201+
node.type !== "FunctionDeclaration" ||
202+
node.id.name !== renderFunctionName
203+
) {
204+
return false;
205+
}
206+
const program = result.ast;
207+
program.body.splice(program.body.indexOf(node), 1, ...node.body.body);
208+
for (const body of node.body.body) {
209+
body.parent = program;
210+
}
211+
212+
const scopeManager = result.scopeManager as ScopeManager;
213+
removeFunctionScope(node, scopeManager);
214+
return true;
215+
});
216+
}
217+
207218
/**
208219
* Transform for `$: id = ...` to `$: let id = ...`
209220
*/
@@ -230,34 +241,58 @@ function transformForDeclareReactiveVar(
230241
// $: let {id} = fn()
231242
// function fn () { return foo; }
232243

244+
/**
245+
* The opening paren tokens for
246+
* `$: ({id} = foo);`
247+
* ^
248+
*/
233249
const openParens: TSESTree.Token[] = [];
250+
/**
251+
* The equal token for
252+
* `$: ({id} = foo);`
253+
* ^
254+
*/
234255
let eq: TSESTree.Token | null = null;
256+
/**
257+
* The closing paren tokens for
258+
* `$: ({id} = foo);`
259+
* ^
260+
*/
235261
const closeParens: TSESTree.Token[] = [];
262+
/**
263+
* The closing paren token for
264+
* `$: id = (foo);`
265+
* ^
266+
*/
267+
let expressionCloseParen: TSESTree.Token | null = null;
236268
const startIndex = sortedLastIndex(
237269
tokens,
238270
(target) => target.range[0] - statement.range[0]
239271
);
240272
for (let index = startIndex; index < tokens.length; index++) {
241273
const token = tokens[index];
274+
if (statement.range[1] <= token.range[0]) {
275+
break;
276+
}
277+
if (token.range[1] <= statement.range[0]) {
278+
continue;
279+
}
280+
if (token.value === "(" && token.range[1] <= expression.range[0]) {
281+
openParens.push(token);
282+
}
242283
if (
243-
statement.range[0] <= token.range[0] &&
244-
token.range[1] <= statement.range[1]
284+
token.value === "=" &&
285+
expression.left.range[1] <= token.range[0] &&
286+
token.range[1] <= expression.right.range[0]
245287
) {
246-
if (token.value === "(" && token.range[1] <= expression.range[0]) {
247-
openParens.push(token);
248-
}
249-
if (
250-
token.value === "=" &&
251-
expression.left.range[1] <= token.range[0] &&
252-
token.range[1] <= expression.right.range[0]
253-
) {
254-
eq = token;
255-
}
256-
if (token.value === ")" && expression.range[1] <= token.range[0]) {
288+
eq = token;
289+
}
290+
if (token.value === ")") {
291+
if (expression.range[1] <= token.range[0]) {
257292
closeParens.push(token);
293+
} else if (expression.right.range[1] <= token.range[0]) {
294+
expressionCloseParen = token;
258295
}
259-
} else if (statement.range[1] <= token.range[0]) {
260-
break;
261296
}
262297
}
263298

@@ -327,9 +362,16 @@ function transformForDeclareReactiveVar(
327362
right: returnStatement.argument,
328363
loc: {
329364
start: idDecl.id.loc.start,
330-
end: returnStatement.argument.loc.end,
365+
end: expressionCloseParen
366+
? expressionCloseParen.loc.end
367+
: returnStatement.argument.loc.end,
331368
},
332-
range: [idDecl.id.range[0], returnStatement.argument.range[1]],
369+
range: [
370+
idDecl.id.range[0],
371+
expressionCloseParen
372+
? expressionCloseParen.range[1]
373+
: returnStatement.argument.range[1],
374+
],
333375
};
334376
idDecl.id.parent = newExpression;
335377
returnStatement.argument.parent = newExpression;

0 commit comments

Comments
 (0)