From 05995aa229e9cc22b95b9960777b92b6ea9e8f85 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 6 Jul 2022 15:40:41 +0200 Subject: [PATCH 1/5] (feat) enable actions to enhance typings on applied element --- .../svelte2tsx/src/htmlxtojsx_v2/index.ts | 2 +- .../src/htmlxtojsx_v2/nodes/Action.ts | 27 +-- .../src/htmlxtojsx_v2/nodes/Element.ts | 172 +++++++++++++----- packages/svelte2tsx/svelte-jsx.d.ts | 5 +- packages/svelte2tsx/svelte-shims.d.ts | 6 +- .../samples/action-bare/expectedv2.js | 2 +- .../samples/action-nested/expectedv2.js | 12 +- .../samples/action-params/expectedv2.js | 6 +- .../samples/action-svelte-body/expectedv2.js | 2 +- .../samples/directive-quoted/expectedv2.js | 2 +- .../samples/editing-action/expectedv2.js | 2 +- .../samples/$store-as-directive/expectedv2.ts | 4 +- 12 files changed, 154 insertions(+), 88 deletions(-) diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts index 5754d01d5..bba0d30b9 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts @@ -126,7 +126,7 @@ export function convertHtmlxToJsx( handleStyleDirective(str, node as StyleDirective, element as Element); break; case 'Action': - handleActionDirective(str, node as BaseDirective, element as Element); + handleActionDirective(node as BaseDirective, element as Element); break; case 'Transition': handleTransitionDirective(str, node as BaseDirective, element as Element); diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Action.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Action.ts index df5eea163..1c2835bed 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Action.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Action.ts @@ -1,32 +1,9 @@ -import MagicString from 'magic-string'; import { BaseDirective } from '../../interfaces'; -import { - getDirectiveNameStartEndIdx, - rangeWithTrailingPropertyAccess, - TransformationArray -} from '../utils/node-utils'; import { Element } from './Element'; /** * use:xxx={params} ---> __sveltets_2_ensureAction(xxx(svelte.mapElementTag('ParentNodeName'),(params))); */ -export function handleActionDirective( - str: MagicString, - attr: BaseDirective, - element: Element -): void { - const transformations: TransformationArray = [ - '__sveltets_2_ensureAction(', - getDirectiveNameStartEndIdx(str, attr), - `(${element.typingsNamespace}.mapElementTag('${element.tagName}')` - ]; - if (attr.expression) { - transformations.push( - ',(', - rangeWithTrailingPropertyAccess(str.original, attr.expression), - ')' - ); - } - transformations.push('));'); - element.appendToStartEnd(transformations); +export function handleActionDirective(attr: BaseDirective, element: Element): void { + element.addAction(attr); } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts index 1abd0fe74..41d38dda4 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts @@ -1,11 +1,13 @@ import MagicString from 'magic-string'; -import { BaseNode } from '../../interfaces'; +import { BaseDirective, BaseNode } from '../../interfaces'; import { surroundWithIgnoreComments } from '../../utils/ignore'; import { transform, TransformationArray, sanitizePropName, - surroundWith + surroundWith, + getDirectiveNameStartEndIdx, + rangeWithTrailingPropertyAccess } from '../utils/node-utils'; const voidTags = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'.split(','); @@ -30,10 +32,11 @@ const voidTags = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,tra * ``` */ export class Element { - private startTransformation: TransformationArray = []; private startEndTransformation: TransformationArray = ['});']; private attrsTransformation: TransformationArray = []; private slotLetsTransformation?: [TransformationArray, TransformationArray]; + private actionsTransformation: TransformationArray = []; + private actionIdentifiers: string[] = []; private endTransformation: TransformationArray = []; private startTagStart: number; private startTagEnd: number; @@ -43,13 +46,10 @@ export class Element { // Add const $$xxx = ... only if the variable name is actually used // in order to prevent "$$xxx is defined but never used" TS hints - private addNameConstDeclaration?: () => void; + private referencedName = false; private _name: string; public get name(): string { - if (this.addNameConstDeclaration) { - this.addNameConstDeclaration(); - this.addNameConstDeclaration = undefined; - } + this.referencedName = true; return this._name; } @@ -73,7 +73,6 @@ export class Element { this.isSelfclosing = this.computeIsSelfclosing(); this.startTagStart = this.node.start; this.startTagEnd = this.computeStartTagEnd(); - const createElement = `${this.typingsNamespace}.createElement`; const tagEnd = this.startTagStart + this.node.name.length + 1; // Ensure deleted characters are mapped to the attributes object so we @@ -98,24 +97,10 @@ export class Element { // remove the colon: svelte:xxx -> sveltexxx const nodeName = `svelte${this.node.name.substring(7)}`; this._name = '$$_' + nodeName + this.computeDepth(); - this.startTransformation.push(`{ ${createElement}("${this.node.name}", {`); - this.addNameConstDeclaration = () => - (this.startTransformation[0] = `{ const ${this._name} = ${createElement}("${this.node.name}", {`); break; } case 'svelte:element': { - const nodeName = this.node.tag - ? typeof this.node.tag !== 'string' - ? ([this.node.tag.start, this.node.tag.end] as [number, number]) - : `"${this.node.tag}"` - : '""'; this._name = '$$_svelteelement' + this.computeDepth(); - this.startTransformation.push(`{ ${createElement}(`, nodeName, ', {'); - this.addNameConstDeclaration = () => ( - (this.startTransformation[0] = `{ const ${this._name} = ${createElement}(`), - nodeName, - ', {' - ); break; } case 'slot': { @@ -124,29 +109,10 @@ export class Element { // of the slot tag are correct. The check will error if the user defined $$Slots // and the slot definition or its attributes contradict that type definition. this._name = '$$_slot' + this.computeDepth(); - const slotName = - this.node.attributes?.find((a: BaseNode) => a.name === 'name')?.value[0] || - 'default'; - this.startTransformation.push( - '{ __sveltets_createSlot(', - typeof slotName === 'string' - ? `"${slotName}"` - : surroundWith(this.str, [slotName.start, slotName.end], '"', '"'), - ', {' - ); - this.addNameConstDeclaration = () => - (this.startTransformation[0] = `{ const ${this._name} = __sveltets_createSlot(`); break; } default: { this._name = '$$_' + sanitizePropName(this.node.name) + this.computeDepth(); - this.startTransformation.push( - `{ ${createElement}("`, - [this.node.start + 1, this.node.start + 1 + this.node.name.length], - '", {' - ); - this.addNameConstDeclaration = () => - (this.startTransformation[0] = `{ const ${this._name} = ${createElement}("`); break; } } @@ -183,6 +149,28 @@ export class Element { this.slotLetsTransformation[1].push(...transformation, ','); } + addAction(attr: BaseDirective) { + const id = `$$action_${this.actionIdentifiers.length}`; + this.actionIdentifiers.push(id); + if (!this.actionsTransformation.length) { + this.actionsTransformation.push('{'); + } + + this.actionsTransformation.push( + `const ${id} = __sveltets_2_ensureAction(`, + getDirectiveNameStartEndIdx(this.str, attr), + `(${this.typingsNamespace}.mapElementTag('${this.tagName}')` + ); + if (attr.expression) { + this.actionsTransformation.push( + ',(', + rangeWithTrailingPropertyAccess(this.str.original, attr.expression), + ')' + ); + } + this.actionsTransformation.push('));'); + } + /** * Add something right after the start tag end. */ @@ -216,13 +204,18 @@ export class Element { this.endTransformation.push('}'); } + if (this.actionIdentifiers.length) { + this.endTransformation.push('}'); + } + if (this.isSelfclosing) { transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [ // Named slot transformations go first inside a outer block scope because //
means "use the x of let:x", and without a separate // block scope this would give a "used before defined" error ...slotLetTransformation, - ...this.startTransformation, + ...this.actionsTransformation, + ...this.getStartTransformation(), ...this.attrsTransformation, ...this.startEndTransformation, ...this.endTransformation @@ -230,7 +223,8 @@ export class Element { } else { transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [ ...slotLetTransformation, - ...this.startTransformation, + ...this.actionsTransformation, + ...this.getStartTransformation(), ...this.attrsTransformation, ...this.startEndTransformation ]); @@ -244,6 +238,96 @@ export class Element { } } + private getStartTransformation(): TransformationArray { + const createElement = `${this.typingsNamespace}.createElement`; + const addActions = () => { + if (this.actionIdentifiers.length) { + return `, __sveltets_2_union(${this.actionIdentifiers.join(',')})`; + } else { + return ''; + } + }; + + switch (this.node.name) { + // Although not everything that is possible to add to Element + // is valid on the special svelte elements, + // we still also handle them here and let the Svelte parser handle invalid + // cases. For us it doesn't make a difference to a normal HTML element. + case 'svelte:options': + case 'svelte:head': + case 'svelte:window': + case 'svelte:body': + case 'svelte:fragment': { + if (!this.referencedName) { + return [`{ ${createElement}("${this.node.name}"${addActions()}, {`]; + } else { + return [ + `{ const ${this._name} = ${createElement}("${ + this.node.name + }"${addActions()}, {` + ]; + } + } + case 'svelte:element': { + const nodeName = this.node.tag + ? typeof this.node.tag !== 'string' + ? ([this.node.tag.start, this.node.tag.end] as [number, number]) + : `"${this.node.tag}"` + : '""'; + if (!this.referencedName) { + return [`{ ${createElement}(`, nodeName, `${addActions()}, {`]; + } else { + return [ + `{ const ${this._name} = ${createElement}(`, + nodeName, + `${addActions()}, {` + ]; + } + } + case 'slot': { + // If the element is a tag, create the element with the createSlot-function + // which is created inside createRenderFunction.ts to check that the name and attributes + // of the slot tag are correct. The check will error if the user defined $$Slots + // and the slot definition or its attributes contradict that type definition. + const slotName = + this.node.attributes?.find((a: BaseNode) => a.name === 'name')?.value[0] || + 'default'; + if (!this.referencedName) { + return [ + '{ __sveltets_createSlot(', + typeof slotName === 'string' + ? `"${slotName}"` + : surroundWith(this.str, [slotName.start, slotName.end], '"', '"'), + ', {' + ]; + } else { + return [ + `{ const ${this._name} = __sveltets_createSlot(`, + typeof slotName === 'string' + ? `"${slotName}"` + : surroundWith(this.str, [slotName.start, slotName.end], '"', '"'), + ', {' + ]; + } + } + default: { + if (!this.referencedName) { + return [ + `{ ${createElement}("`, + [this.node.start + 1, this.node.start + 1 + this.node.name.length], + `"${addActions()}, {` + ]; + } else { + return [ + `{ const ${this._name} = ${createElement}("`, + [this.node.start + 1, this.node.start + 1 + this.node.name.length], + `"${addActions()}, {` + ]; + } + } + } + } + private computeStartTagEnd() { if (this.node.children?.length) { return this.node.children[0].start; diff --git a/packages/svelte2tsx/svelte-jsx.d.ts b/packages/svelte2tsx/svelte-jsx.d.ts index 77da6d931..c5c3a3872 100644 --- a/packages/svelte2tsx/svelte-jsx.d.ts +++ b/packages/svelte2tsx/svelte-jsx.d.ts @@ -17,7 +17,10 @@ declare namespace svelteHTML { // "undefined | null" because of element: Key | undefined | null, attrs: Elements[Key] ): Key extends keyof ElementTagNameMap ? ElementTagNameMap[Key] : Key extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Key] : any; - + function createElement( + // "undefined | null" because of + element: Key | undefined | null, attrsEnhancers: T, attrs: Elements[Key] & T + ): Key extends keyof ElementTagNameMap ? ElementTagNameMap[Key] : Key extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Key] : any; type NativeElement = HTMLElement; diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts index 858159c0f..98023dbbb 100644 --- a/packages/svelte2tsx/svelte-shims.d.ts +++ b/packages/svelte2tsx/svelte-shims.d.ts @@ -246,9 +246,11 @@ declare function __sveltets_2_ensureAnimation(animationCall: __sveltets_2_Svelte type __sveltets_2_SvelteActionReturnType = { update?: (args: any) => void, - destroy?: () => void + destroy?: () => void, + attributes?: Record, + events?: Record } | void -declare function __sveltets_2_ensureAction(actionCall: __sveltets_2_SvelteActionReturnType): {}; +declare function __sveltets_2_ensureAction(actionCall: T): T extends {attributes?: any, events?: any} ? T['attributes'] & {[Key in keyof T['events'] as `on:${Key & string}`]?: T['events'][Key]} : {}; type __sveltets_2_SvelteTransitionConfig = { delay?: number, diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/action-bare/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/action-bare/expectedv2.js index da7507776..a0c4f80e9 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/action-bare/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/action-bare/expectedv2.js @@ -1 +1 @@ - { svelteHTML.createElement("h1", { });__sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1'))); } \ No newline at end of file + {const $$action_0 = __sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1')));{ svelteHTML.createElement("h1", __sveltets_2_union($$action_0), { }); }} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/action-nested/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/action-nested/expectedv2.js index efdd01be2..259575fff 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/action-nested/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/action-nested/expectedv2.js @@ -1,6 +1,6 @@ - { svelteHTML.createElement("svg", { });__sveltets_2_ensureAction(svgAction(svelteHTML.mapElementTag('svg'))); } - { svelteHTML.createElement("div", { });__sveltets_2_ensureAction(divAction(svelteHTML.mapElementTag('div'))); - { svelteHTML.createElement("input", { });__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('input')));} - { svelteHTML.createElement("p", { });__sveltets_2_ensureAction(pAction(svelteHTML.mapElementTag('p'))); } - { svelteHTML.createElement("unknownTag", { });__sveltets_2_ensureAction(unknownAction(svelteHTML.mapElementTag('unknownTag'))); } - } \ No newline at end of file + {const $$action_0 = __sveltets_2_ensureAction(svgAction(svelteHTML.mapElementTag('svg')));{ svelteHTML.createElement("svg", __sveltets_2_union($$action_0), { }); }} + {const $$action_0 = __sveltets_2_ensureAction(divAction(svelteHTML.mapElementTag('div')));{ svelteHTML.createElement("div", __sveltets_2_union($$action_0), { }); + {const $$action_0 = __sveltets_2_ensureAction(action(svelteHTML.mapElementTag('input')));{ svelteHTML.createElement("input", __sveltets_2_union($$action_0), { });}} + {const $$action_0 = __sveltets_2_ensureAction(pAction(svelteHTML.mapElementTag('p')));{ svelteHTML.createElement("p", __sveltets_2_union($$action_0), { }); }} + {const $$action_0 = __sveltets_2_ensureAction(unknownAction(svelteHTML.mapElementTag('unknownTag')));{ svelteHTML.createElement("unknownTag", __sveltets_2_union($$action_0), { }); }} + }} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/action-params/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/action-params/expectedv2.js index 9fb8de8b6..b5510db8b 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/action-params/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/action-params/expectedv2.js @@ -1,3 +1,3 @@ - { svelteHTML.createElement("h1", { });__sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1'),(500,2))); } - { svelteHTML.createElement("h1", { });__sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1'),(500,2))); } - { svelteHTML.createElement("h1", { });__sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1'),(500,2))); } \ No newline at end of file + {const $$action_0 = __sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1'),(500,2)));{ svelteHTML.createElement("h1", __sveltets_2_union($$action_0), { }); }} + {const $$action_0 = __sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1'),(500,2)));{ svelteHTML.createElement("h1", __sveltets_2_union($$action_0), { }); }} + {const $$action_0 = __sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('h1'),(500,2)));{ svelteHTML.createElement("h1", __sveltets_2_union($$action_0), { }); }} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/action-svelte-body/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/action-svelte-body/expectedv2.js index ea3887a87..57be9156c 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/action-svelte-body/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/action-svelte-body/expectedv2.js @@ -1 +1 @@ - { svelteHTML.createElement("svelte:body", { });__sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('body')));} \ No newline at end of file + {const $$action_0 = __sveltets_2_ensureAction(blink(svelteHTML.mapElementTag('body')));{ svelteHTML.createElement("svelte:body", __sveltets_2_union($$action_0), { });}} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js index 6b1b60b5a..f76a84461 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js @@ -1,6 +1,6 @@ { svelteHTML.createElement("h1", { "onclick":()=>console.log("click"),}); } { const $$_Component0C = __sveltets_2_ensureComponent(Component); const $$_Component0 = new $$_Component0C({ target: __sveltets_2_any(), props: { }});$$_Component0.$on("click", test);} - { svelteHTML.createElement("img", { });__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('img'),(thing)));} + {const $$action_0 = __sveltets_2_ensureAction(action(svelteHTML.mapElementTag('img'),(thing)));{ svelteHTML.createElement("img", __sveltets_2_union($$action_0), { });}} { svelteHTML.createElement("img", { });__sveltets_2_ensureTransition(fade(svelteHTML.mapElementTag('img'),(params)));} { svelteHTML.createElement("img", { });classthing;} { svelteHTML.createElement("img", { });__sveltets_2_ensureAnimation(thing(svelteHTML.mapElementTag('img'),__sveltets_2_AnimationMove,(params)));} diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-action/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-action/expectedv2.js index 25f5a65ba..3a7e14291 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-action/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-action/expectedv2.js @@ -1 +1 @@ - { svelteHTML.createElement("div", { });__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('div'),(opt.))); } \ No newline at end of file + {const $$action_0 = __sveltets_2_ensureAction(action(svelteHTML.mapElementTag('div'),(opt.)));{ svelteHTML.createElement("div", __sveltets_2_union($$action_0), { }); }} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/$store-as-directive/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/$store-as-directive/expectedv2.ts index eec917c26..19aced34b 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/$store-as-directive/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/$store-as-directive/expectedv2.ts @@ -9,8 +9,8 @@ ; async () => { - { svelteHTML.createElement("div", { });__sveltets_2_ensureTransition($transitionStore(svelteHTML.mapElementTag('div'),({ y: 100 })));__sveltets_2_ensureAction($actionStore(svelteHTML.mapElementTag('div')));__sveltets_2_ensureTransition($inStore(svelteHTML.mapElementTag('div')));__sveltets_2_ensureTransition($outStore(svelteHTML.mapElementTag('div')));__sveltets_2_ensureAnimation($animateStore(svelteHTML.mapElementTag('div'),__sveltets_2_AnimationMove)); - }}; + {const $$action_0 = __sveltets_2_ensureAction($actionStore(svelteHTML.mapElementTag('div')));{ svelteHTML.createElement("div", __sveltets_2_union($$action_0), { });__sveltets_2_ensureTransition($transitionStore(svelteHTML.mapElementTag('div'),({ y: 100 })));__sveltets_2_ensureTransition($inStore(svelteHTML.mapElementTag('div')));__sveltets_2_ensureTransition($outStore(svelteHTML.mapElementTag('div')));__sveltets_2_ensureAnimation($animateStore(svelteHTML.mapElementTag('div'),__sveltets_2_AnimationMove)); + }}}; return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) { From e155d65295a2e1b178eae488df3802f25812748c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 22 Aug 2022 15:19:52 +0200 Subject: [PATCH 2/5] cleanup --- .../src/htmlxtojsx_v2/nodes/Element.ts | 77 ++++++------------- 1 file changed, 25 insertions(+), 52 deletions(-) diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts index 41d38dda4..5b0b30285 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts @@ -104,10 +104,6 @@ export class Element { break; } case 'slot': { - // If the element is a tag, create the element with the createSlot-function - // which is created inside createRenderFunction.ts to check that the name and attributes - // of the slot tag are correct. The check will error if the user defined $$Slots - // and the slot definition or its attributes contradict that type definition. this._name = '$$_slot' + this.computeDepth(); break; } @@ -248,6 +244,7 @@ export class Element { } }; + let createElementStatement: TransformationArray; switch (this.node.name) { // Although not everything that is possible to add to Element // is valid on the special svelte elements, @@ -258,15 +255,8 @@ export class Element { case 'svelte:window': case 'svelte:body': case 'svelte:fragment': { - if (!this.referencedName) { - return [`{ ${createElement}("${this.node.name}"${addActions()}, {`]; - } else { - return [ - `{ const ${this._name} = ${createElement}("${ - this.node.name - }"${addActions()}, {` - ]; - } + createElementStatement = [`${createElement}("${this.node.name}"${addActions()}, {`]; + break; } case 'svelte:element': { const nodeName = this.node.tag @@ -274,15 +264,8 @@ export class Element { ? ([this.node.tag.start, this.node.tag.end] as [number, number]) : `"${this.node.tag}"` : '""'; - if (!this.referencedName) { - return [`{ ${createElement}(`, nodeName, `${addActions()}, {`]; - } else { - return [ - `{ const ${this._name} = ${createElement}(`, - nodeName, - `${addActions()}, {` - ]; - } + createElementStatement = [`${createElement}(`, nodeName, `${addActions()}, {`]; + break; } case 'slot': { // If the element is a tag, create the element with the createSlot-function @@ -292,40 +275,30 @@ export class Element { const slotName = this.node.attributes?.find((a: BaseNode) => a.name === 'name')?.value[0] || 'default'; - if (!this.referencedName) { - return [ - '{ __sveltets_createSlot(', - typeof slotName === 'string' - ? `"${slotName}"` - : surroundWith(this.str, [slotName.start, slotName.end], '"', '"'), - ', {' - ]; - } else { - return [ - `{ const ${this._name} = __sveltets_createSlot(`, - typeof slotName === 'string' - ? `"${slotName}"` - : surroundWith(this.str, [slotName.start, slotName.end], '"', '"'), - ', {' - ]; - } + createElementStatement = [ + '__sveltets_createSlot(', + typeof slotName === 'string' + ? `"${slotName}"` + : surroundWith(this.str, [slotName.start, slotName.end], '"', '"'), + ', {' + ]; + break; } default: { - if (!this.referencedName) { - return [ - `{ ${createElement}("`, - [this.node.start + 1, this.node.start + 1 + this.node.name.length], - `"${addActions()}, {` - ]; - } else { - return [ - `{ const ${this._name} = ${createElement}("`, - [this.node.start + 1, this.node.start + 1 + this.node.name.length], - `"${addActions()}, {` - ]; - } + createElementStatement = [ + `${createElement}("`, + [this.node.start + 1, this.node.start + 1 + this.node.name.length], + `"${addActions()}, {` + ]; + break; } } + + if (this.referencedName) { + createElementStatement[0] = `const ${this._name} = ` + createElementStatement[0]; + } + createElementStatement[0] = `{ ${createElementStatement[0]}`; + return createElementStatement; } private computeStartTagEnd() { From 3e84fd702bd54c5681ff0b873ff098dc43bb3fce Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 22 Aug 2022 15:41:23 +0200 Subject: [PATCH 3/5] adjust typings --- packages/svelte2tsx/svelte-shims.d.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts index 98023dbbb..a07dfc3c1 100644 --- a/packages/svelte2tsx/svelte-shims.d.ts +++ b/packages/svelte2tsx/svelte-shims.d.ts @@ -247,10 +247,9 @@ declare function __sveltets_2_ensureAnimation(animationCall: __sveltets_2_Svelte type __sveltets_2_SvelteActionReturnType = { update?: (args: any) => void, destroy?: () => void, - attributes?: Record, - events?: Record + $$attributes?: Record, } | void -declare function __sveltets_2_ensureAction(actionCall: T): T extends {attributes?: any, events?: any} ? T['attributes'] & {[Key in keyof T['events'] as `on:${Key & string}`]?: T['events'][Key]} : {}; +declare function __sveltets_2_ensureAction(actionCall: T): T extends {$$attributes?: any} ? T['$$attributes'] : {}; type __sveltets_2_SvelteTransitionConfig = { delay?: number, From f243d4c9985a3802634887b1147270a0762e75bf Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 26 Aug 2022 16:50:22 +0200 Subject: [PATCH 4/5] adjust typing --- packages/svelte2tsx/svelte-shims.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts index a07dfc3c1..5e30f90f7 100644 --- a/packages/svelte2tsx/svelte-shims.d.ts +++ b/packages/svelte2tsx/svelte-shims.d.ts @@ -247,9 +247,9 @@ declare function __sveltets_2_ensureAnimation(animationCall: __sveltets_2_Svelte type __sveltets_2_SvelteActionReturnType = { update?: (args: any) => void, destroy?: () => void, - $$attributes?: Record, + $$_attributes?: Record, } | void -declare function __sveltets_2_ensureAction(actionCall: T): T extends {$$attributes?: any} ? T['$$attributes'] : {}; +declare function __sveltets_2_ensureAction(actionCall: T): T extends {$$_attributes?: any} ? T['$$_attributes'] : {}; type __sveltets_2_SvelteTransitionConfig = { delay?: number, From 48c145343aab52aced7c517687d1d0eadeb08668 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 26 Aug 2022 17:08:41 +0200 Subject: [PATCH 5/5] typecheck test --- .../actions-enhance-types.only/expected.json | 68 +++++++++++++++++++ .../expectedv2.json | 24 +++++++ .../actions-enhance-types.only/input.svelte | 27 ++++++++ 3 files changed, 119 insertions(+) create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expected.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/input.svelte diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expected.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expected.json new file mode 100644 index 000000000..e05641d80 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expected.json @@ -0,0 +1,68 @@ +[ + { + "range": { + "start": { "line": 22, "character": 9 }, + "end": { "line": 22, "character": 14 } + }, + "severity": 1, + "source": "ts", + "message": "Argument of type '{ $$_attributes: { foo: string; 'on:bar': (e: CustomEvent) => void; }; }' is not assignable to parameter of type 'SvelteActionReturnType'.", + "code": 2345, + "tags": [] + }, + { + "range": { + "start": { "line": 22, "character": 16 }, + "end": { "line": 22, "character": 19 } + }, + "severity": 1, + "source": "ts", + "message": "Type '{ foo: string; onbar: (e: CustomEvent) => void; }' is not assignable to type 'HTMLProps'.\n Property 'foo' does not exist on type 'HTMLProps'.", + "code": 2322, + "tags": [] + }, + { + "range": { + "start": { "line": 25, "character": 9 }, + "end": { "line": 25, "character": 14 } + }, + "severity": 1, + "source": "ts", + "message": "Argument of type '{ $$_attributes: { foo: string; 'on:bar': (e: CustomEvent) => void; }; }' is not assignable to parameter of type 'SvelteActionReturnType'.", + "code": 2345, + "tags": [] + }, + { + "range": { + "start": { "line": 25, "character": 16 }, + "end": { "line": 25, "character": 19 } + }, + "severity": 1, + "source": "ts", + "message": "Type '{ foo: number; onbar: (e: CustomEvent) => void; }' is not assignable to type 'HTMLProps'.\n Property 'foo' does not exist on type 'HTMLProps'.", + "code": 2322, + "tags": [] + }, + { + "range": { + "start": { "line": 26, "character": 9 }, + "end": { "line": 26, "character": 14 } + }, + "severity": 1, + "source": "ts", + "message": "Argument of type '{ $$_attributes: { foo: string; 'on:bar': (e: CustomEvent) => void; }; }' is not assignable to parameter of type 'SvelteActionReturnType'.", + "code": 2345, + "tags": [] + }, + { + "range": { + "start": { "line": 26, "character": 16 }, + "end": { "line": 26, "character": 19 } + }, + "severity": 1, + "source": "ts", + "message": "Type '{ foo: string; onbar: (e: CustomEvent) => void; }' is not assignable to type 'HTMLProps'.\n Property 'foo' does not exist on type 'HTMLProps'.", + "code": 2322, + "tags": [] + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expectedv2.json new file mode 100644 index 000000000..629ebc5f3 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/expectedv2.json @@ -0,0 +1,24 @@ +[ + { + "range": { + "start": { "line": 25, "character": 16 }, + "end": { "line": 25, "character": 19 } + }, + "severity": 1, + "source": "ts", + "message": "Type 'number' is not assignable to type 'string'.", + "code": 2322, + "tags": [] + }, + { + "range": { + "start": { "line": 26, "character": 31 }, + "end": { "line": 26, "character": 34 } + }, + "severity": 1, + "source": "ts", + "message": "Type '(e: CustomEvent) => void' is not assignable to type '(e: CustomEvent) => void'.\n Types of parameters 'e' and 'e' are incompatible.\n Type 'CustomEvent' is not assignable to type 'CustomEvent'.\n Type 'boolean' is not assignable to type 'string'.", + "code": 2322, + "tags": [] + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/input.svelte new file mode 100644 index 000000000..ba253955b --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/actions-enhance-types.only/input.svelte @@ -0,0 +1,27 @@ + + + +
+ + +
+