From 10820f9817eb49432bac0fd7434de3512c285a55 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 22 Nov 2024 21:02:38 +0100 Subject: [PATCH 1/6] fix: ensure organize imports doesn't mess with generated $$Component type #2594 --- .../src/svelte2tsx/nodes/HoistableInterfaces.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts index 093a4a132..7ee4de9b9 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts @@ -337,8 +337,11 @@ export class HoistableInterfaces { for (const [name, node] of hoistable) { let pos = node.pos + astOffset; - // node.pos includes preceeding whitespace, which could mean we accidentally also move stuff appended to a previous node - if (name !== '$$ComponentProps') { + if (name === '$$ComponentProps') { + // So that organize imports doesn't mess with the types + str.prependRight(pos, '\n'); + } else { + // node.pos includes preceeding whitespace, which could mean we accidentally also move stuff appended to a previous node if (str.original[pos] === '\r') { pos++; } @@ -346,7 +349,8 @@ export class HoistableInterfaces { pos++; } - // jsdoc comments would be ignored if they are on the same line as the ;, so we add a newline, too + // jsdoc comments would be ignored if they are on the same line as the ;, so we add a newline, too. + // Also helps with organize imports not messing with the types str.prependRight(pos, ';\n'); str.appendLeft(node.end + astOffset, ';'); } From 0bf5836857f5ab461077bb299ed35c21c278fdb8 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 22 Nov 2024 21:02:54 +0100 Subject: [PATCH 2/6] chore: pin TS to 5.6 for now until we properly investigated supporting it --- package.json | 2 +- packages/language-server/package.json | 2 +- packages/svelte-check/package.json | 2 +- packages/svelte-vscode/package.json | 2 +- packages/svelte2tsx/package.json | 2 +- packages/typescript-plugin/package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 438c5c68b..78483ce60 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "prettier --check ." }, "dependencies": { - "typescript": "^5.6.3" + "typescript": "~5.6.3" }, "devDependencies": { "cross-env": "^7.0.2", diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 6d7a5d4ca..3021cb374 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -62,7 +62,7 @@ "prettier-plugin-svelte": "^3.3.0", "svelte": "^4.2.19", "svelte2tsx": "workspace:~", - "typescript": "^5.6.3", + "typescript": "~5.6.3", "typescript-auto-import-cache": "^0.3.5", "vscode-css-languageservice": "~6.3.0", "vscode-html-languageservice": "~5.3.0", diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 8cf77806e..df5d66db3 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -54,7 +54,7 @@ "rollup-plugin-copy": "^3.4.0", "svelte": "^4.2.19", "svelte-language-server": "workspace:*", - "typescript": "^5.6.3", + "typescript": "~5.6.3", "vscode-languageserver": "8.0.2", "vscode-languageserver-protocol": "3.17.2", "vscode-languageserver-types": "3.17.2", diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index 07a9c2542..30ab2dd03 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -732,7 +732,7 @@ "@types/vscode": "^1.67", "js-yaml": "^3.14.0", "tslib": "^2.4.0", - "typescript": "^5.6.3", + "typescript": "~5.6.3", "vscode-tmgrammar-test": "^0.0.11" }, "dependencies": { diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 7a9f1758c..c608983da 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -40,7 +40,7 @@ "svelte": "~4.2.19", "tiny-glob": "^0.2.6", "tslib": "^2.4.0", - "typescript": "^5.6.3" + "typescript": "~5.6.3" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index b3e934521..9f41a5dfe 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -24,7 +24,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^18.0.0", - "typescript": "^5.6.3", + "typescript": "~5.6.3", "svelte": "^4.2.19" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e1703cb9..8e27e7797 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ importers: .: dependencies: typescript: - specifier: ^5.6.3 + specifier: ~5.6.3 version: 5.6.3 devDependencies: cross-env: @@ -58,7 +58,7 @@ importers: specifier: workspace:~ version: link:../svelte2tsx typescript: - specifier: ^5.6.3 + specifier: ~5.6.3 version: 5.6.3 typescript-auto-import-cache: specifier: ^0.3.5 @@ -168,7 +168,7 @@ importers: specifier: workspace:* version: link:../language-server typescript: - specifier: ^5.6.3 + specifier: ~5.6.3 version: 5.6.3 vscode-languageserver: specifier: 8.0.2 @@ -217,7 +217,7 @@ importers: specifier: ^2.4.0 version: 2.5.2 typescript: - specifier: ^5.6.3 + specifier: ~5.6.3 version: 5.6.3 vscode-tmgrammar-test: specifier: ^0.0.11 @@ -299,7 +299,7 @@ importers: specifier: ^2.4.0 version: 2.5.2 typescript: - specifier: ^5.6.3 + specifier: ~5.6.3 version: 5.6.3 packages/typescript-plugin: @@ -318,7 +318,7 @@ importers: specifier: ^4.2.19 version: 4.2.19 typescript: - specifier: ^5.6.3 + specifier: ~5.6.3 version: 5.6.3 packages: From fda35fedbbcc877b210f3b4644ac5edfe720234a Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 22 Nov 2024 21:06:15 +0100 Subject: [PATCH 3/6] chore: update tests --- .../htmlx2jsx/samples/binding/expectedv2.js | 6 ++-- .../samples/element-attributes/mappings.jsx | 29 ++++++++++--------- .../samples/large-sample-1/mappings.jsx | 6 ++-- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js index 132f731ac..5b3a9a052 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js @@ -1,6 +1,6 @@ - { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} + { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} + { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} + { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/} diff --git a/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx index b5a478a40..f6aa46383 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx @@ -133,24 +133,25 @@ async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});} ↲ />↲ [original] line 21 (rest generated at line 13) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} - { svelteHTML.createElement("element", { "bind:foo":bar,});/*Ωignore_startΩ*/() => bar = __sveltets_2_any(null);/*Ωignore_endΩ*/}}; {/** -•{•svelteHTML.createElement("element",•{••"bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15 -•{•svelteHTML.createElement("element",•{ "bind:foo": [generated] subset -< element ↲ + { svelteHTML.createElement("element", { "bind:foo":bar,});/*Ωignore_startΩ*/() => bar = __sveltets_2_any(null);/*Ωignore_endΩ*/}}; {/** +•{•svelteHTML.createElement("element",•{•••"bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15 +•{•svelteHTML.createElement("element",•{ " [generated] subset +< element ↲ •bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15 - • bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] subset - • bar} -• bar} -••••bind:foo={bar}↲ [original] line 24 +•{•svelteHTML.createElement("element",•{•••"bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15 + •• bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] subset + •{ bind:foo= bar} + #== Order-breaking mappings +• bind:foo={bar} +••••bind:foo={bar}↲ [original] line 24 -•{•svelteHTML.createElement("element",•{••"bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15 - • [generated] subset - / +•{•svelteHTML.createElement("element",•{•••"bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15 + • [generated] subset + / / -/> [original] line 25 +/> [original] line 25 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} return { props: /** @type {Record} */ ({}), slots: {}, events: {} }} diff --git a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx index 44c3de9a8..419f8ad51 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx @@ -368,9 +368,9 @@ s ↲ ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("svelte:window", { "bind:innerWidth":width,});/*Ωignore_startΩ*/() => width = __sveltets_2_any(null);/*Ωignore_endΩ*/} {/** ••{•svelteHTML.createElement("svelte:window",•{•"bind:innerWidth":width,});/*Ωignore_startΩ*/()•=>•width•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 133 -<> ib width} ↲ - #=============================================# Order-breaking mappings -< bi width} >↲ +<> { bind:innerWidth= width} ↲ + #=============================================#= Order-breaking mappings +< bind:innerWidth={width} >↲ ↲ [original] line 269 (rest generated at line 134) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** From 89cc22baef10558a2cd6c720c452b936ebc1ecf0 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:23:54 +0100 Subject: [PATCH 4/6] fix: ensure imports without semicolon doesn't break intellisense (#2610) ...by adding a generated semicolon at the end. That prevents TypeScript from going up until the next semicolon it finds, which may be from another generated code, messing up the generated->original end position #2608 #2607 --- .../features/CodeActionsProvider.test.ts | 15 ++------------- .../svelte2tsx/nodes/handleImportDeclaration.ts | 13 ++++++++++--- .../test/svelte2tsx/samples/imports/expectedv2.ts | 2 +- .../jsdoc-before-first-import/expectedv2.ts | 2 +- .../ts-runes-hoistable-props-2.v5/expectedv2.ts | 3 ++- .../svelte2tsx/samples/ts-runes.v5/expectedv2.ts | 3 ++- .../expectedv2.ts | 3 ++- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts index 1ad7ac620..53fadc529 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts @@ -1755,20 +1755,9 @@ describe('CodeActionsProvider', function () { line: 1 } } - }, - { - newText: "import { } from './somepng.png';\n", - range: { - end: { - character: 0, - line: 4 - }, - start: { - character: 4, - line: 3 - } - } } + // Because the generated code adds a ; after the last import, the + // second import is not appearing in the edits here ], textDocument: { uri: getUri('organize-imports-leading-comment.svelte'), diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts index 9a0b4a5e9..4f022260b 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts @@ -25,9 +25,8 @@ export function handleFirstInstanceImport( hasModuleScript: boolean, str: MagicString ) { - const firstImport = tsAst.statements - .filter(ts.isImportDeclaration) - .sort((a, b) => a.end - b.end)[0]; + const imports = tsAst.statements.filter(ts.isImportDeclaration).sort((a, b) => a.end - b.end); + const firstImport = imports[0]; if (!firstImport) { return; } @@ -42,4 +41,12 @@ export function handleFirstInstanceImport( : firstImport.getStart(); str.appendRight(start + astOffset, '\n' + (hasModuleScript ? '\n' : '')); + + // Add a semi-colon to the last import if it doesn't have one, to prevent auto completion + // and imports from being added at the wrong position + const lastImport = imports[imports.length - 1]; + const end = lastImport.end + astOffset - 1; + if (str.original[end] !== ';') { + str.overwrite(end, lastImport.end + astOffset, str.original[end] + ';\n'); + } } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/imports/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/imports/expectedv2.ts index ae4e57e82..b80485ccf 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/imports/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/imports/expectedv2.ts @@ -2,7 +2,7 @@ ; import { a as b } from "./test.svelte" -import * as c from "b.ts" +import * as c from "b.ts"; function render() { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/jsdoc-before-first-import/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/jsdoc-before-first-import/expectedv2.ts index 3e8c7c047..138ef5946 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/jsdoc-before-first-import/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/jsdoc-before-first-import/expectedv2.ts @@ -2,7 +2,7 @@ ;// non-leading comment /**@typedef {{ a: string }} Foo */ -import '' +import ''; function render() { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/expectedv2.ts index 556d5bb46..b66a2a1d8 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/expectedv2.ts @@ -5,7 +5,8 @@ interface Dependency { a: number; b: typeof value; - };;type $$ComponentProps = { a: Dependency, b: string };function render() { + }; +;type $$ComponentProps = { a: Dependency, b: string };function render() { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts index b15ca4992..ae5caf1a7 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts @@ -1,5 +1,6 @@ /// -;;type $$ComponentProps = { a: number, b: string };function render() { +; +;type $$ComponentProps = { a: number, b: string };function render() { let { a, b }:/*Ωignore_startΩ*/$$ComponentProps/*Ωignore_endΩ*/ = $props(); let x = $state(0); diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts index 51e17c6ce..385c54c68 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts @@ -1,5 +1,6 @@ /// -;;type $$ComponentProps = {form: boolean, data: true };function render() { +; +;type $$ComponentProps = {form: boolean, data: true };function render() { const snapshot: any = {}; let { form, data }:/*Ωignore_startΩ*/$$ComponentProps/*Ωignore_endΩ*/ = $props(); From 131f78aa805c54bb850710c44fd98f17d448f1d3 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:23:48 +0100 Subject: [PATCH 5/6] fix: make sure snapshots are shared per ProjectService (#2614) The ProjectService contains snapshots. Multiple TypeScript plugins may be instantiated for one projectservice. Ensure we share our internal Svelte snapshots between those instances. #2602 --- .../typescript-plugin/src/svelte-snapshots.ts | 160 +++++++++--------- 1 file changed, 76 insertions(+), 84 deletions(-) diff --git a/packages/typescript-plugin/src/svelte-snapshots.ts b/packages/typescript-plugin/src/svelte-snapshots.ts index 438ceb6ca..87cad7305 100644 --- a/packages/typescript-plugin/src/svelte-snapshots.ts +++ b/packages/typescript-plugin/src/svelte-snapshots.ts @@ -269,7 +269,7 @@ export class SvelteSnapshot { } export class SvelteSnapshotManager { - private snapshots = new Map(); + private snapshots: Map; constructor( private typescript: typeof ts, @@ -280,7 +280,9 @@ export class SvelteSnapshotManager { /** undefined if no node_modules with Svelte next to tsconfig.json */ private svelteCompiler: typeof import('svelte/compiler') | undefined ) { - this.patchProjectServiceReadFile(); + this.patchProjectService(); + // @ts-expect-error + this.snapshots = this.projectService[snapshots]; } get(fileName: string) { @@ -321,92 +323,78 @@ export class SvelteSnapshotManager { return snapshot; } - private patchProjectServiceReadFile() { - // @ts-ignore The projectService is shared across some instances, make sure we patch readFile only once - if (!this.projectService.host[onReadSvelteFile]) { - this.logger.log('patching projectService host readFile'); + private patchProjectService() { + // @ts-expect-error The projectService is shared across some instances, make sure we patch only once + if (this.projectService[snapshots]) return; - // @ts-ignore - this.projectService.host[onReadSvelteFile] = []; + this.logger.log('patching projectService'); - const readFile = this.projectService.host.readFile; - this.projectService.host.readFile = (path: string, encoding?: string | undefined) => { - if (!this.configManager.getConfig().enable) { - return readFile(path, encoding); - } + // @ts-expect-error Snapshots are stored on the projectService, so they are shared across all instances + this.snapshots = this.projectService[snapshots] = new Map(); - // The following (very hacky) first two checks make sure that the ambient module definitions - // that tell TS "every import ending with .svelte is a valid module" are removed. - // They exist in svelte2tsx and svelte to make sure that people don't - // get errors in their TS files when importing Svelte files and not using our TS plugin. - // If someone wants to get back the behavior they can add an ambient module definition - // on their own. - const normalizedPath = path.replace(/\\/g, '/'); - if (normalizedPath.endsWith('node_modules/svelte/types/runtime/ambient.d.ts')) { - return ''; - } else if (normalizedPath.endsWith('svelte2tsx/svelte-jsx.d.ts')) { - // Remove the dom lib reference to not load these ambient types in case - // the user has a tsconfig.json with different lib settings like in - // https://github.com/sveltejs/language-tools/issues/1733 - const originalText = readFile(path) || ''; - const toReplace = '/// '; - return originalText.replace(toReplace, ' '.repeat(toReplace.length)); - } else if (normalizedPath.endsWith('svelte2tsx/svelte-shims.d.ts')) { - let originalText = readFile(path) || ''; - if (!originalText.includes('// -- start svelte-ls-remove --')) { - return originalText; // uses an older version of svelte2tsx or is already patched - } - const startIdx = originalText.indexOf('// -- start svelte-ls-remove --'); - const endIdx = originalText.indexOf('// -- end svelte-ls-remove --'); - originalText = - originalText.substring(0, startIdx) + - ' '.repeat(endIdx - startIdx) + - originalText.substring(endIdx); - return originalText; - } else if (isSvelteFilePath(path)) { - this.logger.debug('Read Svelte file:', path); - const svelteCode = readFile(path) || ''; - const isTsFile = true; // TODO check file contents? TS might be okay with importing ts into js. - let code: string; - let mapper: SourceMapper; - - try { - const result = svelte2tsx(svelteCode, { - filename: path.split('/').pop(), - isTsFile, - mode: 'ts', - typingsNamespace: this.svelteOptions.namespace, - // Don't search for compiler from current path - could be a different one from which we have loaded the svelte2tsx globals - parse: this.svelteCompiler?.parse, - version: this.svelteCompiler?.VERSION - }); - code = result.code; - mapper = new SourceMapper(result.map.mappings); - this.logger.log('Successfully read Svelte file contents of', path); - } catch (e) { - this.logger.log('Error loading Svelte file:', path, ' Using fallback.'); - this.logger.debug('Error:', e); - // Return something either way, else "X is not a module" errors will appear - // in the TS files that use this file. - code = 'export default class extends Svelte2TsxComponent {}'; - mapper = new SourceMapper(''); - } - - // @ts-ignore - this.projectService.host[onReadSvelteFile].forEach((listener) => - listener(path, svelteCode, isTsFile, mapper) - ); + const readFile = this.projectService.host.readFile; + this.projectService.host.readFile = (path: string, encoding?: string | undefined) => { + if (!this.configManager.getConfig().enable) { + return readFile(path, encoding); + } - return code; - } else { - return readFile(path, encoding); + // The following (very hacky) first two checks make sure that the ambient module definitions + // that tell TS "every import ending with .svelte is a valid module" are removed. + // They exist in svelte2tsx and svelte to make sure that people don't + // get errors in their TS files when importing Svelte files and not using our TS plugin. + // If someone wants to get back the behavior they can add an ambient module definition + // on their own. + const normalizedPath = path.replace(/\\/g, '/'); + if (normalizedPath.endsWith('node_modules/svelte/types/runtime/ambient.d.ts')) { + return ''; + } else if (normalizedPath.endsWith('svelte2tsx/svelte-jsx.d.ts')) { + // Remove the dom lib reference to not load these ambient types in case + // the user has a tsconfig.json with different lib settings like in + // https://github.com/sveltejs/language-tools/issues/1733 + const originalText = readFile(path) || ''; + const toReplace = '/// '; + return originalText.replace(toReplace, ' '.repeat(toReplace.length)); + } else if (normalizedPath.endsWith('svelte2tsx/svelte-shims.d.ts')) { + let originalText = readFile(path) || ''; + if (!originalText.includes('// -- start svelte-ls-remove --')) { + return originalText; // uses an older version of svelte2tsx or is already patched + } + const startIdx = originalText.indexOf('// -- start svelte-ls-remove --'); + const endIdx = originalText.indexOf('// -- end svelte-ls-remove --'); + originalText = + originalText.substring(0, startIdx) + + ' '.repeat(endIdx - startIdx) + + originalText.substring(endIdx); + return originalText; + } else if (isSvelteFilePath(path)) { + this.logger.debug('Read Svelte file:', path); + const svelteCode = readFile(path) || ''; + const isTsFile = true; // TODO check file contents? TS might be okay with importing ts into js. + let code: string; + let mapper: SourceMapper; + + try { + const result = svelte2tsx(svelteCode, { + filename: path.split('/').pop(), + isTsFile, + mode: 'ts', + typingsNamespace: this.svelteOptions.namespace, + // Don't search for compiler from current path - could be a different one from which we have loaded the svelte2tsx globals + parse: this.svelteCompiler?.parse, + version: this.svelteCompiler?.VERSION + }); + code = result.code; + mapper = new SourceMapper(result.map.mappings); + this.logger.log('Successfully read Svelte file contents of', path); + } catch (e) { + this.logger.log('Error loading Svelte file:', path, ' Using fallback.'); + this.logger.debug('Error:', e); + // Return something either way, else "X is not a module" errors will appear + // in the TS files that use this file. + code = 'export default class extends Svelte2TsxComponent {}'; + mapper = new SourceMapper(''); } - }; - } - // @ts-ignore - this.projectService.host[onReadSvelteFile].push( - (path: string, svelteCode: string, isTsFile: boolean, mapper: SourceMapper) => { const canonicalFilePath = this.projectService.toCanonicalFileName(path); const existingSnapshot = this.snapshots.get(canonicalFilePath); if (existingSnapshot) { @@ -424,9 +412,13 @@ export class SvelteSnapshotManager { ) ); } + + return code; + } else { + return readFile(path, encoding); } - ); + }; } } -const onReadSvelteFile = Symbol('sveltePluginPatchSymbol'); +const snapshots = Symbol('sveltePluginPatchSymbol'); From 02db54de1f2fc44d958d67113a9d0fb41a8f6fe7 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:00:34 +0100 Subject: [PATCH 6/6] fix: support `each` without `as` (#2615) --- .../syntaxes/svelte.tmLanguage.src.yaml | 4 +- .../grammar/samples/each-block/input.svelte | 16 +++++ .../samples/each-block/input.svelte.snap | 66 +++++++++++++++++++ packages/svelte-vscode/test/grammar/test.js | 4 +- .../src/htmlxtojsx_v2/nodes/EachBlock.ts | 22 +++++-- .../each-block-without-as.v5/expectedv2.js | 5 ++ .../each-block-without-as.v5/input.svelte | 7 ++ 7 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/expectedv2.js create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/input.svelte diff --git a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml index 01a68bdeb..786d70adc 100644 --- a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml +++ b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml @@ -260,11 +260,11 @@ repository: patterns: # Start expression. - begin: \G\s*?(?=\S) - end: (?=(?:^\s*|\s+)(as)) + end: (?=(?:(?:^\s*|\s+)(as))|\s*(}|,)) contentName: meta.embedded.expression.svelte source.ts patterns: [ include: source.ts ] # 'as' token and onwards. - - begin: (as) + - begin: (as)|(?=}|,) beginCaptures: { 1: { name: keyword.control.as.svelte } } end: (?=}) patterns: diff --git a/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte b/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte index 7cdc3fb4c..3f1528478 100644 --- a/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte +++ b/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte @@ -7,3 +7,19 @@ {#each showGroups as [key, items] (key)} {/each} + +{#each v} + this should be seen as text +{/each} + +{#each v } + this should be seen as text +{/each} + +{#each v, i} + this should be seen as text +{/each} + +{#each v , i} + this should be seen as text +{/each} diff --git a/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte.snap b/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte.snap index 0888c820a..fb9896c3b 100644 --- a/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte.snap +++ b/packages/svelte-vscode/test/grammar/samples/each-block/input.svelte.snap @@ -61,4 +61,70 @@ # ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.keyword.svelte # ^^^^ source.svelte meta.special.each.svelte meta.special.end.svelte keyword.control.svelte # ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.end.svelte +> +>{#each v} +#^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.start.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte meta.embedded.expression.svelte source.ts +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.end.svelte +> this should be seen as text +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.svelte text.svelte +>{/each} +#^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.end.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.end.svelte +> +>{#each v } +#^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.start.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte meta.embedded.expression.svelte source.ts +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.end.svelte +> this should be seen as text +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.svelte text.svelte +>{/each} +#^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.end.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.end.svelte +> +>{#each v, i} +#^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.start.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte meta.embedded.expression.svelte source.ts +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.separator.svelte +# ^^ source.svelte meta.special.each.svelte meta.special.start.svelte meta.embedded.expression.svelte source.ts +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.end.svelte +> this should be seen as text +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.svelte text.svelte +>{/each} +#^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.end.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.end.svelte +> +>{#each v , i} +#^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.start.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte meta.embedded.expression.svelte source.ts +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.separator.svelte +# ^^ source.svelte meta.special.each.svelte meta.special.start.svelte meta.embedded.expression.svelte source.ts +# ^ source.svelte meta.special.each.svelte meta.special.start.svelte punctuation.definition.block.end.svelte +> this should be seen as text +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.svelte text.svelte +>{/each} +#^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.begin.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.keyword.svelte +# ^^^^ source.svelte meta.special.each.svelte meta.special.end.svelte keyword.control.svelte +# ^ source.svelte meta.special.each.svelte meta.special.end.svelte punctuation.definition.block.end.svelte > \ No newline at end of file diff --git a/packages/svelte-vscode/test/grammar/test.js b/packages/svelte-vscode/test/grammar/test.js index 8a24b9b12..4dc3e8ba5 100644 --- a/packages/svelte-vscode/test/grammar/test.js +++ b/packages/svelte-vscode/test/grammar/test.js @@ -48,7 +48,9 @@ async function snapShotTest() { ]; const code = await promisifySpawn(process.platform === 'win32' ? 'npx.cmd' : 'npx', args, { - stdio: 'inherit' + stdio: 'inherit', + // https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2#command-injection-via-args-parameter-of-child_processspawn-without-shell-option-enabled-on-windows-cve-2024-27980---high + shell: true }); if (code > 0) { diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts index 6ec39fadf..51dcb950e 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts @@ -21,36 +21,44 @@ import { getEnd, transform, TransformationArray } from '../utils/node-utils'; * `ensureArray` will error that there are more args than expected */ export function handleEach(str: MagicString, eachBlock: BaseNode): void { - const startEnd = str.original.indexOf('}', eachBlock.key?.end || eachBlock.context.end) + 1; + const startEnd = + str.original.indexOf( + '}', + eachBlock.key?.end || eachBlock.context?.end || eachBlock.expression.end + ) + 1; let transforms: TransformationArray; // {#each true, [1,2]} is valid but for (const x of true, [1,2]) is not if not wrapped with braces const containsComma = str.original .substring(eachBlock.expression.start, eachBlock.expression.end) .includes(','); const expressionEnd = getEnd(eachBlock.expression); - const contextEnd = getEnd(eachBlock.context); + const contextEnd = eachBlock.context && getEnd(eachBlock.context); const arrayAndItemVarTheSame = + !!eachBlock.context && str.original.substring(eachBlock.expression.start, expressionEnd) === - str.original.substring(eachBlock.context.start, contextEnd); + str.original.substring(eachBlock.context.start, contextEnd); if (arrayAndItemVarTheSame) { transforms = [ `{ const $$_each = __sveltets_2_ensureArray(${containsComma ? '(' : ''}`, [eachBlock.expression.start, eachBlock.expression.end], `${containsComma ? ')' : ''}); for(let `, - [eachBlock.context.start, contextEnd], + [eachBlock.context!.start, contextEnd!], ' of $$_each){' ]; } else { transforms = [ 'for(let ', - [eachBlock.context.start, contextEnd], + eachBlock.context ? [eachBlock.context.start, contextEnd] : '$$each_item', ` of __sveltets_2_ensureArray(${containsComma ? '(' : ''}`, [eachBlock.expression.start, eachBlock.expression.end], - `${containsComma ? ')' : ''})){` + `${containsComma ? ')' : ''})){${eachBlock.context ? '' : '$$each_item;'}` ]; } if (eachBlock.index) { - const indexStart = str.original.indexOf(eachBlock.index, eachBlock.context.end); + const indexStart = str.original.indexOf( + eachBlock.index, + eachBlock.context?.end || eachBlock.expression.end + ); const indexEnd = indexStart + eachBlock.index.length; transforms.push('let ', [indexStart, indexEnd], ' = 1;'); } diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/expectedv2.js new file mode 100644 index 000000000..22df537b6 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/expectedv2.js @@ -0,0 +1,5 @@ + for(let $$each_item of __sveltets_2_ensureArray({ length: 5 })){$$each_item; } + + for(let $$each_item of __sveltets_2_ensureArray({ length: 5 })){$$each_item;let index = 1; + index; +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/input.svelte new file mode 100644 index 000000000..02a113e22 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/each-block-without-as.v5/input.svelte @@ -0,0 +1,7 @@ +{#each { length: 5 }} + hi +{/each} + +{#each { length: 5 }, index} + {index} +{/each} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy