From e5cf8c4034ca5562087ec69d08eeef68c0aabeb6 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 16 Jun 2021 10:05:57 +0200 Subject: [PATCH 1/2] (feat) add experimantal support for $$Slots This adds support for a new experimantel feature, the $$Slots interface. If it's defined, components using slots of that component are type-checked against that definition. It is also made sure that only slots that exist and their correct type are used within the component that defines the $$Slots interface. #442 https://github.com/sveltejs/rfcs/pull/38 --- .../features/DiagnosticsProvider.test.ts | 176 ++++++++++++++++++ .../testfiles/diagnostics/$$slots.svelte | 16 ++ .../diagnostics/using-$$slots.svelte | 14 ++ .../src/htmlxtojsx/nodes/attribute.ts | 43 ++++- .../src/svelte2tsx/createRenderFunction.ts | 112 +++++++++++ packages/svelte2tsx/src/svelte2tsx/index.ts | 104 +---------- .../src/svelte2tsx/nodes/ComponentEvents.ts | 11 +- .../svelte2tsx/src/svelte2tsx/nodes/Stores.ts | 2 +- .../svelte2tsx/src/svelte2tsx/nodes/slot.ts | 8 + .../processInstanceScriptContent.ts | 7 + .../src/svelte2tsx/processModuleScriptTag.ts | 24 ++- .../svelte2tsx/src/svelte2tsx/svelteShims.ts | 1 + .../svelte2tsx/src/svelte2tsx/utils/tsAst.ts | 6 + packages/svelte2tsx/svelte-shims.d.ts | 1 + .../sourcemaps/samples/slots/mappings.jsx | 31 +-- .../component-default-slot/expected.tsx | 5 +- .../component-multiple-slots/expected.tsx | 7 +- .../expected.tsx | 24 +++ .../input.svelte | 16 ++ .../component-slot-$$slot-type/expected.tsx | 24 +++ .../component-slot-$$slot-type/input.svelte | 16 ++ .../expected.tsx | 5 +- .../expected.tsx | 3 +- .../component-slot-inside-await/expected.tsx | 7 +- .../component-slot-inside-each/expected.tsx | 5 +- .../expected.tsx | 3 +- .../component-slot-let-forward/expected.tsx | 3 +- .../component-slot-nest-scope/expected.tsx | 7 +- .../component-slot-object-key/expected.tsx | 5 +- .../component-slot-object-key/input.svelte | 2 +- .../component-slot-var-shadowing/expected.tsx | 3 +- .../samples/creates-dts/expected.tsx | 1 + .../samples/slot-bind-this/expected.tsx | 1 + .../samples/ts-$$generics-dts/expected.tsx | 1 + .../samples/ts-$$generics/expected.tsx | 5 +- .../samples/ts-creates-dts/expected.tsx | 1 + .../samples/uses-$$slots-script/expected.tsx | 3 +- .../samples/uses-$$slots/expected.tsx | 1 + .../expected.tsx | 5 +- 39 files changed, 552 insertions(+), 157 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$slots.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/using-$$slots.svelte create mode 100644 packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/input.svelte diff --git a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts index 4f14095d7..11c220a41 100644 --- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts @@ -1406,4 +1406,180 @@ describe('DiagnosticsProvider', () => { } ]); }); + + it('checks $$Slots usage', async () => { + const { plugin, document } = setup('$$slots.svelte'); + const diagnostics = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics, [ + { + code: 2345, + message: + "Argument of type 'boolean' is not assignable to parameter of type 'string'.", + range: { + start: { + character: 41, + line: 13 + }, + end: { + character: 45, + line: 13 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2345, + message: + 'Argument of type \'"invalidProp1"\' is not assignable to parameter of type \'"valid1" | "validPropWrongType1"\'.', + range: { + start: { + character: 60, + line: 13 + }, + end: { + character: 60, + line: 13 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2345, + message: + "Argument of type 'boolean' is not assignable to parameter of type 'string'.", + range: { + start: { + character: 52, + line: 14 + }, + end: { + character: 56, + line: 14 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2345, + message: + 'Argument of type \'"invalidProp2"\' is not assignable to parameter of type \'"valid2" | "validPropWrongType2"\'.', + range: { + start: { + character: 71, + line: 14 + }, + end: { + character: 71, + line: 14 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2345, + message: + "Argument of type '\"invalid\"' is not assignable to parameter of type 'keyof $$Slots'.", + range: { + start: { + character: 26, + line: 15 + }, + end: { + character: 26, + line: 15 + } + }, + severity: 1, + source: 'ts', + tags: [] + } + ]); + }); + + it('checks $$Slots component usage', async () => { + const { plugin, document } = setup('using-$$slots.svelte'); + const diagnostics = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics, [ + { + code: 2339, + message: + "Property 'invalidProp1' does not exist on type '{ valid1: boolean; validPropWrongType1: string; }'.", + range: { + start: { + character: 46, + line: 4 + }, + end: { + character: 58, + line: 4 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2367, + message: + "This condition will always return 'false' since the types 'string' and 'boolean' have no overlap.", + range: { + start: { + character: 5, + line: 6 + }, + end: { + character: 33, + line: 6 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2339, + message: + "Property 'invalidProp2' does not exist on type '{ valid2: boolean; validPropWrongType2: string; }'.", + range: { + start: { + character: 59, + line: 8 + }, + end: { + character: 71, + line: 8 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2367, + message: + "This condition will always return 'false' since the types 'string' and 'boolean' have no overlap.", + range: { + start: { + character: 9, + line: 10 + }, + end: { + character: 37, + line: 10 + } + }, + severity: 1, + source: 'ts', + tags: [] + } + ]); + }); }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$slots.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$slots.svelte new file mode 100644 index 000000000..566c62db1 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$slots.svelte @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/using-$$slots.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/using-$$slots.svelte new file mode 100644 index 000000000..3d87adc43 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/using-$$slots.svelte @@ -0,0 +1,14 @@ + + + + {valid1 === true} + {validPropWrongType1 === true} + {invalidProp1} +
+ {valid2 === true} + {validPropWrongType2 === true} + {invalidProp2} +
+
\ No newline at end of file diff --git a/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts b/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts index 8b954ccb5..1ce4c301d 100644 --- a/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts +++ b/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts @@ -42,6 +42,11 @@ export function handleAttribute( parent: BaseNode, preserveCase: boolean ): void { + const shouldApplySlotCheck = parent.type === 'Slot' && attr.name !== 'name'; + const slotName = shouldApplySlotCheck + ? parent.attributes?.find((a: BaseNode) => a.name === 'name')?.value[0]?.data || 'default' + : undefined; + const ensureSlotStr = `__sveltets_ensureSlot("${slotName}","${attr.name}",`; let transformedFromDirectiveOrNamespace = false; const transformAttributeCase = (name: string) => { @@ -118,6 +123,10 @@ export function handleAttribute( } str.appendRight(attr.start, `${attrName}=`); + if (shouldApplySlotCheck) { + str.prependRight(attr.start + 1, ensureSlotStr); + str.prependLeft(attr.end - 1, `)`); + } return; } @@ -145,33 +154,51 @@ export function handleAttribute( !isNaN(attrVal.data); if (needsNumberConversion) { + const begin = '{' + (shouldApplySlotCheck ? ensureSlotStr : ''); + const end = shouldApplySlotCheck ? `)}` : '}'; if (needsQuotes) { - str.prependRight(equals + 1, '{'); - str.appendLeft(attr.end, '}'); + str.prependRight(equals + 1, begin); + str.appendLeft(attr.end, end); } else { - str.overwrite(equals + 1, equals + 2, '{'); - str.overwrite(attr.end - 1, attr.end, '}'); + str.overwrite(equals + 1, equals + 2, begin); + str.overwrite(attr.end - 1, attr.end, end); } } else if (needsQuotes) { - str.prependRight(equals + 1, '"'); - str.appendLeft(attr.end, '"'); + const begin = shouldApplySlotCheck ? `{${ensureSlotStr}"` : '"'; + const end = shouldApplySlotCheck ? `")}` : '"'; + str.prependRight(equals + 1, begin); + str.appendLeft(attr.end, end); + } else if (shouldApplySlotCheck) { + str.prependRight(equals + 1, `{${ensureSlotStr}`); + str.appendLeft(attr.end, ')}'); } return; } if (attrVal.type == 'MustacheTag') { + const isInQuotes = attrVal.end != attr.end; //if the end doesn't line up, we are wrapped in quotes - if (attrVal.end != attr.end) { + if (isInQuotes) { str.remove(attrVal.start - 1, attrVal.start); str.remove(attr.end - 1, attr.end); } + if (shouldApplySlotCheck) { + str.prependRight(attrVal.start + 1, ensureSlotStr); + str.appendLeft(attr.end - (isInQuotes ? 2 : 1), ')'); + } return; } return; } // We have multiple attribute values, so we build a template string out of them. - buildTemplateString(attr, str, htmlx, '={`', '`}'); + buildTemplateString( + attr, + str, + htmlx, + shouldApplySlotCheck ? `={${ensureSlotStr}\`` : '={`', + shouldApplySlotCheck ? '`)}' : '`}' + ); } function buildTemplateString( diff --git a/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts new file mode 100644 index 000000000..c07ca7d89 --- /dev/null +++ b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts @@ -0,0 +1,112 @@ +import MagicString from 'magic-string'; +import { Node } from 'estree-walker'; +import { ComponentEvents } from './nodes/ComponentEvents'; +import { createRenderFunctionGetterStr } from './nodes/exportgetters'; +import { InstanceScriptProcessResult } from './processInstanceScriptContent'; +import { surroundWithIgnoreComments } from '../utils/ignore'; + +export interface CreateRenderFunctionPara extends InstanceScriptProcessResult { + str: MagicString; + scriptTag: Node; + scriptDestination: number; + slots: Map>; + events: ComponentEvents; + isTsFile: boolean; + uses$$SlotsInterface: boolean; + mode?: 'tsx' | 'dts'; +} + +export function createRenderFunction({ + str, + scriptTag, + scriptDestination, + slots, + getters, + events, + exportedNames, + isTsFile, + uses$$props, + uses$$restProps, + uses$$slots, + uses$$SlotsInterface, + generics, + mode +}: CreateRenderFunctionPara) { + const htmlx = str.original; + let propsDecl = ''; + + if (uses$$props) { + propsDecl += ' let $$props = __sveltets_allPropsType();'; + } + if (uses$$restProps) { + propsDecl += ' let $$restProps = __sveltets_restPropsType();'; + } + + if (uses$$slots) { + propsDecl += + ' let $$slots = __sveltets_slotsType({' + + Array.from(slots.keys()) + .map((name) => `'${name}': ''`) + .join(', ') + + '});'; + } + + const slotsDeclaration = + slots.size > 0 && mode !== 'dts' + ? '\n' + + surroundWithIgnoreComments( + ';const __sveltets_ensureSlot = __sveltets_createEnsureSlot' + + (uses$$SlotsInterface ? '<$$Slots>' : '') + + '();' + ) + : ''; + + if (scriptTag) { + //I couldn't get magicstring to let me put the script before the <> we prepend during conversion of the template to jsx, so we just close it instead + const scriptTagEnd = htmlx.lastIndexOf('>', scriptTag.content.start) + 1; + str.overwrite(scriptTag.start, scriptTag.start + 1, ';'); + str.overwrite( + scriptTag.start + 1, + scriptTagEnd, + `function render${generics.toDefinitionString(true)}() {${propsDecl}\n` + ); + + const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1); + // wrap template with callback + str.overwrite(scriptEndTagStart, scriptTag.end, `${slotsDeclaration};\n() => (<>`, { + contentOnly: true + }); + } else { + str.prependRight( + scriptDestination, + `;function render${generics.toDefinitionString(true)}() {` + + `${propsDecl}${slotsDeclaration}\n<>` + ); + } + + const slotsAsDef = uses$$SlotsInterface + ? '{} as unknown as $$Slots' + : '{' + + Array.from(slots.entries()) + .map(([name, attrs]) => { + const attrsAsString = Array.from(attrs.entries()) + .map(([exportName, expr]) => `${exportName}:${expr}`) + .join(', '); + return `'${name}': {${attrsAsString}}`; + }) + .join(', ') + + '}'; + + const returnString = + `\nreturn { props: ${exportedNames.createPropsStr(isTsFile)}` + + `, slots: ${slotsAsDef}` + + `, getters: ${createRenderFunctionGetterStr(getters)}` + + `, events: ${events.toDefString()} }}`; + + // wrap template with callback + if (scriptTag) { + str.append(');'); + } + + str.append(returnString); +} diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index 22918dece..8f6650757 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -6,7 +6,6 @@ import { ComponentDocumentation } from './nodes/ComponentDocumentation'; import { ComponentEvents } from './nodes/ComponentEvents'; import { EventHandler } from './nodes/event-handler'; import { ExportedNames } from './nodes/ExportedNames'; -import { createRenderFunctionGetterStr } from './nodes/exportgetters'; import { handleScopeAndResolveForSlot, handleScopeAndResolveLetVarForSlot @@ -16,24 +15,13 @@ import { Scripts } from './nodes/Scripts'; import { SlotHandler } from './nodes/slot'; import { Stores } from './nodes/Stores'; import TemplateScope from './nodes/TemplateScope'; -import { - InstanceScriptProcessResult, - processInstanceScriptContent -} from './processInstanceScriptContent'; +import { processInstanceScriptContent } from './processInstanceScriptContent'; import { processModuleScriptTag } from './processModuleScriptTag'; import { ScopeStack } from './utils/Scope'; import { svelteShims } from './svelteShims'; import { Generics } from './nodes/Generics'; import { addComponentExport } from './addComponentExport'; - -interface CreateRenderFunctionPara extends InstanceScriptProcessResult { - str: MagicString; - scriptTag: Node; - scriptDestination: number; - slots: Map>; - events: ComponentEvents; - isTsFile: boolean; -} +import { createRenderFunction } from './createRenderFunction'; type TemplateProcessResult = { uses$$props: boolean; @@ -293,87 +281,6 @@ function processSvelteTemplate( }; } -function createRenderFunction({ - str, - scriptTag, - scriptDestination, - slots, - getters, - events, - exportedNames, - isTsFile, - uses$$props, - uses$$restProps, - uses$$slots, - generics -}: CreateRenderFunctionPara) { - const htmlx = str.original; - let propsDecl = ''; - - if (uses$$props) { - propsDecl += ' let $$props = __sveltets_allPropsType();'; - } - if (uses$$restProps) { - propsDecl += ' let $$restProps = __sveltets_restPropsType();'; - } - - if (uses$$slots) { - propsDecl += - ' let $$slots = __sveltets_slotsType({' + - Array.from(slots.keys()) - .map((name) => `'${name}': ''`) - .join(', ') + - '});'; - } - - if (scriptTag) { - //I couldn't get magicstring to let me put the script before the <> we prepend during conversion of the template to jsx, so we just close it instead - const scriptTagEnd = htmlx.lastIndexOf('>', scriptTag.content.start) + 1; - str.overwrite(scriptTag.start, scriptTag.start + 1, ';'); - str.overwrite( - scriptTag.start + 1, - scriptTagEnd, - `function render${generics.toDefinitionString(true)}() {${propsDecl}\n` - ); - - const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1); - // wrap template with callback - str.overwrite(scriptEndTagStart, scriptTag.end, ';\n() => (<>', { - contentOnly: true - }); - } else { - str.prependRight( - scriptDestination, - `;function render${generics.toDefinitionString(true)}() {${propsDecl}\n<>` - ); - } - - const slotsAsDef = - '{' + - Array.from(slots.entries()) - .map(([name, attrs]) => { - const attrsAsString = Array.from(attrs.entries()) - .map(([exportName, expr]) => `${exportName}:${expr}`) - .join(', '); - return `'${name}': {${attrsAsString}}`; - }) - .join(', ') + - '}'; - - const returnString = - `\nreturn { props: ${exportedNames.createPropsStr(isTsFile)}` + - `, slots: ${slotsAsDef}` + - `, getters: ${createRenderFunctionGetterStr(getters)}` + - `, events: ${events.toDefString()} }}`; - - // wrap template with callback - if (scriptTag) { - str.append(');'); - } - - str.append(returnString); -} - export function svelte2tsx( svelte: string, options: { @@ -424,6 +331,7 @@ export function svelte2tsx( let exportedNames = new ExportedNames(); let getters = new Set(); let generics = new Generics(str, 0); + let uses$$SlotsInterface = false; if (scriptTag) { //ensure it is between the module script and the rest of the template (the variables need to be declared before the jsx template) if (scriptTag.start != instanceScriptTarget) { @@ -434,7 +342,7 @@ export function svelte2tsx( uses$$restProps = uses$$restProps || res.uses$$restProps; uses$$slots = uses$$slots || res.uses$$slots; - ({ exportedNames, events, getters, generics } = res); + ({ exportedNames, events, getters, generics, uses$$SlotsInterface } = res); } //wrap the script tag and template content in a function returning the slot and exports @@ -450,7 +358,9 @@ export function svelte2tsx( uses$$props, uses$$restProps, uses$$slots, - generics + uses$$SlotsInterface, + generics, + mode: options.mode }); // we need to process the module script after the instance script has moved otherwise we get warnings about moving edited items diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts index 50ed229be..c56b36134 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts @@ -1,15 +1,16 @@ import ts from 'typescript'; import { EventHandler } from './event-handler'; -import { getVariableAtTopLevel, getLastLeadingDoc } from '../utils/tsAst'; +import { + getVariableAtTopLevel, + getLastLeadingDoc, + isInterfaceOrTypeDeclaration +} from '../utils/tsAst'; import MagicString from 'magic-string'; export function is$$EventsDeclaration( node: ts.Node ): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration { - return ( - (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && - node.name.text === '$$Events' - ); + return isInterfaceOrTypeDeclaration(node) && node.name.text === '$$Events'; } /** diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts index a4a1a3fac..4ee4396fa 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts @@ -59,7 +59,7 @@ export function handleStore(node: Node, parent: Node, str: MagicString): void { // - in order to get ts errors if store is not assignable to SvelteStore // - use $store variable defined above to get ts flow control const dollar = str.original.indexOf('$', node.start); - str.overwrite(dollar, dollar + 1, '(__sveltets_store_get('); + str.overwrite(dollar, dollar + 1, '(__sveltets_store_get(', { contentOnly: true }); str.prependLeft(node.end, `), $${storename})`); } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts index 4ec1725c8..ea4b3a217 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts @@ -12,6 +12,8 @@ import TemplateScope from './TemplateScope'; import { SvelteIdentifier, WithName } from '../../interfaces'; import { getTypeForComponent } from '../../htmlxtojsx/utils/node-utils'; import { Directive } from 'svelte/types/compiler/interfaces'; +import ts from 'typescript'; +import { isInterfaceOrTypeDeclaration } from '../utils/tsAst'; function attributeStrValueAsJsExpression(attr: Node): string { if (attr.value.length == 0) return "''"; //wut? @@ -30,6 +32,12 @@ function attributeStrValueAsJsExpression(attr: Node): string { return '"__svelte_ts_string"'; } +export function is$$SlotsDeclaration( + node: ts.Node +): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration { + return isInterfaceOrTypeDeclaration(node) && node.name.text === '$$Slots'; +} + export class SlotHandler { constructor(private readonly htmlx: string) {} diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index e88d0915e..71dec7fe4 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -14,6 +14,7 @@ import { Scope } from './utils/Scope'; import { handleTypeAssertion } from './nodes/handleTypeAssertion'; import { ImplicitStoreValues } from './nodes/ImplicitStoreValues'; import { Generics } from './nodes/Generics'; +import { is$$SlotsDeclaration } from './nodes/slot'; export interface InstanceScriptProcessResult { exportedNames: ExportedNames; @@ -21,6 +22,7 @@ export interface InstanceScriptProcessResult { uses$$props: boolean; uses$$restProps: boolean; uses$$slots: boolean; + uses$$SlotsInterface: boolean; getters: Set; generics: Generics; } @@ -55,6 +57,7 @@ export function processInstanceScriptContent( let uses$$props = false; let uses$$restProps = false; let uses$$slots = false; + let uses$$SlotsInterface = false; //track if we are in a declaration scope let isDeclaration = false; @@ -354,6 +357,9 @@ export function processInstanceScriptContent( if (is$$EventsDeclaration(node)) { events.setComponentEventsInterface(node, astOffset); } + if (is$$SlotsDeclaration(node)) { + uses$$SlotsInterface = true; + } if (ts.isVariableStatement(node)) { const exportModifier = findExportKeyword(node); @@ -512,6 +518,7 @@ export function processInstanceScriptContent( uses$$props, uses$$restProps, uses$$slots, + uses$$SlotsInterface, getters, generics }; diff --git a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts index 651b8c3e9..02d835951 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts @@ -6,6 +6,7 @@ import { handleTypeAssertion } from './nodes/handleTypeAssertion'; import { Generics } from './nodes/Generics'; import { is$$EventsDeclaration } from './nodes/ComponentEvents'; import { throwError } from './utils/error'; +import { is$$SlotsDeclaration } from './nodes/slot'; export function processModuleScriptTag( str: MagicString, @@ -30,6 +31,7 @@ export function processModuleScriptTag( generics.throwIfIsGeneric(node); throwIfIs$$EventsDeclaration(node, str, astOffset); + throwIfIs$$SlotsDeclaration(node, str, astOffset); ts.forEachChild(node, (n) => walk(n)); }; @@ -72,11 +74,21 @@ function resolveImplicitStoreValue( function throwIfIs$$EventsDeclaration(node: ts.Node, str: MagicString, astOffset: number) { if (is$$EventsDeclaration(node)) { - throwError( - node.getStart() + astOffset, - node.getEnd() + astOffset, - '$$Events can only be declared in the instance script', - str.original - ); + throw$$Error(node, str, astOffset, '$$Events'); } } + +function throwIfIs$$SlotsDeclaration(node: ts.Node, str: MagicString, astOffset: number) { + if (is$$SlotsDeclaration(node)) { + throw$$Error(node, str, astOffset, '$$Slots'); + } +} + +function throw$$Error(node: ts.Node, str: MagicString, astOffset: number, type: string) { + throwError( + node.getStart() + astOffset, + node.getEnd() + astOffset, + `${type} can only be declared in the instance script`, + str.original + ); +} diff --git a/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts index d9d9246b1..84b724fae 100644 --- a/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts +++ b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts @@ -111,6 +111,7 @@ declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {} declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {}; declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {}; declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {}; +declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2]; declare function __sveltets_cssProp(prop: Record): {}; declare function __sveltets_ctorOf(type: T): AConstructorTypeOf; declare function __sveltets_instanceOf(type: AConstructorTypeOf): T; diff --git a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts index 5f36a4270..935fdecc6 100644 --- a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts +++ b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts @@ -1,5 +1,11 @@ import ts from 'typescript'; +export function isInterfaceOrTypeDeclaration( + node: ts.Node +): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration { + return ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node); +} + export function findExportKeyword(node: ts.Node) { return node.modifiers?.find((x) => x.kind == ts.SyntaxKind.ExportKeyword); } diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts index 022f58b2c..d6b84da38 100644 --- a/packages/svelte2tsx/svelte-shims.d.ts +++ b/packages/svelte2tsx/svelte-shims.d.ts @@ -112,6 +112,7 @@ declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {} declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {}; declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {}; declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {}; +declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2]; declare function __sveltets_cssProp(prop: Record): {}; declare function __sveltets_ctorOf(type: T): AConstructorTypeOf; declare function __sveltets_instanceOf(type: AConstructorTypeOf): T; diff --git a/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx index 97ef481ca..93633da6c 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx @@ -1,9 +1,10 @@ /// -<>;function render() { {/** +<>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ {/** ------------------------------------------------------------------------------------------------------------------------------------------------------ */} <> {/** =# Originless mappings -<>↲ [generated] line 3 +<>↲ [generated] line 4 ↲ [original] line 1 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} @@ -11,24 +12,28 @@ fallback {/** ------------------------------------------------------------------------------------------------------------------------------------------------------ */} -fallback {/** -fallback↲ [generated] line 7 -fallback
↲ -fallback
↲ [original] line 5 +fallback {/** +fallback
↲ [generated] line 8 +fallback↲ +fallback↲ [original] line 5 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} -fallback {/** ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** -↲ [generated] line 13 +↲ [generated] line 14 [original] line 11 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} return { props: {}, slots: {'default': {}, 'foo': {}, 'bar': {foo:foo, baz:baz}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-default-slot/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-default-slot/expected.tsx index 895d9c458..68e4a10e6 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-default-slot/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-default-slot/expected.tsx @@ -2,10 +2,11 @@ <>;function render() { let b = 7; -; + +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/; () => (<>
- Hello + Hello
); return { props: {}, slots: {'default': {a:b}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx index b11d718de..5625aa9af 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx @@ -4,11 +4,12 @@ let b = 7; let d = 5; let e = 5; -; + +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/; () => (<>
- Hello - + Hello +
); return { props: {}, slots: {'default': {a:b}, 'test': {c:d, e:e}, 'abc-cde.113': {}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/expected.tsx new file mode 100644 index 000000000..b1ef275fc --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/expected.tsx @@ -0,0 +1,24 @@ +/// +<>;function render() { + + interface $$Slots { + default: { + a: number; + }, + foo: { + b: number + } + } + let b = 7; + +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot<$$Slots>();/*Ωignore_endΩ*/; +() => (<> + +
+ + +
); +return { props: {}, slots: {} as unknown as $$Slots, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render()))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/input.svelte new file mode 100644 index 000000000..03e4637c2 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-interface/input.svelte @@ -0,0 +1,16 @@ + + +
+ + +
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/expected.tsx new file mode 100644 index 000000000..2a4e218cc --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/expected.tsx @@ -0,0 +1,24 @@ +/// +<>;function render() { + + type $$Slots = { + default: { + a: number; + }, + foo: { + b: number + } + } + let b = 7; + +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot<$$Slots>();/*Ωignore_endΩ*/; +() => (<> + +
+ + +
); +return { props: {}, slots: {} as unknown as $$Slots, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render()))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/input.svelte new file mode 100644 index 000000000..9645c1d1d --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-$$slot-type/input.svelte @@ -0,0 +1,16 @@ + + +
+ + +
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx index b5e896560..1b3561124 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx @@ -2,10 +2,11 @@ <>;function render() { let b = 7; -; + +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/; () => (<>
- Hello + Hello
); return { props: {}, slots: {'default': {a:b, b:b, c:"b", d:"__svelte_ts_string", e:b}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx index b0d280206..3e0e8438c 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx @@ -1,7 +1,8 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{() => { let {foo} = /*Ωignore_startΩ*/new Parent({target: __sveltets_any(''), props: {'propA':true, 'propB':propB, 'propC':"", 'propD':"", 'propE':""}})/*Ωignore_endΩ*/.$$slot_def['default'];<> - + }} return { props: {}, slots: {'default': {foo:__sveltets_instanceOf(Parent).$$slot_def['default'].foo}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-await/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-await/expected.tsx index 5e279cf4d..618d83b3b 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-await/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-await/expected.tsx @@ -1,12 +1,13 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{() => {let _$$p = (promise); __sveltets_awaitThen(_$$p, (value) => {<> - Hello + Hello }, (err) => {<> - Hello + Hello })}} {() => {let _$$p = (promise2); __sveltets_awaitThen(_$$p, ({ b }) => {<> - Hello + Hello })}} return { props: {}, slots: {'default': {a:__sveltets_unwrapPromiseLike(promise)}, 'err': {err:__sveltets_any({})}, 'second': {a:(({ b }) => b)(__sveltets_unwrapPromiseLike(promise2))}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-each/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-each/expected.tsx index 4cc47b6fd..223791819 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-each/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-inside-each/expected.tsx @@ -1,10 +1,11 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{__sveltets_each(items, (item) => <> - Hello + Hello )} {__sveltets_each(items2, ({ a }) => <> - Hello + Hello )} return { props: {}, slots: {'default': {a:__sveltets_unwrapArr(items)}, 'second': {a:(({ a }) => a)(__sveltets_unwrapArr(items2))}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward-named-slot/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward-named-slot/expected.tsx index 60905548c..db46e8aed 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward-named-slot/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward-named-slot/expected.tsx @@ -1,8 +1,9 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <> {() => { let {a} = /*Ωignore_startΩ*/new Component({target: __sveltets_any(''), props: {}})/*Ωignore_endΩ*/.$$slot_def['b'];<>
- +
}}
return { props: {}, slots: {'default': {a:__sveltets_instanceOf(Component).$$slot_def['b'].a}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward/expected.tsx index f3fadf13e..ddab2d708 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-let-forward/expected.tsx @@ -1,7 +1,8 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{() => { let {name:n, thing, whatever:{ bla }} = /*Ωignore_startΩ*/new Component({target: __sveltets_any(''), props: {}})/*Ωignore_endΩ*/.$$slot_def['default'];<> - + }} return { props: {}, slots: {'default': {n:__sveltets_instanceOf(Component).$$slot_def['default'].name, thing:__sveltets_instanceOf(Component).$$slot_def['default'].thing, bla:(({ bla }) => bla)(__sveltets_instanceOf(Component).$$slot_def['default'].whatever)}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-nest-scope/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-nest-scope/expected.tsx index 555711745..38d539ec2 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-nest-scope/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-nest-scope/expected.tsx @@ -1,16 +1,17 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{__sveltets_each(items, (item) => <> {__sveltets_each(item, ({ a }) => <> - Hello + Hello )} - + )} {() => { let {c} = /*Ωignore_startΩ*/new Component({target: __sveltets_any(''), props: {}})/*Ωignore_endΩ*/.$$slot_def['default'];<>{ c }}} {() => {let _$$p = (promise); __sveltets_awaitThen(_$$p, (d) => {<> {d} })}} - + return { props: {}, slots: {'default': {a:(({ a }) => a)(__sveltets_unwrapArr(__sveltets_unwrapArr(items)))}, 'second': {a:a}, 'third': {d:d, c:c}}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render()))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx index d144f4495..06a668fcd 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx @@ -1,9 +1,10 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{__sveltets_each(items, (item) => <> - Hello + Hello )} -return { props: {}, slots: {'default': {a:__sveltets_unwrapArr(items), b:{ item:__sveltets_unwrapArr(items) }, c:{ item: 'abc' }.item, d:{ item: __sveltets_unwrapArr(items) }}}, getters: {}, events: {} }} +return { props: {}, slots: {'default': {a:__sveltets_unwrapArr(items), b:{ item:__sveltets_unwrapArr(items) }, c:{ item: 'abc' }.item, d:{ item: __sveltets_unwrapArr(items) }, e:$item, f:$item}}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render()))) { } \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/input.svelte index 61411a164..684b9027f 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/input.svelte +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/input.svelte @@ -1,3 +1,3 @@ {#each items as item} - Hello + Hello {/each} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-var-shadowing/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-var-shadowing/expected.tsx index b2313bda5..7e7c2f879 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-var-shadowing/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-var-shadowing/expected.tsx @@ -1,7 +1,8 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{__sveltets_each(items, (items) => <> - Hello + Hello )} return { props: {}, slots: {'default': {a:__sveltets_unwrapArr(items)}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx index 69130333b..deef3d6b0 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx @@ -109,6 +109,7 @@ declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {} declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {}; declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {}; declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {}; +declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2]; declare function __sveltets_cssProp(prop: Record): {}; declare function __sveltets_ctorOf(type: T): AConstructorTypeOf; declare function __sveltets_instanceOf(type: AConstructorTypeOf): T; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx index cb9085153..2fa15b916 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx @@ -1,5 +1,6 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <> return { props: {}, slots: {'s': {}}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx index 04e22e766..376f8b38a 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx @@ -109,6 +109,7 @@ declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {} declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {}; declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {}; declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {}; +declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2]; declare function __sveltets_cssProp(prop: Record): {}; declare function __sveltets_ctorOf(type: T): AConstructorTypeOf; declare function __sveltets_instanceOf(type: AConstructorTypeOf): T; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx index 0b40768c1..a5dd1d023 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx @@ -14,10 +14,11 @@ function render(); -; + +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/; () => (<> -); +); return { props: {a: a , b: b , c: c} as {a: A, b: B, c: C}, slots: {'default': {c:c}}, getters: {}, events: {...__sveltets_toEventTypings<{a: A}>()} }} class __sveltets_Render { props() { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx index 1f56c00c3..5c295ae54 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx @@ -109,6 +109,7 @@ declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {} declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {}; declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {}; declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {}; +declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2]; declare function __sveltets_cssProp(prop: Record): {}; declare function __sveltets_ctorOf(type: T): AConstructorTypeOf; declare function __sveltets_instanceOf(type: AConstructorTypeOf): T; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots-script/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots-script/expected.tsx index e251118e7..85de923aa 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots-script/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots-script/expected.tsx @@ -3,7 +3,8 @@ let name = $$slots.foo; let dashedName = $$slots['dashed-name']; -; + +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/; () => (<>

{name}

diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots/expected.tsx index 63a8abe6c..f326b0a69 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$$slots/expected.tsx @@ -1,5 +1,6 @@ /// <>;function render() { let $$slots = __sveltets_slotsType({'foo': '', 'dashed-name': '', 'default': ''}); +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>

{$$slots.foo}

{$$slots['dashed-name']}

diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-svelte-components-let-forward/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-svelte-components-let-forward/expected.tsx index eaffa4eff..b60c321f1 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-svelte-components-let-forward/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-svelte-components-let-forward/expected.tsx @@ -1,12 +1,13 @@ /// <>;function render() { +/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_createEnsureSlot();/*Ωignore_endΩ*/ <>{(true) ? <> {() => { let {prop} = __sveltets_instanceOf(__sveltets_componentType()).$$slot_def['default'];/*Ωignore_startΩ*/((true)) && /*Ωignore_endΩ*/<> - + }} : <>} {() => { let {prop} = __sveltets_instanceOf(__sveltets_componentType()).$$slot_def['default'];<> - + }} return { props: {}, slots: {'default': {prop:__sveltets_instanceOf(__sveltets_componentType()).$$slot_def['default'].prop}}, getters: {}, events: {} }} From 0c377ee74336d9c5b23ebaa3806543084895bca7 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 17 Jun 2021 09:18:47 +0200 Subject: [PATCH 2/2] lint --- packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts | 6 +++--- packages/svelte2tsx/src/htmlxtojsx/nodes/slot.ts | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts b/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts index 1ce4c301d..c20995990 100644 --- a/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts +++ b/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts @@ -125,7 +125,7 @@ export function handleAttribute( str.appendRight(attr.start, `${attrName}=`); if (shouldApplySlotCheck) { str.prependRight(attr.start + 1, ensureSlotStr); - str.prependLeft(attr.end - 1, `)`); + str.prependLeft(attr.end - 1, ')'); } return; } @@ -155,7 +155,7 @@ export function handleAttribute( if (needsNumberConversion) { const begin = '{' + (shouldApplySlotCheck ? ensureSlotStr : ''); - const end = shouldApplySlotCheck ? `)}` : '}'; + const end = shouldApplySlotCheck ? ')}' : '}'; if (needsQuotes) { str.prependRight(equals + 1, begin); str.appendLeft(attr.end, end); @@ -165,7 +165,7 @@ export function handleAttribute( } } else if (needsQuotes) { const begin = shouldApplySlotCheck ? `{${ensureSlotStr}"` : '"'; - const end = shouldApplySlotCheck ? `")}` : '"'; + const end = shouldApplySlotCheck ? '")}' : '"'; str.prependRight(equals + 1, begin); str.appendLeft(attr.end, end); } else if (shouldApplySlotCheck) { diff --git a/packages/svelte2tsx/src/htmlxtojsx/nodes/slot.ts b/packages/svelte2tsx/src/htmlxtojsx/nodes/slot.ts index 5a4a701a5..1be21930d 100644 --- a/packages/svelte2tsx/src/htmlxtojsx/nodes/slot.ts +++ b/packages/svelte2tsx/src/htmlxtojsx/nodes/slot.ts @@ -17,6 +17,9 @@ interface ComponentNode extends BaseNode { [shadowedPropsSymbol]?: PropsShadowedByLet[]; } +/** + * Transforms the usage of a slot (let:xx) + */ export function handleSlot( htmlx: string, str: MagicString,