Skip to content

Commit 064a38e

Browse files
authored
Add type to some scriptlets. (#30)
1 parent 84601d1 commit 064a38e

25 files changed

+6110
-90
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
"require-jsdoc": "error",
1919
"no-warning-comments": "warn",
2020
"no-lonely-if": "off",
21+
"no-shadow": "off",
2122
},
2223
overrides: [
2324
{

src/context/index.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import fs from "fs"
2+
import path from "path"
13
import type { Comment, Locations, Position, Token } from "../ast"
24
import lodash from "lodash"
35
import type ESTree from "estree"
46
import { ScriptLetContext } from "./script-let"
57
import { LetDirectiveCollections } from "./let-directive-collection"
8+
import { getParserName } from "../parser/resolve-parser"
69

710
export class ScriptsSourceCode {
811
private readonly raw
@@ -68,6 +71,8 @@ export class Context {
6871

6972
public readonly letDirCollections = new LetDirectiveCollections()
7073

74+
private state: { isTypeScript?: boolean } = {}
75+
7176
public constructor(code: string, parserOptions: any) {
7277
this.code = code
7378
this.parserOptions = parserOptions
@@ -154,8 +159,49 @@ export class Context {
154159
/**
155160
* get text
156161
*/
157-
public getText(range: { start: number; end: number }): string {
158-
return this.code.slice(range.start, range.end)
162+
public getText(
163+
range: { start: number; end: number } | ESTree.Node,
164+
): string {
165+
return this.code.slice((range as any).start, (range as any).end)
166+
}
167+
168+
public isTypeScript(): boolean {
169+
if (this.state.isTypeScript != null) {
170+
return this.state.isTypeScript
171+
}
172+
const lang = this.sourceCode.scripts.attrs.lang
173+
if (!lang) {
174+
return (this.state.isTypeScript = false)
175+
}
176+
const parserName = getParserName(
177+
this.sourceCode.scripts.attrs,
178+
this.parserOptions?.parser,
179+
)
180+
if (parserName === "@typescript-eslint/parser") {
181+
return (this.state.isTypeScript = true)
182+
}
183+
if (parserName.includes("@typescript-eslint/parser")) {
184+
let targetPath = parserName
185+
while (targetPath) {
186+
const pkgPath = path.join(targetPath, "package.json")
187+
if (fs.existsSync(pkgPath)) {
188+
try {
189+
return (this.state.isTypeScript =
190+
JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
191+
?.name === "@typescript-eslint/parser")
192+
} catch {
193+
return (this.state.isTypeScript = false)
194+
}
195+
}
196+
const parent = path.dirname(targetPath)
197+
if (targetPath === parent) {
198+
break
199+
}
200+
targetPath = parent
201+
}
202+
}
203+
204+
return (this.state.isTypeScript = false)
159205
}
160206
}
161207

src/context/let-directive-collection.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export class LetDirectiveCollection {
77
private readonly list: {
88
pattern: ESTree.Pattern
99
directive: SvelteLetDirective
10+
typing: string
1011
callbacks: ScriptLetCallback<ESTree.Pattern>[]
1112
}[] = []
1213

@@ -32,14 +33,20 @@ export class LetDirectiveCollection {
3233
}
3334
}
3435

36+
public getTypes(): string[] {
37+
return this.list.map((d) => d.typing)
38+
}
39+
3540
public addPattern(
3641
pattern: ESTree.Pattern,
3742
directive: SvelteLetDirective,
43+
typing: string,
3844
...callbacks: ScriptLetCallback<ESTree.Pattern>[]
3945
): ScriptLetCallback<ESTree.Pattern>[] {
4046
this.list.push({
4147
pattern,
4248
directive,
49+
typing,
4350
callbacks,
4451
})
4552
return callbacks

src/context/script-let.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type ScriptLetCallbackOption = {
2222
getScope: (node: ESTree.Node) => Scope
2323
getInnermostScope: (node: ESTree.Node) => Scope
2424
registerNodeToScope: (node: any, scope: Scope) => void
25+
scopeManager: ScopeManager
2526
visitorKeys?: { [type: string]: string[] }
2627
}
2728
export type ScriptLetRestoreCallback = (
@@ -34,6 +35,7 @@ type ScriptLetRestoreCallbackOption = {
3435
getScope: (node: ESTree.Node) => Scope
3536
getInnermostScope: (node: ESTree.Node) => Scope
3637
registerNodeToScope: (node: any, scope: Scope) => void
38+
scopeManager: ScopeManager
3739
visitorKeys?: { [type: string]: string[] }
3840
}
3941

@@ -98,16 +100,20 @@ export class ScriptLetContext {
98100
public addExpression<E extends ESTree.Expression>(
99101
expression: E,
100102
parent: SvelteNode,
103+
typing?: string | null,
101104
...callbacks: ScriptLetCallback<E>[]
102105
): ScriptLetCallback<E>[] {
103106
const range = getNodeRange(expression)
104107
const part = this.ctx.code.slice(...range)
108+
const isTS = typing && this.ctx.isTypeScript()
105109
this.appendScript(
106-
`(${part});`,
110+
`(${part})${isTS ? `as (${typing})` : ""};`,
107111
range[0] - 1,
108-
(st, tokens, _comments, result) => {
112+
(st, tokens, comments, result) => {
109113
const exprSt = st as ESTree.ExpressionStatement
110-
const node = exprSt.expression
114+
const node: ESTree.Expression = isTS
115+
? (exprSt.expression as any).expression
116+
: exprSt.expression
111117
// Process for nodes
112118
for (const callback of callbacks) {
113119
callback(node as E, result)
@@ -118,6 +124,28 @@ export class ScriptLetContext {
118124
tokens.pop() // )
119125
tokens.pop() // ;
120126

127+
if (isTS) {
128+
removeScope(
129+
result.scopeManager,
130+
result.getScope(
131+
(exprSt.expression as any).typeAnnotation
132+
.typeAnnotation,
133+
),
134+
)
135+
this.remapNodes(
136+
[
137+
{
138+
offset: range[0] - 1,
139+
range,
140+
newNode: node,
141+
},
142+
],
143+
tokens,
144+
comments,
145+
result.visitorKeys,
146+
)
147+
}
148+
121149
// Disconnect the tree structure.
122150
exprSt.expression = null as never
123151
},
@@ -277,6 +305,7 @@ export class ScriptLetContext {
277305
nodes: ESTree.Pattern[],
278306
options: ScriptLetCallbackOption,
279307
) => void,
308+
typings: string[],
280309
): void
281310

282311
public nestBlock(
@@ -286,6 +315,7 @@ export class ScriptLetContext {
286315
nodes: ESTree.Pattern[],
287316
options: ScriptLetCallbackOption,
288317
) => void,
318+
typings?: string[],
289319
): void {
290320
if (!params) {
291321
const restore = this.appendScript(
@@ -315,7 +345,8 @@ export class ScriptLetContext {
315345
}[] = []
316346

317347
let source = ""
318-
for (const range of ranges) {
348+
for (let index = 0; index < ranges.length; index++) {
349+
const range = ranges[index]
319350
if (source) {
320351
source += ","
321352
}
@@ -326,6 +357,9 @@ export class ScriptLetContext {
326357
offset,
327358
range,
328359
})
360+
if (this.ctx.isTypeScript()) {
361+
source += ` as (${typings![index]})`
362+
}
329363
}
330364
const restore = this.appendScript(
331365
`(${source})=>{`,
@@ -525,6 +559,7 @@ export class ScriptLetContext {
525559
getScope: getScopeFromNode,
526560
getInnermostScope: getInnermostScopeFromNode,
527561
registerNodeToScope,
562+
scopeManager: result.scopeManager!,
528563
visitorKeys: result.visitorKeys,
529564
})
530565

@@ -615,6 +650,7 @@ export class ScriptLetContext {
615650
visitorKeys,
616651
)
617652
}
653+
tokens.splice(tokenIndex)
618654
}
619655

620656
/** Fix locations */
@@ -748,6 +784,24 @@ function removeReference(reference: Reference, baseScope: Scope) {
748784
}
749785
}
750786

787+
/** Remove scope */
788+
function removeScope(scopeManager: ScopeManager, scope: Scope) {
789+
while (scope.references[0]) {
790+
removeReference(scope.references[0], scope)
791+
}
792+
const upper = scope.upper
793+
if (upper) {
794+
const index = upper.childScopes.indexOf(scope)
795+
if (index >= 0) {
796+
upper.childScopes.splice(index, 1)
797+
}
798+
}
799+
const index = scopeManager.scopes.indexOf(scope)
800+
if (index >= 0) {
801+
scopeManager.scopes.splice(index, 1)
802+
}
803+
}
804+
751805
/** Get the node to scope map from given scope manager */
752806
function getNodeToScope(
753807
scopeManager: ScopeManager,

src/parser/converts/attr.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ function convertAttribute(
123123
loc: attribute.loc,
124124
range: attribute.range,
125125
}
126-
ctx.scriptLet.addExpression(key, sAttr, (es) => {
126+
ctx.scriptLet.addExpression(key, sAttr, null, (es) => {
127127
sAttr.key = es
128128
sAttr.value = es
129129
})
@@ -177,7 +177,7 @@ function convertSpreadAttribute(
177177
end: spreadStart + 3,
178178
})
179179

180-
ctx.scriptLet.addExpression(node.expression, attribute, (es) => {
180+
ctx.scriptLet.addExpression(node.expression, attribute, null, (es) => {
181181
attribute.argument = es
182182
})
183183

@@ -203,6 +203,7 @@ function convertBindingDirective(
203203
return ctx.scriptLet.addExpression(
204204
expression,
205205
directive,
206+
null,
206207
(es, { getInnermostScope }) => {
207208
directive.expression = es
208209
const scope = getInnermostScope(es)
@@ -238,11 +239,20 @@ function convertEventHandlerDirective(
238239
parent,
239240
...ctx.getConvertLocation(node),
240241
}
242+
const isCustomEvent =
243+
parent.parent.type === "SvelteElement" &&
244+
(parent.parent.kind === "component" || parent.parent.kind === "special")
241245
processDirective(
242246
node,
243247
directive,
244248
ctx,
245-
buildProcessExpressionForExpression(directive, ctx),
249+
buildProcessExpressionForExpression(
250+
directive,
251+
ctx,
252+
isCustomEvent
253+
? "(e:CustomEvent<any>)=>void"
254+
: `(e:HTMLElementEventMap['${node.name}'])=>void`,
255+
),
246256
)
247257
return directive
248258
}
@@ -266,7 +276,7 @@ function convertClassDirective(
266276
node,
267277
directive,
268278
ctx,
269-
buildProcessExpressionForExpression(directive, ctx),
279+
buildProcessExpressionForExpression(directive, ctx, null),
270280
)
271281
return directive
272282
}
@@ -292,7 +302,7 @@ function convertTransitionDirective(
292302
node,
293303
directive,
294304
ctx,
295-
buildProcessExpressionForExpression(directive, ctx),
305+
buildProcessExpressionForExpression(directive, ctx, null),
296306
(name) => ctx.scriptLet.addExpression(name, directive),
297307
)
298308
return directive
@@ -317,7 +327,7 @@ function convertAnimationDirective(
317327
node,
318328
directive,
319329
ctx,
320-
buildProcessExpressionForExpression(directive, ctx),
330+
buildProcessExpressionForExpression(directive, ctx, null),
321331
(name) => ctx.scriptLet.addExpression(name, directive),
322332
)
323333
return directive
@@ -342,7 +352,7 @@ function convertActionDirective(
342352
node,
343353
directive,
344354
ctx,
345-
buildProcessExpressionForExpression(directive, ctx),
355+
buildProcessExpressionForExpression(directive, ctx, null),
346356
(name) => ctx.scriptLet.addExpression(name, directive),
347357
)
348358
return directive
@@ -370,15 +380,15 @@ function convertLetDirective(
370380
(pattern) => {
371381
return ctx.letDirCollections
372382
.getCollection()
373-
.addPattern(pattern, directive)
383+
.addPattern(pattern, directive, "any")
374384
},
375385
node.expression
376386
? undefined
377387
: (name) => {
378388
// shorthand
379389
return ctx.letDirCollections
380390
.getCollection()
381-
.addPattern(name, directive, (es) => {
391+
.addPattern(name, directive, "any", (es) => {
382392
directive.expression = es
383393
})
384394
},
@@ -463,9 +473,6 @@ function processDirective<
463473
})
464474
} else {
465475
ctx.addToken("HTMLIdentifier", nameRange)
466-
// ctx.scriptLet.addExpression(directive.name, directive, (es) => {
467-
// directive.name = es
468-
// })
469476
}
470477
}
471478
}
@@ -474,8 +481,9 @@ function processDirective<
474481
function buildProcessExpressionForExpression(
475482
directive: SvelteDirective & { expression: null | ESTree.Expression },
476483
ctx: Context,
484+
typing: string | null,
477485
): (expression: ESTree.Expression) => ScriptLetCallback<ESTree.Expression>[] {
478486
return (expression) => {
479-
return ctx.scriptLet.addExpression(expression, directive)
487+
return ctx.scriptLet.addExpression(expression, directive, typing)
480488
}
481489
}

0 commit comments

Comments
 (0)