From d6a20316fea8d12ac8d71c1f0b36ae35a5a89938 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:37:55 +0200 Subject: [PATCH 01/25] breaking: svelte-check v4 (#2453) - breaking: make TypeScript a peer dependency - breaking: require node 18 or later - breaking: require Svelte 4 or later (devDependencies pinned to Svelte 3 because other packages in this repo still use it. Theoretically we still support Svelte 3 with svelte-check v4 but this gives us the opportunity to adjust that later without a major) - chore: switch from fast-glob to fdir (#2433) closes #2397 fixes #2364 --------- Co-authored-by: Simon Holthausen Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/language-server/package.json | 2 +- .../src/lib/documents/configLoader.ts | 27 ++- .../test/lib/documents/configLoader.test.ts | 44 ++-- packages/svelte-check/package.json | 16 +- packages/svelte-check/rollup.config.mjs | 1 - packages/svelte-check/src/index.ts | 46 +++- packages/svelte-check/src/options.ts | 20 +- pnpm-lock.yaml | 199 ++---------------- 8 files changed, 138 insertions(+), 217 deletions(-) diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 680e21419..f79ba9ede 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -50,7 +50,7 @@ "@vscode/emmet-helper": "2.8.4", "chokidar": "^3.4.1", "estree-walker": "^2.0.1", - "fast-glob": "^3.2.7", + "fdir": "^6.2.0", "lodash": "^4.17.21", "prettier": "~3.2.5", "prettier-plugin-svelte": "^3.2.2", diff --git a/packages/language-server/src/lib/documents/configLoader.ts b/packages/language-server/src/lib/documents/configLoader.ts index 4d7e583c6..49413a6f8 100644 --- a/packages/language-server/src/lib/documents/configLoader.ts +++ b/packages/language-server/src/lib/documents/configLoader.ts @@ -4,7 +4,7 @@ import { CompileOptions } from 'svelte/types/compiler/interfaces'; // @ts-ignore import { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; import { importSveltePreprocess } from '../../importPackage'; -import _glob from 'fast-glob'; +import { fdir } from 'fdir'; import _path from 'path'; import _fs from 'fs'; import { pathToFileURL, URL } from 'url'; @@ -47,6 +47,8 @@ const _dynamicImport = new Function('modulePath', 'return import(modulePath)') a modulePath: URL ) => Promise; +const configRegex = /\/svelte\.config\.(js|cjs|mjs)$/; + /** * Loads svelte.config.{js,cjs,mjs} files. Provides both a synchronous and asynchronous * interface to get a config file because snapshots need access to it synchronously. @@ -61,7 +63,7 @@ export class ConfigLoader { private disabled = false; constructor( - private globSync: typeof _glob.sync, + private globSync: typeof fdir, private fs: Pick, private path: Pick, private dynamicImport: typeof _dynamicImport @@ -84,12 +86,19 @@ export class ConfigLoader { Logger.log('Trying to load configs for', directory); try { - const pathResults = this.globSync('**/svelte.config.{js,cjs,mjs}', { - cwd: directory, - // the second pattern is necessary because else fast-glob treats .tmp/../node_modules/.. as a valid match for some reason - ignore: ['**/node_modules/**', '**/.*/**'], - onlyFiles: true - }); + const pathResults = new this.globSync({}) + .withPathSeparator('/') + .exclude((_, path) => { + // no / at the start, path could start with node_modules + return path.includes('node_modules/') || path.includes('/.') || path[0] === '.'; + }) + .filter((path, isDir) => { + return !isDir && configRegex.test(path); + }) + .withRelativePaths() + .crawl(directory) + .sync(); + const someConfigIsImmediateFileInDirectory = pathResults.length > 0 && pathResults.some((res) => !this.path.dirname(res)); if (!someConfigIsImmediateFileInDirectory) { @@ -296,4 +305,4 @@ export class ConfigLoader { } } -export const configLoader = new ConfigLoader(_glob.sync, _fs, _path, _dynamicImport); +export const configLoader = new ConfigLoader(fdir, _fs, _path, _dynamicImport); diff --git a/packages/language-server/test/lib/documents/configLoader.test.ts b/packages/language-server/test/lib/documents/configLoader.test.ts index 1825a2ec9..dbdfb677a 100644 --- a/packages/language-server/test/lib/documents/configLoader.test.ts +++ b/packages/language-server/test/lib/documents/configLoader.test.ts @@ -19,6 +19,29 @@ describe('ConfigLoader', () => { return path.join(...filePath.split('/')); } + function mockFdir(results: string[] | (() => string[])): any { + return class { + withPathSeparator() { + return this; + } + exclude() { + return this; + } + filter() { + return this; + } + withRelativePaths() { + return this; + } + crawl() { + return this; + } + sync() { + return typeof results === 'function' ? results() : results; + } + }; + } + async function assertFindsConfig( configLoader: ConfigLoader, filePath: string, @@ -32,7 +55,7 @@ describe('ConfigLoader', () => { it('should load all config files below and the one inside/above given directory', async () => { const configLoader = new ConfigLoader( - (() => ['svelte.config.js', 'below/svelte.config.js']) as any, + mockFdir(['svelte.config.js', 'below/svelte.config.js']), { existsSync: () => true }, path, (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) @@ -63,7 +86,7 @@ describe('ConfigLoader', () => { it('finds first above if none found inside/below directory', async () => { const configLoader = new ConfigLoader( - () => [], + mockFdir([]), { existsSync: (p) => typeof p === 'string' && p.endsWith(path.join('some', 'svelte.config.js')) @@ -78,7 +101,7 @@ describe('ConfigLoader', () => { it('adds fallback if no config found', async () => { const configLoader = new ConfigLoader( - () => [], + mockFdir([]), { existsSync: () => false }, path, (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) @@ -98,14 +121,14 @@ describe('ConfigLoader', () => { let firstGlobCall = true; let nrImportCalls = 0; const configLoader = new ConfigLoader( - (() => { + mockFdir(() => { if (firstGlobCall) { firstGlobCall = false; return ['svelte.config.js']; } else { return []; } - }) as any, + }), { existsSync: (p) => typeof p === 'string' && @@ -139,11 +162,8 @@ describe('ConfigLoader', () => { }); it('can deal with missing config', () => { - const configLoader = new ConfigLoader( - () => [], - { existsSync: () => false }, - path, - () => Promise.resolve('unimportant') + const configLoader = new ConfigLoader(mockFdir([]), { existsSync: () => false }, path, () => + Promise.resolve('unimportant') ); assert.deepStrictEqual( configLoader.getConfig(normalizePath('/some/file.svelte')), @@ -153,7 +173,7 @@ describe('ConfigLoader', () => { it('should await config', async () => { const configLoader = new ConfigLoader( - () => [], + mockFdir([]), { existsSync: () => true }, path, (module: URL) => Promise.resolve({ default: { preprocess: module.toString() } }) @@ -167,7 +187,7 @@ describe('ConfigLoader', () => { it('should not load config when disabled', async () => { const moduleLoader = spy(); const configLoader = new ConfigLoader( - () => [], + mockFdir([]), { existsSync: () => true }, path, moduleLoader diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 9d8309c46..361c2c33f 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -1,7 +1,7 @@ { "name": "svelte-check", "description": "Svelte Code Checker Terminal Interface", - "version": "3.0.0", + "version": "4.0.0", "main": "./dist/src/index.js", "bin": "./bin/svelte-check", "author": "The Svelte Community", @@ -22,16 +22,19 @@ "url": "https://github.com/sveltejs/language-tools/issues" }, "homepage": "https://github.com/sveltejs/language-tools#readme", + "engines": { + "node": ">= 18.0.0" + }, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "chokidar": "^3.4.1", + "fdir": "^6.2.0", "picocolors": "^1.0.0", - "sade": "^1.7.4", - "svelte-preprocess": "^5.1.3", - "typescript": "^5.0.3" + "sade": "^1.7.4" }, "peerDependencies": { - "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" }, "scripts": { "build": "rollup -c && node ./dist/src/index.js --workspace ./test --tsconfig ./tsconfig.json", @@ -46,11 +49,12 @@ "@rollup/plugin-typescript": "^10.0.0", "@types/sade": "^1.7.2", "builtin-modules": "^3.3.0", - "fast-glob": "^3.2.7", "rollup": "3.7.5", "rollup-plugin-cleanup": "^3.2.0", "rollup-plugin-copy": "^3.4.0", + "svelte": "^3.57.0", "svelte-language-server": "workspace:*", + "typescript": "^5.5.2", "vscode-languageserver": "8.0.2", "vscode-languageserver-protocol": "3.17.2", "vscode-languageserver-types": "3.17.2", diff --git a/packages/svelte-check/rollup.config.mjs b/packages/svelte-check/rollup.config.mjs index 2488f2bae..e5346edee 100644 --- a/packages/svelte-check/rollup.config.mjs +++ b/packages/svelte-check/rollup.config.mjs @@ -65,7 +65,6 @@ export default [ 'sade', 'svelte', 'svelte/compiler', - 'svelte-preprocess', '@jridgewell/trace-mapping' // import-fresh removed some time ago, no dependency uses it anymore. // if it creeps back in check if the dependency uses a version that diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index 6e64414ed..866d23bf3 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -4,7 +4,7 @@ import { watch } from 'chokidar'; import * as fs from 'fs'; -import glob from 'fast-glob'; +import { fdir } from 'fdir'; import * as path from 'path'; import { SvelteCheck, SvelteCheckOptions } from 'svelte-language-server'; import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol'; @@ -30,11 +30,47 @@ async function openAllDocuments( filePathsToIgnore: string[], svelteCheck: SvelteCheck ) { - const files = await glob('**/*.svelte', { - cwd: workspaceUri.fsPath, - ignore: ['node_modules/**'].concat(filePathsToIgnore.map((ignore) => `${ignore}/**`)) + const offset = workspaceUri.fsPath.length + 1; + // We support a very limited subset of glob patterns: You can only have ** at the end or the start + const ignored: Array<(path: string) => boolean> = filePathsToIgnore.map((i) => { + if (i.endsWith('**')) i = i.slice(0, -2); + + if (i.startsWith('**')) { + i = i.slice(2); + + if (i.includes('*')) + throw new Error( + 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' + ); + + return (path) => path.includes(i); + } + + if (i.includes('*')) + throw new Error( + 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' + ); + + return (path) => path.startsWith(i); }); - const absFilePaths = files.map((f) => path.resolve(workspaceUri.fsPath, f)); + const isIgnored = (path: string) => { + path = path.slice(offset); + for (const i of ignored) { + if (i(path)) { + return true; + } + } + return false; + }; + const absFilePaths = await new fdir() + .filter((path) => path.endsWith('.svelte') && !isIgnored(path)) + .exclude((_, path) => { + return path.includes('/node_modules/') || path.includes('/.'); + }) + .withPathSeparator('/') + .withFullPaths() + .crawl(workspaceUri.fsPath) + .withPromise(); for (const absFilePath of absFilePaths) { const text = fs.readFileSync(absFilePath, 'utf-8'); diff --git a/packages/svelte-check/src/options.ts b/packages/svelte-check/src/options.ts index bf270e3c4..b43dd7695 100644 --- a/packages/svelte-check/src/options.ts +++ b/packages/svelte-check/src/options.ts @@ -67,13 +67,19 @@ export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) { ) .action((opts) => { const workspaceUri = getWorkspaceUri(opts); + const tsconfig = getTsconfig(opts, workspaceUri.fsPath); + + if (opts.ignore && tsconfig) { + throwError('`--ignore` only has an effect when using `--no-tsconfig`'); + } + cb({ workspaceUri, outputFormat: getOutputFormat(opts), watch: !!opts.watch, preserveWatchOutput: !!opts.preserveWatchOutput, - tsconfig: getTsconfig(opts, workspaceUri.fsPath), - filePathsToIgnore: getFilepathsToIgnore(opts), + tsconfig, + filePathsToIgnore: opts.ignore?.split(',') || [], failOnWarnings: !!opts['fail-on-warnings'], compilerWarnings: getCompilerWarnings(opts), diagnosticSources: getDiagnosticSources(opts), @@ -141,11 +147,15 @@ function getTsconfig(myArgs: Record, workspacePath: string) { tsconfig = path.join(workspacePath, tsconfig); } if (tsconfig && !fs.existsSync(tsconfig)) { - throw new Error('Could not find tsconfig/jsconfig file at ' + myArgs.tsconfig); + throwError('Could not find tsconfig/jsconfig file at ' + myArgs.tsconfig); } return tsconfig; } +function throwError(msg: string) { + throw new Error('Invalid svelte-check CLI args: ' + msg); +} + function getCompilerWarnings(opts: Record) { return stringToObj(opts['compiler-warnings']); @@ -180,10 +190,6 @@ function getDiagnosticSources(opts: Record): DiagnosticSource[] { : diagnosticSources; } -function getFilepathsToIgnore(opts: Record): string[] { - return opts.ignore?.split(',') || []; -} - const thresholds = ['warning', 'error'] as const; type Threshold = (typeof thresholds)[number]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1e587871..2ebe4e95d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,9 +36,9 @@ importers: estree-walker: specifier: ^2.0.1 version: 2.0.2 - fast-glob: - specifier: ^3.2.7 - version: 3.2.12 + fdir: + specifier: ^6.2.0 + version: 6.2.0 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -118,21 +118,15 @@ importers: chokidar: specifier: ^3.4.1 version: 3.5.3 + fdir: + specifier: ^6.2.0 + version: 6.2.0 picocolors: specifier: ^1.0.0 version: 1.0.0 sade: specifier: ^1.7.4 version: 1.8.1 - svelte: - specifier: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 - version: 3.57.0 - svelte-preprocess: - specifier: ^5.1.3 - version: 5.1.3(svelte@3.57.0)(typescript@5.4.5) - typescript: - specifier: ^5.0.3 - version: 5.4.5 devDependencies: '@rollup/plugin-commonjs': specifier: ^24.0.0 @@ -148,16 +142,13 @@ importers: version: 5.0.2(rollup@3.7.5) '@rollup/plugin-typescript': specifier: ^10.0.0 - version: 10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.4.5) + version: 10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.5.2) '@types/sade': specifier: ^1.7.2 version: 1.7.4 builtin-modules: specifier: ^3.3.0 version: 3.3.0 - fast-glob: - specifier: ^3.2.7 - version: 3.2.12 rollup: specifier: 3.7.5 version: 3.7.5 @@ -167,9 +158,15 @@ importers: rollup-plugin-copy: specifier: ^3.4.0 version: 3.4.0 + svelte: + specifier: ^3.57.0 + version: 3.57.0 svelte-language-server: specifier: workspace:* version: link:../language-server + typescript: + specifier: ^5.5.2 + version: 5.5.2 vscode-languageserver: specifier: 8.0.2 version: 8.0.2 @@ -489,9 +486,6 @@ packages: '@types/prettier@2.7.2': resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} - '@types/pug@2.0.6': - resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} - '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -589,9 +583,6 @@ packages: browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -683,10 +674,6 @@ packages: resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} engines: {node: '>=8'} - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -709,9 +696,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - es6-promise@3.3.1: - resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} - escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -742,6 +726,14 @@ packages: fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + fdir@6.2.0: + resolution: {integrity: sha512-9XaWcDl0riOX5j2kYfy0kKdg7skw3IY6kA4LFT8Tk2yF9UdrADUy8D6AJuBLtf7ISm/MksumwAHE3WVbMRyCLw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -946,10 +938,6 @@ packages: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} - magic-string@0.30.7: - resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} - engines: {node: '>=12'} - make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -961,10 +949,6 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -976,13 +960,6 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - mocha@9.2.2: resolution: {integrity: sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==} engines: {node: '>= 12.0.0'} @@ -1100,10 +1077,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - hasBin: true - rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true @@ -1140,9 +1113,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - sander@0.5.1: - resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} - semver@7.5.1: resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} engines: {node: '>=10'} @@ -1170,10 +1140,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - sorcery@0.11.0: - resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} - hasBin: true - source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -1196,10 +1162,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1220,43 +1182,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-preprocess@5.1.3: - resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==} - engines: {node: '>= 16.0.0', pnpm: ^8.0.0} - peerDependencies: - '@babel/core': ^7.10.2 - coffeescript: ^2.5.1 - less: ^3.11.3 || ^4.0.0 - postcss: ^7 || ^8 - postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 - pug: ^3.0.0 - sass: ^1.26.8 - stylus: ^0.55.0 - sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 - svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 - typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' - peerDependenciesMeta: - '@babel/core': - optional: true - coffeescript: - optional: true - less: - optional: true - postcss: - optional: true - postcss-load-config: - optional: true - pug: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - typescript: - optional: true - svelte@3.57.0: resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==} engines: {node: '>= 8'} @@ -1292,11 +1217,6 @@ packages: typescript-auto-import-cache@0.3.3: resolution: {integrity: sha512-ojEC7+Ci1ij9eE6hp8Jl9VUNnsEKzztktP5gtYNRMrTmfXVwA1PITYYAkpxCvvupdSYa/Re51B6KMcv1CTZEUA==} - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.5.2: resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} engines: {node: '>=14.17'} @@ -1497,15 +1417,6 @@ snapshots: optionalDependencies: rollup: 3.7.5 - '@rollup/plugin-typescript@10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.4.5)': - dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.7.5) - resolve: 1.22.2 - typescript: 5.4.5 - optionalDependencies: - rollup: 3.7.5 - tslib: 2.5.2 - '@rollup/plugin-typescript@10.0.1(rollup@3.7.5)(tslib@2.5.2)(typescript@5.5.2)': dependencies: '@rollup/pluginutils': 5.0.2(rollup@3.7.5) @@ -1584,8 +1495,6 @@ snapshots: '@types/prettier@2.7.2': {} - '@types/pug@2.0.6': {} - '@types/resolve@1.20.2': {} '@types/sade@1.7.4': @@ -1676,8 +1585,6 @@ snapshots: browser-stdout@1.3.1: {} - buffer-crc32@0.2.13: {} - buffer-from@1.1.2: {} builtin-modules@3.3.0: {} @@ -1770,8 +1677,6 @@ snapshots: rimraf: 3.0.2 slash: 3.0.0 - detect-indent@6.1.0: {} - diff@4.0.2: {} diff@5.0.0: {} @@ -1789,8 +1694,6 @@ snapshots: emoji-regex@8.0.0: {} - es6-promise@3.3.1: {} - escalade@3.1.1: {} escape-string-regexp@1.0.5: {} @@ -1815,6 +1718,8 @@ snapshots: dependencies: reusify: 1.0.4 + fdir@6.2.0: {} + fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 @@ -2016,10 +1921,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - magic-string@0.30.7: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - make-error@1.3.6: {} merge2@1.4.1: {} @@ -2029,8 +1930,6 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 - min-indent@1.0.1: {} - minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -2043,12 +1942,6 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} - - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - mocha@9.2.2: dependencies: '@ungap/promise-all-settled': 1.1.2 @@ -2172,10 +2065,6 @@ snapshots: reusify@1.0.4: {} - rimraf@2.7.1: - dependencies: - glob: 7.2.3 - rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -2216,13 +2105,6 @@ snapshots: safe-buffer@5.2.1: {} - sander@0.5.1: - dependencies: - es6-promise: 3.3.1 - graceful-fs: 4.2.11 - mkdirp: 0.5.6 - rimraf: 2.7.1 - semver@7.5.1: dependencies: lru-cache: 6.0.0 @@ -2250,13 +2132,6 @@ snapshots: slash@3.0.0: {} - sorcery@0.11.0: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - buffer-crc32: 0.2.13 - minimist: 1.2.8 - sander: 0.5.1 - source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -2278,10 +2153,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - strip-json-comments@3.1.1: {} supports-color@5.5.0: @@ -2298,28 +2169,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-preprocess@5.1.3(svelte@3.57.0)(typescript@5.4.5): - dependencies: - '@types/pug': 2.0.6 - detect-indent: 6.1.0 - magic-string: 0.30.7 - sorcery: 0.11.0 - strip-indent: 3.0.0 - svelte: 3.57.0 - optionalDependencies: - typescript: 5.4.5 - - svelte-preprocess@5.1.3(svelte@3.57.0)(typescript@5.5.2): - dependencies: - '@types/pug': 2.0.6 - detect-indent: 6.1.0 - magic-string: 0.30.7 - sorcery: 0.11.0 - strip-indent: 3.0.0 - svelte: 3.57.0 - optionalDependencies: - typescript: 5.5.2 - svelte@3.57.0: {} tiny-glob@0.2.9: @@ -2357,8 +2206,6 @@ snapshots: dependencies: semver: 7.5.1 - typescript@5.4.5: {} - typescript@5.5.2: {} unist-util-stringify-position@3.0.3: From 8f5904aa6012c58729195a41e7d4cfb70db152a5 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:45:13 +0800 Subject: [PATCH 02/25] breaking: project reference support (#2463) - closes #2148 - fixes #1234 - due to the change of how files are resolved (breaking change) - fixes #1976 - less options are now forced (breaking change) - fixes #2154 (breaking change) --- .../plugins/typescript/LSAndTSDocResolver.ts | 73 +- .../src/plugins/typescript/SnapshotManager.ts | 5 + .../plugins/typescript/TypeScriptPlugin.ts | 9 +- .../features/CallHierarchyProvider.ts | 11 +- .../features/CodeActionsProvider.ts | 22 +- .../typescript/features/CompletionProvider.ts | 14 +- .../FindComponentReferencesProvider.ts | 7 +- .../features/FindFileReferencesProvider.ts | 11 +- .../features/FindReferencesProvider.ts | 8 +- .../features/ImplementationProvider.ts | 4 +- .../typescript/features/InlayHintProvider.ts | 4 +- .../typescript/features/RenameProvider.ts | 6 +- .../features/TypeDefinitionProvider.ts | 4 +- .../features/UpdateImportsProvider.ts | 76 ++- .../src/plugins/typescript/features/utils.ts | 22 +- .../src/plugins/typescript/module-loader.ts | 70 +- .../src/plugins/typescript/service.ts | 632 +++++++++++++----- .../src/plugins/typescript/serviceCache.ts | 6 +- .../src/plugins/typescript/svelte-sys.ts | 8 +- packages/language-server/src/server.ts | 5 +- packages/language-server/src/svelte-check.ts | 48 +- .../features/DiagnosticsProvider.test.ts | 2 +- .../features/FindReferencesProvider.test.ts | 6 +- .../typescript/features/HoverProvider.test.ts | 6 +- .../features/ImplemenationProvider.test.ts | 2 +- .../features/TypeDefinitionProvider.test.ts | 2 +- .../features/UpdateImportsProvider.test.ts | 5 +- .../project-reference/nested/expectedv2.json | 10 + .../project-reference/nested/imported.ts | 5 + .../project-reference/nested/input.svelte | 8 + .../nested/tsconfig_sub2.json | 7 + .../nested/tsconfig_sub3.json | 10 + .../project-reference/paths/expectedv2.json | 10 + .../project-reference/paths/imported.ts | 5 + .../project-reference/paths/input.svelte | 8 + .../project-reference/paths/tsconfig_sub.json | 11 + .../fixtures/project-reference/tsconfig.json | 11 + .../diagnostics/fixtures/tsconfig.json | 4 +- .../inlayHints/fixtures/tsconfig.json | 12 + .../plugins/typescript/module-loader.test.ts | 144 +--- .../test/plugins/typescript/service.test.ts | 277 +++++++- .../test/plugins/typescript/test-utils.ts | 56 +- .../testfiles/diagnostics/tsconfig.json | 2 + .../typescript/testfiles/tsconfig.json | 2 + packages/svelte-check/src/writers.ts | 21 +- packages/svelte-check/test/tsconfig.json | 2 +- 46 files changed, 1181 insertions(+), 492 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/imported.ts create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/input.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub3.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/imported.ts create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/input.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/tsconfig_sub.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/tsconfig.json create mode 100644 packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/tsconfig.json diff --git a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts index 3843e8a89..3b0a76c06 100644 --- a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts +++ b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts @@ -1,6 +1,10 @@ import { dirname, join } from 'path'; import ts from 'typescript'; -import { RelativePattern, TextDocumentContentChangeEvent } from 'vscode-languageserver'; +import { + PublishDiagnosticsParams, + RelativePattern, + TextDocumentContentChangeEvent +} from 'vscode-languageserver'; import { Document, DocumentManager } from '../../lib/documents'; import { LSConfigManager } from '../../ls-config'; import { @@ -37,6 +41,7 @@ interface LSAndTSDocResolverOptions { tsconfigPath?: string; onProjectReloaded?: () => void; + reportConfigError?: (diagnostic: PublishDiagnosticsParams) => void; watch?: boolean; tsSystem?: ts.System; watchDirectory?: (patterns: RelativePattern[]) => void; @@ -50,14 +55,10 @@ export class LSAndTSDocResolver { private readonly configManager: LSConfigManager, private readonly options?: LSAndTSDocResolverOptions ) { - const handleDocumentChange = (document: Document) => { - // This refreshes the document in the ts language service - this.getSnapshot(document); - }; docManager.on( 'documentChange', debounceSameArg( - handleDocumentChange, + this.updateSnapshot.bind(this), (newDoc, prevDoc) => newDoc.uri === prevDoc?.uri, 1000 ) @@ -68,7 +69,11 @@ export class LSAndTSDocResolver { // where multiple files and their dependencies // being loaded in a short period of times docManager.on('documentOpen', (document) => { - handleDocumentChange(document); + if (document.openedByClient) { + this.getOrCreateSnapshot(document); + } else { + this.updateSnapshot(document); + } docManager.lockDocument(document.uri); }); @@ -121,7 +126,8 @@ export class LSAndTSDocResolver { watchDirectory: this.options?.watchDirectory ? this.watchDirectory.bind(this) : undefined, - nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern + nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern, + reportConfigError: this.options?.reportConfigError }; } @@ -151,18 +157,20 @@ export class LSAndTSDocResolver { private lsDocumentContext: LanguageServiceDocumentContext; private readonly watchedDirectories: FileSet; - async getLSForPath(path: string) { - return (await this.getTSService(path)).getService(); - } - async getLSAndTSDoc(document: Document): Promise<{ tsDoc: SvelteDocumentSnapshot; lang: ts.LanguageService; userPreferences: ts.UserPreferences; + lsContainer: LanguageServiceContainer; }> { const { tsDoc, lsContainer, userPreferences } = await this.getLSAndTSDocWorker(document); - return { tsDoc, lang: lsContainer.getService(), userPreferences }; + return { + tsDoc, + lang: lsContainer.getService(), + userPreferences, + lsContainer + }; } /** @@ -181,7 +189,7 @@ export class LSAndTSDocResolver { private async getLSAndTSDocWorker(document: Document) { const lsContainer = await this.getTSService(document.getFilePath() || ''); - const tsDoc = await this.getSnapshot(document); + const tsDoc = await this.getOrCreateSnapshot(document); const userPreferences = this.getUserPreferences(tsDoc); return { tsDoc, lsContainer, userPreferences }; @@ -192,13 +200,21 @@ export class LSAndTSDocResolver { * the ts service it primarily belongs into. * The update is mirrored in all other services, too. */ - async getSnapshot(document: Document): Promise; - async getSnapshot(pathOrDoc: string | Document): Promise; - async getSnapshot(pathOrDoc: string | Document) { + async getOrCreateSnapshot(document: Document): Promise; + async getOrCreateSnapshot(pathOrDoc: string | Document): Promise; + async getOrCreateSnapshot(pathOrDoc: string | Document) { const filePath = typeof pathOrDoc === 'string' ? pathOrDoc : pathOrDoc.getFilePath() || ''; const tsService = await this.getTSService(filePath); return tsService.updateSnapshot(pathOrDoc); } + private async updateSnapshot(document: Document) { + const filePath = document.getFilePath(); + if (!filePath) { + return; + } + // ensure no new service is created + await this.updateExistingFile(filePath, (service) => service.updateSnapshot(document)); + } /** * Updates snapshot path in all existing ts services and retrieves snapshot @@ -217,7 +233,7 @@ export class LSAndTSDocResolver { }); } else { // This may not be a file but a directory, still try - await this.getSnapshot(newPath); + await this.getOrCreateSnapshot(newPath); } } @@ -280,19 +296,11 @@ export class LSAndTSDocResolver { }); } - /** - * @internal Public for tests only - */ - async getSnapshotManager(filePath: string): Promise { - return (await this.getTSService(filePath)).snapshotManager; - } - async getTSService(filePath?: string): Promise { if (this.options?.tsconfigPath) { - return getServiceForTsconfig( - this.options?.tsconfigPath, - dirname(this.options.tsconfigPath), - this.lsDocumentContext + return this.getTSServiceByConfigPath( + this.options.tsconfigPath, + dirname(this.options.tsconfigPath) ); } if (!filePath) { @@ -301,6 +309,13 @@ export class LSAndTSDocResolver { return getService(filePath, this.workspaceUris, this.lsDocumentContext); } + async getTSServiceByConfigPath( + tsconfigPath: string, + workspacePath: string + ): Promise { + return getServiceForTsconfig(tsconfigPath, workspacePath, this.lsDocumentContext); + } + private getUserPreferences(tsDoc: DocumentSnapshot): ts.UserPreferences { const configLang = tsDoc.scriptKind === ts.ScriptKind.TS || tsDoc.scriptKind === ts.ScriptKind.TSX diff --git a/packages/language-server/src/plugins/typescript/SnapshotManager.ts b/packages/language-server/src/plugins/typescript/SnapshotManager.ts index 6bf64f0b6..85b152e49 100644 --- a/packages/language-server/src/plugins/typescript/SnapshotManager.ts +++ b/packages/language-server/src/plugins/typescript/SnapshotManager.ts @@ -262,6 +262,11 @@ export class SnapshotManager { return Array.from(this.projectFileToOriginalCasing.values()); } + isProjectFile(fileName: string): boolean { + fileName = normalizePath(fileName); + return this.projectFileToOriginalCasing.has(this.getCanonicalFileName(fileName)); + } + private logStatistics() { const date = new Date(); // Don't use setInterval because that will keep tests running forever diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts index bc97f6175..264fe6665 100644 --- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts @@ -168,7 +168,10 @@ export class TypeScriptPlugin this.completionProvider, configManager ); - this.updateImportsProvider = new UpdateImportsProviderImpl(this.lsAndTsDocResolver); + this.updateImportsProvider = new UpdateImportsProviderImpl( + this.lsAndTsDocResolver, + ts.sys.useCaseSensitiveFileNames + ); this.diagnosticsProvider = new DiagnosticsProviderImpl( this.lsAndTsDocResolver, configManager @@ -383,7 +386,7 @@ export class TypeScriptPlugin } async getDefinitions(document: Document, position: Position): Promise { - const { lang, tsDoc } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); + const { lang, tsDoc, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); const defs = lang.getDefinitionAndBoundSpan( tsDoc.filePath, @@ -394,7 +397,7 @@ export class TypeScriptPlugin return []; } - const snapshots = new SnapshotMap(this.lsAndTsDocResolver); + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshots.set(tsDoc.filePath, tsDoc); const result = await Promise.all( diff --git a/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts b/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts index b58e797a2..3d60b40ac 100644 --- a/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts @@ -41,7 +41,7 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider { position: Position, cancellationToken?: CancellationToken ): Promise { - const { lang, tsDoc } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); + const { lang, tsDoc, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); if (cancellationToken?.isCancellationRequested) { return null; @@ -52,7 +52,7 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider { const itemsArray = Array.isArray(items) ? items : items ? [items] : []; - const snapshots = new SnapshotMap(this.lsAndTsDocResolver); + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshots.set(tsDoc.filePath, tsDoc); const program = lang.getProgram(); @@ -251,8 +251,9 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider { return null; } - const lang = await this.lsAndTsDocResolver.getLSForPath(filePath); - const tsDoc = await this.lsAndTsDocResolver.getSnapshot(filePath); + const lsContainer = await this.lsAndTsDocResolver.getTSService(filePath); + const lang = lsContainer.getService(); + const tsDoc = await this.lsAndTsDocResolver.getOrCreateSnapshot(filePath); if (cancellationToken?.isCancellationRequested) { return null; @@ -260,7 +261,7 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider { const program = lang.getProgram(); - const snapshots = new SnapshotMap(this.lsAndTsDocResolver); + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshots.set(tsDoc.filePath, tsDoc); const isComponentModulePosition = diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 287f21664..59def877c 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -46,7 +46,6 @@ import { import { CompletionsProviderImpl } from './CompletionProvider'; import { findClosestContainingNode, - findContainingNode, FormatCodeBasis, getFormatCodeBasis, getNewScriptStartTag, @@ -56,6 +55,7 @@ import { } from './utils'; import { DiagnosticCode } from './DiagnosticsProvider'; import { createGetCanonicalFileName } from '../../../utils'; +import { LanguageServiceContainer } from '../service'; /** * TODO change this to protocol constant if it's part of the protocol @@ -156,7 +156,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return codeAction; } - const { lang, tsDoc, userPreferences } = + const { lang, tsDoc, userPreferences, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); if (cancellationToken?.isCancellationRequested) { return codeAction; @@ -180,10 +180,11 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { const isImportFix = codeAction.data.fixName === FIX_IMPORT_FIX_NAME; const virtualDocInfo = isImportFix - ? await this.createVirtualDocumentForCombinedImportCodeFix( + ? this.createVirtualDocumentForCombinedImportCodeFix( document, getDiagnostics(), tsDoc, + lsContainer, lang ) : undefined; @@ -218,7 +219,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { await this.lsAndTsDocResolver.deleteSnapshot(virtualDocPath); } - const snapshots = new SnapshotMap(this.lsAndTsDocResolver); + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); const fixActions: ts.CodeFixAction[] = [ { fixName: codeAction.data.fixName, @@ -259,10 +260,11 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { * Do not use this in regular code action * This'll cause TypeScript to rebuild and invalidate caches every time. It'll be slow */ - private async createVirtualDocumentForCombinedImportCodeFix( + private createVirtualDocumentForCombinedImportCodeFix( document: Document, diagnostics: Diagnostic[], tsDoc: DocumentSnapshot, + lsContainer: LanguageServiceContainer, lang: ts.LanguageService ) { const virtualUri = document.uri + '.__virtual__.svelte'; @@ -314,10 +316,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { const virtualDoc = new Document(virtualUri, newText); virtualDoc.openedByClient = true; // let typescript know about the virtual document - // getLSAndTSDoc instead of getSnapshot so that project dirty state is correctly tracked by us - // otherwise, sometime the applied code fix might not be picked up by the language service - // because we think the project is still dirty and doesn't update the project version - await this.lsAndTsDocResolver.getLSAndTSDoc(virtualDoc); + lsContainer.openVirtualDocument(virtualDoc); + lsContainer.getService(); return { virtualDoc, @@ -553,7 +553,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { context: CodeActionContext, cancellationToken: CancellationToken | undefined ) { - const { lang, tsDoc, userPreferences } = await this.getLSAndTSDoc(document); + const { lang, tsDoc, userPreferences, lsContainer } = await this.getLSAndTSDoc(document); if (cancellationToken?.isCancellationRequested) { return []; @@ -613,7 +613,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ); } - const snapshots = new SnapshotMap(this.lsAndTsDocResolver); + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshots.set(tsDoc.filePath, tsDoc); const codeActionsPromises = codeFixes.map(async (fix) => { diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index ffb044938..e7b5bec75 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -49,6 +49,7 @@ import { isPartOfImportStatement } from './utils'; import { isInTag as svelteIsInTag } from '../svelte-ast-utils'; +import { LanguageServiceContainer } from '../service'; export interface CompletionResolveInfo extends Pick, @@ -170,7 +171,7 @@ export class CompletionsProviderImpl implements CompletionsProvider 0 ? [] - : await this.getCustomElementCompletions(lang, document, tsDoc, position); + : this.getCustomElementCompletions(lang, lsContainer, document, tsDoc, position); const formatSettings = await this.configManager.getFormatCodeSettingsForFile( document, @@ -474,12 +475,13 @@ export class CompletionsProviderImpl implements CompletionsProvider { + ): CompletionItem[] | undefined { const offset = document.offsetAt(position); const tag = getNodeIfIsInHTMLStartTag(document.html, offset); @@ -499,9 +501,7 @@ export class CompletionsProviderImpl implements CompletionsProvider ref.definition.kind === ts.ScriptElementKind.alias)) { @@ -124,7 +124,7 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { let storeReferences: ts.ReferencedSymbolEntry[] = []; const storeReference = references.find( (ref) => - ref.fileName === tsDoc.filePath && + normalizePath(ref.fileName) === tsDoc.filePath && isTextSpanInGeneratedCode(tsDoc.getFullText(), ref.textSpan) && is$storeVariableIn$storeDeclaration(tsDoc.getFullText(), ref.textSpan.start) ); diff --git a/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts b/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts index e4a91c1bb..693afaa49 100644 --- a/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts @@ -18,7 +18,7 @@ export class ImplementationProviderImpl implements ImplementationProvider { position: Position, cancellationToken?: CancellationToken ): Promise { - const { tsDoc, lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); + const { tsDoc, lang, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); if (cancellationToken?.isCancellationRequested) { return null; @@ -27,7 +27,7 @@ export class ImplementationProviderImpl implements ImplementationProvider { const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)); const implementations = lang.getImplementationAtPosition(tsDoc.filePath, offset); - const snapshots = new SnapshotMap(this.lsAndTsDocResolver); + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshots.set(tsDoc.filePath, tsDoc); if (!implementations) { diff --git a/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts b/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts index 9ca919900..a11fb0f43 100644 --- a/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts @@ -41,7 +41,7 @@ export class InlayHintProviderImpl implements InlayHintProvider { return null; } - const { tsDoc, lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); + const { tsDoc, lang, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); const inlayHints = lang.provideInlayHints( tsDoc.filePath, @@ -59,7 +59,7 @@ export class InlayHintProviderImpl implements InlayHintProvider { const renderFunctionReturnTypeLocation = renderFunction && this.getTypeAnnotationPosition(renderFunction); - const snapshotMap = new SnapshotMap(this.lsAndTsDocResolver); + const snapshotMap = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshotMap.set(tsDoc.filePath, tsDoc); const convertPromises = inlayHints diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts index 281ae659f..3b9caf86e 100644 --- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts @@ -65,7 +65,7 @@ export class RenameProviderImpl implements RenameProvider { position: Position, newName: string ): Promise { - const { lang, tsDoc } = await this.getLSAndTSDoc(document); + const { lang, tsDoc, lsContainer } = await this.getLSAndTSDoc(document); const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)); @@ -85,7 +85,7 @@ export class RenameProviderImpl implements RenameProvider { return null; } - const docs = new SnapshotMap(this.lsAndTsDocResolver); + const docs = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); docs.set(tsDoc.filePath, tsDoc); let convertedRenameLocations: TsRenameLocation[] = await this.mapAndFilterRenameLocations( @@ -536,7 +536,7 @@ export class RenameProviderImpl implements RenameProvider { } private getSnapshot(filePath: string) { - return this.lsAndTsDocResolver.getSnapshot(filePath); + return this.lsAndTsDocResolver.getOrCreateSnapshot(filePath); } private checkShortHandBindingOrSlotLetLocation( diff --git a/packages/language-server/src/plugins/typescript/features/TypeDefinitionProvider.ts b/packages/language-server/src/plugins/typescript/features/TypeDefinitionProvider.ts index 93674fd9e..3c2ecf625 100644 --- a/packages/language-server/src/plugins/typescript/features/TypeDefinitionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/TypeDefinitionProvider.ts @@ -10,11 +10,11 @@ export class TypeDefinitionProviderImpl implements TypeDefinitionProvider { constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {} async getTypeDefinition(document: Document, position: Position): Promise { - const { tsDoc, lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); + const { tsDoc, lang, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)); const typeDefs = lang.getTypeDefinitionAtPosition(tsDoc.filePath, offset); - const snapshots = new SnapshotMap(this.lsAndTsDocResolver); + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshots.set(tsDoc.filePath, tsDoc); if (!typeDefs) { diff --git a/packages/language-server/src/plugins/typescript/features/UpdateImportsProvider.ts b/packages/language-server/src/plugins/typescript/features/UpdateImportsProvider.ts index aa46594ff..54aa9a060 100644 --- a/packages/language-server/src/plugins/typescript/features/UpdateImportsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/UpdateImportsProvider.ts @@ -6,14 +6,27 @@ import { WorkspaceEdit } from 'vscode-languageserver'; import { mapRangeToOriginal } from '../../../lib/documents'; -import { urlToPath } from '../../../utils'; +import { + createGetCanonicalFileName, + GetCanonicalFileName, + normalizePath, + urlToPath +} from '../../../utils'; import { FileRename, UpdateImportsProvider } from '../../interfaces'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; +import { forAllServices, LanguageServiceContainer } from '../service'; import { convertRange } from '../utils'; import { isKitTypePath, SnapshotMap } from './utils'; export class UpdateImportsProviderImpl implements UpdateImportsProvider { - constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {} + constructor( + private readonly lsAndTsDocResolver: LSAndTSDocResolver, + useCaseSensitiveFileNames: boolean + ) { + this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + } + + private getCanonicalFileName: GetCanonicalFileName; async updateImports(fileRename: FileRename): Promise { // TODO does this handle folder moves/renames correctly? old/new path isn't a file then @@ -23,7 +36,47 @@ export class UpdateImportsProviderImpl implements UpdateImportsProvider { return null; } - const ls = await this.getLSForPath(newPath); + const services: LanguageServiceContainer[] = []; + await forAllServices((ls) => { + services.push(ls); + }); + + const documentChanges = new Map(); + for (const service of services) { + await this.updateImportForSingleService(oldPath, newPath, service, documentChanges); + } + + return { + documentChanges: Array.from(documentChanges.values()) + }; + } + + async updateImportForSingleService( + oldPath: string, + newPath: string, + lsContainer: LanguageServiceContainer, + documentChanges: Map + ) { + const ls = lsContainer.getService(); + const program = ls.getProgram(); + if (!program) { + return; + } + + const canonicalOldPath = this.getCanonicalFileName(normalizePath(oldPath)); + const canonicalNewPath = this.getCanonicalFileName(normalizePath(newPath)); + const hasFile = program.getSourceFiles().some((sf) => { + const normalizedFileName = this.getCanonicalFileName(normalizePath(sf.fileName)); + return ( + normalizedFileName.startsWith(canonicalOldPath) || + normalizedFileName.startsWith(canonicalNewPath) + ); + }); + + if (!hasFile) { + return; + } + const oldPathTsProgramCasing = ls.getProgram()?.getSourceFile(oldPath)?.fileName ?? oldPath; // `getEditsForFileRename` might take a while const fileChanges = ls @@ -75,12 +128,15 @@ export class UpdateImportsProviderImpl implements UpdateImportsProvider { return change; }); - const docs = new SnapshotMap(this.lsAndTsDocResolver); - const documentChanges = await Promise.all( + const docs = new SnapshotMap(this.lsAndTsDocResolver, lsContainer); + await Promise.all( updateImportsChanges.map(async (change) => { + if (documentChanges.has(change.fileName)) { + return; + } const snapshot = await docs.retrieve(change.fileName); - return TextDocumentEdit.create( + const edit = TextDocumentEdit.create( OptionalVersionedTextDocumentIdentifier.create(snapshot.getURL(), null), change.textChanges.map((edit) => { const range = mapRangeToOriginal( @@ -90,13 +146,9 @@ export class UpdateImportsProviderImpl implements UpdateImportsProvider { return TextEdit.replace(range, edit.newText); }) ); + + documentChanges.set(change.fileName, edit); }) ); - - return { documentChanges }; - } - - private async getLSForPath(path: string) { - return this.lsAndTsDocResolver.getLSForPath(path); } } diff --git a/packages/language-server/src/plugins/typescript/features/utils.ts b/packages/language-server/src/plugins/typescript/features/utils.ts index f8ee48a21..dba13177a 100644 --- a/packages/language-server/src/plugins/typescript/features/utils.ts +++ b/packages/language-server/src/plugins/typescript/features/utils.ts @@ -12,6 +12,7 @@ import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; import { or } from '../../../utils'; import { FileMap } from '../../../lib/documents/fileCollection'; import { LSConfig } from '../../../ls-config'; +import { LanguageServiceContainer } from '../service'; type NodePredicate = (node: ts.Node) => boolean; @@ -144,7 +145,10 @@ export function getStoreOffsetOf$storeDeclaration(text: string, $storeVarStart: export class SnapshotMap { private map = new FileMap(); - constructor(private resolver: LSAndTSDocResolver) {} + constructor( + private resolver: LSAndTSDocResolver, + private sourceLs: LanguageServiceContainer + ) {} set(fileName: string, snapshot: DocumentSnapshot) { this.map.set(fileName, snapshot); @@ -156,12 +160,18 @@ export class SnapshotMap { async retrieve(fileName: string) { let snapshot = this.get(fileName); - if (!snapshot) { - const snap = await this.resolver.getSnapshot(fileName); - this.set(fileName, snap); - snapshot = snap; + if (snapshot) { + return snapshot; } - return snapshot; + + const snap = + this.sourceLs.snapshotManager.get(fileName) ?? + // should not happen in most cases, + // the file should be in the project otherwise why would we know about it + (await this.resolver.getOrCreateSnapshot(fileName)); + + this.set(fileName, snap); + return snap; } } diff --git a/packages/language-server/src/plugins/typescript/module-loader.ts b/packages/language-server/src/plugins/typescript/module-loader.ts index 91e59827b..915e50b26 100644 --- a/packages/language-server/src/plugins/typescript/module-loader.ts +++ b/packages/language-server/src/plugins/typescript/module-loader.ts @@ -166,7 +166,8 @@ export function createSvelteModuleLoader( getSnapshot: (fileName: string) => DocumentSnapshot, compilerOptions: ts.CompilerOptions, tsSystem: ts.System, - tsModule: typeof ts + tsModule: typeof ts, + getModuleResolutionHost: () => ts.ModuleResolutionHost | undefined ) { const getCanonicalFileName = createGetCanonicalFileName(tsSystem.useCaseSensitiveFileNames); const svelteSys = createSvelteSys(tsSystem); @@ -206,9 +207,16 @@ export function createSvelteModuleLoader( const previousTriedButFailed = failedPathToContainingFile.get(path); - for (const containingFile of previousTriedButFailed ?? []) { + if (!previousTriedButFailed) { + return; + } + + for (const containingFile of previousTriedButFailed) { failedLocationInvalidated.add(containingFile); } + + tsModuleCache.clear(); + typeReferenceCache.clear(); }, resolveModuleNames, resolveTypeReferenceDirectiveReferences, @@ -221,8 +229,8 @@ export function createSvelteModuleLoader( moduleNames: string[], containingFile: string, _reusedNames: string[] | undefined, - _redirectedReference: ts.ResolvedProjectReference | undefined, - _options: ts.CompilerOptions, + redirectedReference: ts.ResolvedProjectReference | undefined, + options: ts.CompilerOptions, containingSourceFile?: ts.SourceFile | undefined ): Array { return moduleNames.map((moduleName, index) => { @@ -234,7 +242,9 @@ export function createSvelteModuleLoader( moduleName, containingFile, containingSourceFile, - index + index, + redirectedReference, + options ); resolvedModule?.failedLookupLocations?.forEach((failedLocation) => { @@ -252,60 +262,44 @@ export function createSvelteModuleLoader( name: string, containingFile: string, containingSourceFile: ts.SourceFile | undefined, - index: number + index: number, + redirectedReference: ts.ResolvedProjectReference | undefined, + option: ts.CompilerOptions ): ts.ResolvedModuleWithFailedLookupLocations { - const mode = impliedNodeFormatResolver.resolve( - name, - index, - containingSourceFile, - compilerOptions - ); - // Delegate to the TS resolver first. - // If that does not bring up anything, try the Svelte Module loader - // which is able to deal with .svelte files. - const tsResolvedModuleWithFailedLookup = tsModule.resolveModuleName( + const mode = impliedNodeFormatResolver.resolve(name, index, containingSourceFile, option); + const resolvedModuleWithFailedLookup = tsModule.resolveModuleName( name, containingFile, compilerOptions, - tsSystem, + getModuleResolutionHost() ?? svelteSys, tsModuleCache, - undefined, + redirectedReference, mode ); - const tsResolvedModule = tsResolvedModuleWithFailedLookup.resolvedModule; - if (tsResolvedModule) { - return tsResolvedModuleWithFailedLookup; + const resolvedModule = resolvedModuleWithFailedLookup.resolvedModule; + + if (!resolvedModule || !isVirtualSvelteFilePath(resolvedModule.resolvedFileName)) { + return resolvedModuleWithFailedLookup; } - const svelteResolvedModuleWithFailedLookup = tsModule.resolveModuleName( - name, - containingFile, - compilerOptions, - svelteSys, - undefined, - undefined, - mode + const resolvedFileName = svelteSys.getRealSveltePathIfExists( + resolvedModule.resolvedFileName ); - const svelteResolvedModule = svelteResolvedModuleWithFailedLookup.resolvedModule; - if ( - !svelteResolvedModule || - !isVirtualSvelteFilePath(svelteResolvedModule.resolvedFileName) - ) { - return svelteResolvedModuleWithFailedLookup; + if (!isSvelteFilePath(resolvedFileName)) { + return resolvedModuleWithFailedLookup; } - const resolvedFileName = ensureRealSvelteFilePath(svelteResolvedModule.resolvedFileName); const snapshot = getSnapshot(resolvedFileName); const resolvedSvelteModule: ts.ResolvedModuleFull = { extension: getExtensionFromScriptKind(snapshot && snapshot.scriptKind), resolvedFileName, - isExternalLibraryImport: svelteResolvedModule.isExternalLibraryImport + isExternalLibraryImport: resolvedModule.isExternalLibraryImport }; return { - ...svelteResolvedModuleWithFailedLookup, + ...resolvedModuleWithFailedLookup, resolvedModule: resolvedSvelteModule }; } diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index fa2fe0c2b..8f5720bb0 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -1,19 +1,25 @@ -import { basename, dirname, join, resolve } from 'path'; +import { dirname, join, resolve, basename } from 'path'; import ts from 'typescript'; -import { RelativePattern, TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol'; +import { + PublishDiagnosticsParams, + RelativePattern, + TextDocumentContentChangeEvent +} from 'vscode-languageserver-protocol'; import { getPackageInfo, importSvelte } from '../../importPackage'; import { Document } from '../../lib/documents'; import { configLoader } from '../../lib/documents/configLoader'; import { FileMap, FileSet } from '../../lib/documents/fileCollection'; import { Logger } from '../../logger'; -import { createGetCanonicalFileName, normalizePath, pathToUrl, urlToPath } from '../../utils'; +import { + createGetCanonicalFileName, + isNotNullOrUndefined, + normalizePath, + pathToUrl, + urlToPath +} from '../../utils'; import { DocumentSnapshot, SvelteSnapshotOptions } from './DocumentSnapshot'; import { createSvelteModuleLoader } from './module-loader'; -import { - GlobalSnapshotsManager, - ignoredBuildDirectories, - SnapshotManager -} from './SnapshotManager'; +import { GlobalSnapshotsManager, SnapshotManager } from './SnapshotManager'; import { ensureRealSvelteFilePath, findTsConfigPath, @@ -27,15 +33,13 @@ export interface LanguageServiceContainer { readonly tsconfigPath: string; readonly compilerOptions: ts.CompilerOptions; readonly configErrors: ts.Diagnostic[]; - /** - * @internal Public for tests only - */ readonly snapshotManager: SnapshotManager; getService(skipSynchronize?: boolean): ts.LanguageService; updateSnapshot(documentOrFilePath: Document | string): DocumentSnapshot; deleteSnapshot(filePath: string): void; invalidateModuleCache(filePath: string[]): void; scheduleProjectFileUpdate(watcherNewFiles: string[]): void; + ensureProjectFileUpdates(): void; updateTsOrJsFile(fileName: string, changes?: TextDocumentContentChangeEvent[]): void; /** * Checks if a file is present in the project. @@ -50,7 +54,9 @@ export interface LanguageServiceContainer { onAutoImportProviderSettingsChanged(): void; onPackageJsonChange(packageJsonPath: string): void; getTsConfigSvelteOptions(): { namespace: string }; - + getResolvedProjectReferences(): TsConfigInfo[]; + openVirtualDocument(document: Document): void; + isShimFiles(filePath: string): boolean; dispose(): void; } @@ -63,7 +69,12 @@ declare module 'typescript' { */ hasInvalidatedResolutions?: (sourceFile: string) => boolean; + /** + * @internal + */ getModuleResolutionCache?(): ts.ModuleResolutionCache; + /** @internal */ + setCompilerHost?(host: ts.CompilerHost): void; } interface ResolvedModuleWithFailedLookupLocations { @@ -82,17 +93,26 @@ declare module 'typescript' { } } +export interface TsConfigInfo { + parsedCommandLine: ts.ParsedCommandLine; + snapshotManager: SnapshotManager; + pendingProjectFileUpdate: boolean; + configFilePath: string; + extendedConfigPaths?: Set; +} + const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; // 20 MB const services = new FileMap>(); const serviceSizeMap = new FileMap(); const configWatchers = new FileMap(); -const extendedConfigWatchers = new FileMap(); -const extendedConfigToTsConfigPath = new FileMap(); +const dependedConfigWatchers = new FileMap(); +const configPathToDependedProject = new FileMap(); const configFileModifiedTime = new FileMap(); const configFileForOpenFiles = new FileMap(); const pendingReloads = new FileSet(); const documentRegistries = new Map(); const pendingForAllServices = new Set>(); +const parsedTsConfigInfo = new FileMap(); /** * For testing only: Reset the cache for services. @@ -101,6 +121,7 @@ const pendingForAllServices = new Set>(); */ export function __resetCache() { services.clear(); + parsedTsConfigInfo.clear(); serviceSizeMap.clear(); configFileForOpenFiles.clear(); } @@ -112,7 +133,8 @@ export interface LanguageServiceDocumentContext { globalSnapshotsManager: GlobalSnapshotsManager; notifyExceedSizeLimit: (() => void) | undefined; extendedConfigCache: Map; - onProjectReloaded: (() => void) | undefined; + onProjectReloaded: ((configFileNames: string[]) => void) | undefined; + reportConfigError: ((diagnostics: PublishDiagnosticsParams) => void) | undefined; watchTsConfig: boolean; tsSystem: ts.System; projectService: ProjectService | undefined; @@ -129,13 +151,35 @@ export async function getService( docContext.tsSystem.useCaseSensitiveFileNames ); - const tsconfigPath = + const fileExistsWithCache = (fileName: string) => { + return ( + (parsedTsConfigInfo.has(fileName) && !pendingReloads.has(fileName)) || + docContext.tsSystem.fileExists(fileName) + ); + }; + + let tsconfigPath = configFileForOpenFiles.get(path) ?? - findTsConfigPath(path, workspaceUris, docContext.tsSystem.fileExists, getCanonicalFileName); + findTsConfigPath(path, workspaceUris, fileExistsWithCache, getCanonicalFileName); if (tsconfigPath) { - configFileForOpenFiles.set(path, tsconfigPath); - return getServiceForTsconfig(tsconfigPath, dirname(tsconfigPath), docContext); + /** + * Prevent infinite loop when the project reference is circular + */ + const triedTsConfig = new Set(); + const needAssign = !configFileForOpenFiles.has(path); + let service = await getConfiguredService(tsconfigPath); + if (!needAssign) { + return service; + } + + const defaultService = await findDefaultServiceForFile(service, triedTsConfig); + if (defaultService) { + configFileForOpenFiles.set(path, defaultService.tsconfigPath); + return defaultService; + } + + tsconfigPath = ''; } // Find closer boundary: workspace uri or node_modules @@ -156,6 +200,55 @@ export async function getService( docContext.tsSystem.getCurrentDirectory(), docContext ); + + function getConfiguredService(tsconfigPath: string) { + return getServiceForTsconfig(tsconfigPath, dirname(tsconfigPath), docContext); + } + + async function findDefaultServiceForFile( + service: LanguageServiceContainer, + triedTsConfig: Set + ): Promise { + service.ensureProjectFileUpdates(); + if (service.snapshotManager.isProjectFile(path)) { + return service; + } + if (triedTsConfig.has(service.tsconfigPath)) { + return; + } + + // TODO: maybe add support for ts 5.6's ancestor searching + return findDefaultFromProjectReferences(service, triedTsConfig); + } + + async function findDefaultFromProjectReferences( + service: LanguageServiceContainer, + triedTsConfig: Set + ) { + const projectReferences = service.getResolvedProjectReferences(); + if (projectReferences.length === 0) { + return undefined; + } + + let possibleSubPaths: string[] = []; + for (const ref of projectReferences) { + if (ref.snapshotManager.isProjectFile(path)) { + return getConfiguredService(ref.configFilePath); + } + + if (ref.parsedCommandLine.projectReferences?.length) { + possibleSubPaths.push(ref.configFilePath); + } + } + + for (const ref of possibleSubPaths) { + const subService = await getConfiguredService(ref); + const defaultService = await findDefaultServiceForFile(subService, triedTsConfig); + if (defaultService) { + return defaultService; + } + } + } } export async function forAllServices( @@ -182,6 +275,9 @@ export async function getServiceForTsconfig( workspacePath: string, docContext: LanguageServiceDocumentContext ): Promise { + if (tsconfigPath) { + tsconfigPath = normalizePath(tsconfigPath); + } const tsconfigPathOrWorkspacePath = tsconfigPath || workspacePath; const reloading = pendingReloads.has(tsconfigPath); @@ -190,6 +286,7 @@ export async function getServiceForTsconfig( if (reloading || !services.has(tsconfigPathOrWorkspacePath)) { if (reloading) { Logger.log('Reloading ts service at ', tsconfigPath, ' due to config updated'); + parsedTsConfigInfo.delete(tsconfigPath); } else { Logger.log('Initialize new ts service at ', tsconfigPath); } @@ -216,36 +313,31 @@ async function createLanguageService( ): Promise { const { tsSystem } = docContext; - const { - options: compilerOptions, - errors: configErrors, - fileNames: files, - raw, - extendedConfigPaths, - wildcardDirectories - } = getParsedConfig(); + const projectConfig = getParsedConfig(); + const { options: compilerOptions, raw, errors: configErrors } = projectConfig; const getCanonicalFileName = createGetCanonicalFileName(tsSystem.useCaseSensitiveFileNames); - watchWildCardDirectories(); - - // raw is the tsconfig merged with extending config - // see: https://github.com/microsoft/TypeScript/blob/08e4f369fbb2a5f0c30dee973618d65e6f7f09f8/src/compiler/commandLineParser.ts#L2537 - const snapshotManager = new SnapshotManager( - docContext.globalSnapshotsManager, - raw, - workspacePath, - tsSystem, - files, - wildcardDirectories - ); + watchWildCardDirectories(projectConfig); + + const snapshotManager = createSnapshotManager(projectConfig, tsconfigPath); // Load all configs within the tsconfig scope and the one above so that they are all loaded // by the time they need to be accessed synchronously by DocumentSnapshots. await configLoader.loadConfigs(workspacePath); - const svelteModuleLoader = createSvelteModuleLoader(getSnapshot, compilerOptions, tsSystem, ts); + const svelteModuleLoader = createSvelteModuleLoader( + getSnapshot, + compilerOptions, + tsSystem, + ts, + () => host?.getCompilerHost?.() + ); let svelteTsPath: string; + /** + * set and clear during program creation, shouldn't not be cached elsewhere + */ + let compilerHost: ts.CompilerHost | undefined; try { // For when svelte2tsx/svelte-check is part of node_modules, for example VS Code extension svelteTsPath = dirname(require.resolve(docContext.ambientTypesSource)); @@ -262,27 +354,12 @@ async function createLanguageService( ? importSvelte(tsconfigPath || workspacePath) : undefined; - const isSvelte3 = sveltePackageInfo.version.major === 3; - const svelteHtmlDeclaration = isSvelte3 - ? undefined - : join(sveltePackageInfo.path, 'svelte-html.d.ts'); - const svelteHtmlFallbackIfNotExist = - svelteHtmlDeclaration && tsSystem.fileExists(svelteHtmlDeclaration) - ? svelteHtmlDeclaration - : './svelte-jsx-v4.d.ts'; - const changedFilesForExportCache = new Set(); - - const svelteTsxFiles = ( - isSvelte3 - ? ['./svelte-shims.d.ts', './svelte-jsx.d.ts', './svelte-native-jsx.d.ts'] - : ['./svelte-shims-v4.d.ts', svelteHtmlFallbackIfNotExist, './svelte-native-jsx.d.ts'] - ).map((f) => tsSystem.resolvePath(resolve(svelteTsPath, f))); + const svelteTsxFiles = getSvelteShimFiles(); let languageServiceReducedMode = false; let projectVersion = 0; - let dirty = false; - let pendingProjectFileUpdate = false; + let dirty = projectConfig.fileNames.length > 0; const host: ts.LanguageServiceHost = { log: (message) => Logger.debug(`[ts] ${message}`), @@ -297,7 +374,10 @@ async function createLanguageService( readFile: svelteModuleLoader.readFile, resolveModuleNames: svelteModuleLoader.resolveModuleNames, readDirectory: svelteModuleLoader.readDirectory, + realpath: tsSystem.realpath, getDirectories: tsSystem.getDirectories, + getProjectReferences: () => projectConfig.projectReferences, + getParsedCommandLine, useCaseSensitiveFileNames: () => tsSystem.useCaseSensitiveFileNames, getScriptKind: (fileName: string) => getSnapshot(fileName).scriptKind, getProjectVersion: () => projectVersion.toString(), @@ -305,11 +385,17 @@ async function createLanguageService( resolveTypeReferenceDirectiveReferences: svelteModuleLoader.resolveTypeReferenceDirectiveReferences, hasInvalidatedResolutions: svelteModuleLoader.mightHaveInvalidatedResolutions, - getModuleResolutionCache: svelteModuleLoader.getModuleResolutionCache + getModuleResolutionCache: svelteModuleLoader.getModuleResolutionCache, + useSourceOfProjectReferenceRedirect() { + return !languageServiceReducedMode; + }, + setCompilerHost: (host) => (compilerHost = host), + getCompilerHost: () => compilerHost }; const documentRegistry = getOrCreateDocumentRegistry( - host.getCurrentDirectory(), + // this should mostly be a singleton while host.getCurrentDirectory() might be the directory where the tsconfig is + tsSystem.getCurrentDirectory(), tsSystem.useCaseSensitiveFileNames ); @@ -326,8 +412,7 @@ async function createLanguageService( docContext.globalSnapshotsManager.onChange(scheduleUpdate); reduceLanguageServiceCapabilityIfFileSizeTooBig(); - updateExtendedConfigDependents(); - watchConfigFile(); + watchConfigFiles(projectConfig.extendedConfigPaths, projectConfig); return { tsconfigPath, @@ -338,6 +423,7 @@ async function createLanguageService( deleteSnapshot, scheduleProjectFileUpdate, updateTsOrJsFile, + ensureProjectFileUpdates, hasFile, fileBelongsToProject, snapshotManager, @@ -345,10 +431,34 @@ async function createLanguageService( onAutoImportProviderSettingsChanged, onPackageJsonChange, getTsConfigSvelteOptions, + getResolvedProjectReferences, + openVirtualDocument, + isShimFiles, dispose }; - function watchWildCardDirectories() { + function createSnapshotManager( + parsedCommandLine: ts.ParsedCommandLine, + configFileName: string + ) { + const cached = configFileName ? parsedTsConfigInfo.get(configFileName) : undefined; + if (cached?.snapshotManager) { + return cached.snapshotManager; + } + // raw is the tsconfig merged with extending config + // see: https://github.com/microsoft/TypeScript/blob/08e4f369fbb2a5f0c30dee973618d65e6f7f09f8/src/compiler/commandLineParser.ts#L2537 + return new SnapshotManager( + docContext.globalSnapshotsManager, + parsedCommandLine.raw, + configFileName ? dirname(configFileName) : workspacePath, + tsSystem, + parsedCommandLine.fileNames.map(normalizePath), + parsedCommandLine.wildcardDirectories + ); + } + + function watchWildCardDirectories(parseCommandLine: ts.ParsedCommandLine) { + const { wildcardDirectories } = parseCommandLine; if (!wildcardDirectories || !docContext.watchDirectory) { return; } @@ -376,10 +486,8 @@ async function createLanguageService( } function getService(skipSynchronize?: boolean) { - if (pendingProjectFileUpdate) { - updateProjectFiles(); - pendingProjectFileUpdate = false; - } + ensureProjectFileUpdates(); + if (!skipSynchronize) { updateIfDirty(); } @@ -411,7 +519,13 @@ async function createLanguageService( function updateSnapshotFromDocument(document: Document): DocumentSnapshot { const filePath = document.getFilePath() || ''; const prevSnapshot = snapshotManager.get(filePath); - if (prevSnapshot?.version === document.version) { + + if ( + prevSnapshot?.version === document.version && + // In the test, there might be a new document instance with a different openedByClient + // In that case, Create a new snapshot otherwise the getClientFileNames won't include the new client file + prevSnapshot.isOpenedInClient() === document.openedByClient + ) { return prevSnapshot; } @@ -481,17 +595,38 @@ async function createLanguageService( } function scheduleProjectFileUpdate(watcherNewFiles: string[]): void { - if (snapshotManager.areIgnoredFromNewFileWatch(watcherNewFiles)) { - return; + if (!snapshotManager.areIgnoredFromNewFileWatch(watcherNewFiles)) { + scheduleUpdate(); + const info = parsedTsConfigInfo.get(tsconfigPath); + if (info) { + info.pendingProjectFileUpdate = true; + } } - scheduleUpdate(); - pendingProjectFileUpdate = true; + if (!projectConfig.projectReferences) { + return; + } + for (const ref of projectConfig.projectReferences) { + const config = parsedTsConfigInfo.get(ref.path); + if ( + config && + // handled by the respective service + !services.has(config.configFilePath) && + !config.snapshotManager.areIgnoredFromNewFileWatch(watcherNewFiles) + ) { + config.pendingProjectFileUpdate = true; + scheduleUpdate(); + } + } } - function updateProjectFiles(): void { + function ensureProjectFileUpdates(): void { + const info = parsedTsConfigInfo.get(tsconfigPath); + if (!info || !info.pendingProjectFileUpdate) { + return; + } const projectFileCountBefore = snapshotManager.getProjectFileNames().length; - snapshotManager.updateProjectFiles(); + ensureFilesForConfigUpdates(info); const projectFileCountAfter = snapshotManager.getProjectFileNames().length; if (projectFileCountAfter > projectFileCountBefore) { @@ -535,72 +670,26 @@ async function createLanguageService( } function getParsedConfig() { - const forcedCompilerOptions: ts.CompilerOptions = { - allowNonTsExtensions: true, - target: ts.ScriptTarget.Latest, - allowJs: true, - noEmit: true, - declaration: false, - skipLibCheck: true - }; - - // always let ts parse config to get default compilerOption - let configJson = - (tsconfigPath && ts.readConfigFile(tsconfigPath, tsSystem.readFile).config) || - getDefaultJsConfig(); - - // Only default exclude when no extends for now - if (!configJson.extends) { - configJson = Object.assign( - { - exclude: getDefaultExclude() - }, - configJson - ); - } - - const extendedConfigPaths = new Set(); - const { extendedConfigCache } = docContext; - const cacheMonitorProxy = { - ...docContext.extendedConfigCache, - get(key: string) { - extendedConfigPaths.add(key); - return extendedConfigCache.get(key); - }, - has(key: string) { - extendedConfigPaths.add(key); - return extendedConfigCache.has(key); - }, - set(key: string, value: ts.ExtendedConfigCacheEntry) { - extendedConfigPaths.add(key); - return extendedConfigCache.set(key, value); + let compilerOptions: ts.CompilerOptions; + let parsedConfig: ts.ParsedCommandLine; + let extendedConfigPaths: Set | undefined; + + if (tsconfigPath) { + const info = ensureTsConfigInfoUpToDate(tsconfigPath); + // tsconfig is either found from file-system or passed from svelte-check + // so this is already be validated to exist + if (!info) { + throw new Error('Failed to get tsconfig: ' + tsconfigPath); } - }; - - const parsedConfig = ts.parseJsonConfigFileContent( - configJson, - tsSystem, - workspacePath, - forcedCompilerOptions, - tsconfigPath, - undefined, - [ - { - extension: 'svelte', - isMixedContent: true, - // Deferred was added in a later TS version, fall back to tsx - // If Deferred exists, this means that all Svelte files are included - // in parsedConfig.fileNames - scriptKind: ts.ScriptKind.Deferred ?? ts.ScriptKind.TS - } - ], - cacheMonitorProxy - ); + compilerOptions = info.parsedCommandLine.options; + parsedConfig = info.parsedCommandLine; + extendedConfigPaths = info.extendedConfigPaths; + } else { + const config = parseDefaultCompilerOptions(); + compilerOptions = config.compilerOptions; + parsedConfig = config.parsedConfig; + } - const compilerOptions: ts.CompilerOptions = { - ...parsedConfig.options, - ...forcedCompilerOptions - }; if ( !compilerOptions.moduleResolution || compilerOptions.moduleResolution === ts.ModuleResolutionKind.Classic @@ -609,6 +698,7 @@ async function createLanguageService( // NodeJS: up to 4.9, Node10: since 5.0 (ts.ModuleResolutionKind as any).NodeJs ?? ts.ModuleResolutionKind.Node10; } + if ( !compilerOptions.module || [ @@ -623,6 +713,12 @@ async function createLanguageService( compilerOptions.module = ts.ModuleKind.ESNext; } + if (!compilerOptions.target) { + compilerOptions.target = ts.ScriptTarget.Latest; + } else if (ts.ScriptTarget.ES2015 > compilerOptions.target) { + compilerOptions.target = ts.ScriptTarget.ES2015; + } + // detect which JSX namespace to use (svelte | svelteNative) if not specified or not compatible if (!compilerOptions.jsxFactory || !compilerOptions.jsxFactory.startsWith('svelte')) { //override if we detect svelte-native @@ -640,6 +736,20 @@ async function createLanguageService( } } + const svelteConfigDiagnostics = checkSvelteInput(parsedConfig); + if (svelteConfigDiagnostics.length > 0) { + docContext.reportConfigError?.({ + uri: pathToUrl(tsconfigPath), + diagnostics: svelteConfigDiagnostics.map((d) => ({ + message: d.messageText as string, + range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, + severity: ts.DiagnosticCategory.Error, + source: 'svelte' + })) + }); + parsedConfig.errors.push(...svelteConfigDiagnostics); + } + return { ...parsedConfig, fileNames: parsedConfig.fileNames.map(normalizePath), @@ -648,15 +758,43 @@ async function createLanguageService( }; } - /** - * This should only be used when there's no jsconfig/tsconfig at all - */ - function getDefaultJsConfig(): { - compilerOptions: ts.CompilerOptions; - include: string[]; - } { - return { + function checkSvelteInput(config: ts.ParsedCommandLine) { + if (!tsconfigPath || config.raw.references || config.raw.files) { + return []; + } + + const svelteFiles = config.fileNames.filter(isSvelteFilePath); + if (svelteFiles.length > 0) { + return []; + } + const { include, exclude } = config.raw; + const inputText = JSON.stringify(include); + const excludeText = JSON.stringify(exclude); + const svelteConfigDiagnostics: ts.Diagnostic[] = [ + { + category: ts.DiagnosticCategory.Error, + code: 0, + file: undefined, + start: undefined, + length: undefined, + messageText: + `No svelte input files were found in config file '${tsconfigPath}'. ` + + `Did you forget to add svelte files to the 'include' in your ${basename(tsconfigPath)}? ` + + `Specified 'include' paths were '${inputText}' and 'exclude' paths were '${excludeText}'`, + source: 'svelte' + } + ]; + + return svelteConfigDiagnostics; + } + + function parseDefaultCompilerOptions() { + let configJson = { compilerOptions: { + allowJs: true, + noEmit: true, + declaration: false, + skipLibCheck: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true }, @@ -664,10 +802,17 @@ async function createLanguageService( // with potentially completely unrelated .ts/.js files: include: [] }; - } - function getDefaultExclude() { - return ['node_modules', ...ignoredBuildDirectories]; + const parsedConfig = ts.parseJsonConfigFileContent(configJson, tsSystem, workspacePath); + + const compilerOptions: ts.CompilerOptions = { + ...parsedConfig.options, + target: ts.ScriptTarget.Latest, + allowNonTsExtensions: true, + moduleResolution: ts.ModuleResolutionKind.Node10 + }; + + return { compilerOptions, parsedConfig }; } /** @@ -694,6 +839,7 @@ async function createLanguageService( } function dispose() { + compilerHost = undefined; languageService.dispose(); snapshotManager.dispose(); configWatchers.get(tsconfigPath)?.close(); @@ -702,19 +848,23 @@ async function createLanguageService( docContext.globalSnapshotsManager.removeChangeListener(scheduleUpdate); } - function updateExtendedConfigDependents() { - extendedConfigPaths.forEach((extendedConfig) => { - let dependedTsConfig = extendedConfigToTsConfigPath.get(extendedConfig); + function watchConfigFiles( + extendedConfigPaths: Set | undefined, + parsedCommandLine: ts.ParsedCommandLine + ) { + const tsconfigDependencies = Array.from(extendedConfigPaths ?? []).concat( + parsedCommandLine.projectReferences?.map((r) => r.path) ?? [] + ); + tsconfigDependencies.forEach((configPath) => { + let dependedTsConfig = configPathToDependedProject.get(configPath); if (!dependedTsConfig) { dependedTsConfig = new FileSet(tsSystem.useCaseSensitiveFileNames); - extendedConfigToTsConfigPath.set(extendedConfig, dependedTsConfig); + configPathToDependedProject.set(configPath, dependedTsConfig); } dependedTsConfig.add(tsconfigPath); }); - } - function watchConfigFile() { if (!tsSystem.watchFile || !docContext.watchTsConfig) { return; } @@ -728,16 +878,16 @@ async function createLanguageService( ); } - for (const config of extendedConfigPaths) { - if (extendedConfigWatchers.has(config)) { + for (const config of tsconfigDependencies) { + if (dependedConfigWatchers.has(config)) { continue; } configFileModifiedTime.set(config, tsSystem.getModifiedTime?.(config)); - extendedConfigWatchers.set( + dependedConfigWatchers.set( config, // for some reason setting the polling interval is necessary, else some error in TS is thrown - tsSystem.watchFile(config, createWatchExtendedConfigCallback(docContext), 1000) + tsSystem.watchFile(config, createWatchDependedConfigCallback(docContext), 1000) ); } } @@ -763,7 +913,8 @@ async function createLanguageService( configFileForOpenFiles.clear(); } - docContext.onProjectReloaded?.(); + docContext.onProjectReloaded?.([fileName]); + docContext.reportConfigError?.({ uri: pathToUrl(fileName), diagnostics: [] }); } function updateIfDirty() { @@ -780,6 +931,7 @@ async function createLanguageService( } dirty = false; + compilerHost = undefined; // https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624 // host.getCachedExportInfoMap will create the cache if it doesn't exist @@ -791,6 +943,9 @@ async function createLanguageService( } exportMapCache.releaseSymbols(); + // https://github.com/microsoft/TypeScript/blob/941d1543c201e40d87e63c9db04818493afdd9e7/src/server/project.ts#L1731 + // if one file change results in clearing the cache + // don't continue to check other files, this will mark the cache as usable while it's empty for (const fileName of changedFilesForExportCache) { const oldFile = oldProgram.getSourceFile(fileName); const newFile = program?.getSourceFile(fileName); @@ -800,11 +955,15 @@ async function createLanguageService( continue; } - if (oldFile && newFile) { - exportMapCache.onFileChanged?.(oldFile, newFile, false); - } else { + if (!oldFile || !newFile) { // new file or deleted file exportMapCache.clear(); + break; + } + + const cleared = exportMapCache.onFileChanged?.(oldFile, newFile, false); + if (cleared) { + break; } } changedFilesForExportCache.clear(); @@ -875,6 +1034,141 @@ async function createLanguageService( namespace: transformationConfig.typingsNamespace }; } + + function ensureTsConfigInfoUpToDate(configFilePath: string) { + const cached = parsedTsConfigInfo.get(configFilePath); + if (cached !== undefined) { + ensureFilesForConfigUpdates(cached); + return cached; + } + + const content = tsSystem.fileExists(configFilePath) && tsSystem.readFile(configFilePath); + if (!content) { + parsedTsConfigInfo.set(configFilePath, null); + return null; + } + + const json = ts.parseJsonText(configFilePath, content); + + const extendedConfigPaths = new Set(); + const { extendedConfigCache } = docContext; + const cacheMonitorProxy = { + ...docContext.extendedConfigCache, + get(key: string) { + extendedConfigPaths.add(key); + return extendedConfigCache.get(key); + }, + has(key: string) { + extendedConfigPaths.add(key); + return extendedConfigCache.has(key); + }, + set(key: string, value: ts.ExtendedConfigCacheEntry) { + extendedConfigPaths.add(key); + return extendedConfigCache.set(key, value); + } + }; + + // TypeScript will throw if the parsedCommandLine doesn't include the sourceFile for the config file + // i.e. it must be directly parse from the json text instead of a javascript object like we do in getParsedConfig + const parsedCommandLine = ts.parseJsonSourceFileConfigFileContent( + json, + tsSystem, + dirname(configFilePath), + /*existingOptions*/ undefined, + configFilePath, + /*resolutionStack*/ undefined, + [ + { + extension: 'svelte', + isMixedContent: true, + // Deferred was added in a later TS version, fall back to tsx + // If Deferred exists, this means that all Svelte files are included + // in parsedConfig.fileNames + scriptKind: ts.ScriptKind.Deferred ?? ts.ScriptKind.TS + } + ], + cacheMonitorProxy + ); + + parsedCommandLine.options.allowNonTsExtensions = true; + + const snapshotManager = createSnapshotManager(parsedCommandLine, configFilePath); + + const tsconfigInfo: TsConfigInfo = { + parsedCommandLine, + snapshotManager, + pendingProjectFileUpdate: false, + configFilePath, + extendedConfigPaths + }; + parsedTsConfigInfo.set(configFilePath, tsconfigInfo); + + watchConfigFiles(extendedConfigPaths, parsedCommandLine); + + return tsconfigInfo; + } + + function getParsedCommandLine(configFilePath: string) { + return ensureTsConfigInfoUpToDate(configFilePath)?.parsedCommandLine; + } + + function ensureFilesForConfigUpdates(info: TsConfigInfo | null) { + if (info?.pendingProjectFileUpdate) { + info.pendingProjectFileUpdate = false; + info.snapshotManager.updateProjectFiles(); + info.parsedCommandLine.fileNames = info.snapshotManager.getProjectFileNames(); + } + } + + function getResolvedProjectReferences(): TsConfigInfo[] { + if (!tsconfigPath || !projectConfig.projectReferences) { + return []; + } + + return projectConfig.projectReferences + .map((ref) => ensureTsConfigInfoUpToDate(normalizePath(ref.path))) + .filter(isNotNullOrUndefined); + } + + function openVirtualDocument(document: Document) { + const filePath = document.getFilePath(); + if (!filePath) { + return; + } + configFileForOpenFiles.set(filePath, tsconfigPath || workspacePath); + updateSnapshot(document); + scheduleUpdate(filePath); + } + + function getSvelteShimFiles() { + const isSvelte3 = sveltePackageInfo.version.major === 3; + const svelteHtmlDeclaration = isSvelte3 + ? undefined + : join(sveltePackageInfo.path, 'svelte-html.d.ts'); + const svelteHtmlFallbackIfNotExist = + svelteHtmlDeclaration && tsSystem.fileExists(svelteHtmlDeclaration) + ? svelteHtmlDeclaration + : './svelte-jsx-v4.d.ts'; + + const svelteTsxFiles = ( + isSvelte3 + ? ['./svelte-shims.d.ts', './svelte-jsx.d.ts', './svelte-native-jsx.d.ts'] + : [ + './svelte-shims-v4.d.ts', + svelteHtmlFallbackIfNotExist, + './svelte-native-jsx.d.ts' + ] + ).map((f) => tsSystem.resolvePath(resolve(svelteTsPath, f))); + + const result = new FileSet(tsSystem.useCaseSensitiveFileNames); + + svelteTsxFiles.forEach((f) => result.add(normalizePath(f))); + return result; + } + + function isShimFiles(filePath: string) { + return svelteTsxFiles.has(normalizePath(filePath)); + } } /** @@ -934,7 +1228,7 @@ function exceedsTotalSizeLimitForNonTsFiles( * because it would reference the closure * So that GC won't drop it and cause memory leaks */ -function createWatchExtendedConfigCallback(docContext: LanguageServiceDocumentContext) { +function createWatchDependedConfigCallback(docContext: LanguageServiceDocumentContext) { return async ( fileName: string, kind: ts.FileWatcherEventKind, @@ -950,10 +1244,18 @@ function createWatchExtendedConfigCallback(docContext: LanguageServiceDocumentCo return; } + const getCanonicalFileName = createGetCanonicalFileName( + docContext.tsSystem.useCaseSensitiveFileNames + ); + + docContext.extendedConfigCache.delete(getCanonicalFileName(fileName)); + // rely on TypeScript internal behavior so delete both just in case docContext.extendedConfigCache.delete(fileName); - const promises = Array.from(extendedConfigToTsConfigPath.get(fileName) ?? []).map( + const reloadingConfigs: string[] = []; + const promises = Array.from(configPathToDependedProject.get(fileName) ?? []).map( async (config) => { + reloadingConfigs.push(config); const oldService = services.get(config); scheduleReload(config); (await oldService)?.dispose(); @@ -961,7 +1263,7 @@ function createWatchExtendedConfigCallback(docContext: LanguageServiceDocumentCo ); await Promise.all(promises); - docContext.onProjectReloaded?.(); + docContext.onProjectReloaded?.(reloadingConfigs); }; } diff --git a/packages/language-server/src/plugins/typescript/serviceCache.ts b/packages/language-server/src/plugins/typescript/serviceCache.ts index 379a0306f..d91b05b32 100644 --- a/packages/language-server/src/plugins/typescript/serviceCache.ts +++ b/packages/language-server/src/plugins/typescript/serviceCache.ts @@ -81,8 +81,10 @@ export function createProject( 'getSymlinkCache', 'getPackageJsonsVisibleToFile', 'getPackageJsonAutoImportProvider', - 'includePackageJsonAutoImports', - 'useSourceOfProjectReferenceRedirect' + 'includePackageJsonAutoImports' + // Volar doesn't have the "languageServiceReducedMode" support but we do + // so don't proxy this method and implement this directly in the ts.LanguageServiceHost + // 'useSourceOfProjectReferenceRedirect' ]; proxyMethods.forEach((key) => ((host as any)[key] = project[key].bind(project))); diff --git a/packages/language-server/src/plugins/typescript/svelte-sys.ts b/packages/language-server/src/plugins/typescript/svelte-sys.ts index 4fb3c205c..a639b2a58 100644 --- a/packages/language-server/src/plugins/typescript/svelte-sys.ts +++ b/packages/language-server/src/plugins/typescript/svelte-sys.ts @@ -20,12 +20,18 @@ export function createSvelteSys(tsSystem: ts.System) { } } + function getRealSveltePathIfExists(path: string) { + return svelteFileExists(path) ? toRealSvelteFilePath(path) : path; + } + const svelteSys: ts.System & { deleteFromCache: (path: string) => void; svelteFileExists: (path: string) => boolean; + getRealSveltePathIfExists: (path: string) => string; } = { ...tsSystem, svelteFileExists, + getRealSveltePathIfExists, fileExists(path: string) { // We need to check both .svelte and .svelte.ts/js because that's how Svelte 5 will likely mark files with runes in them const sveltePathExists = svelteFileExists(path); @@ -36,7 +42,7 @@ export function createSvelteSys(tsSystem: ts.System) { }, readFile(path: string) { // No getSnapshot here, because TS will very rarely call this and only for files that are not in the project - return tsSystem.readFile(svelteFileExists(path) ? toRealSvelteFilePath(path) : path); + return tsSystem.readFile(getRealSveltePathIfExists(path)); }, readDirectory(path, extensions, exclude, include, depth) { const extensionsWithSvelte = extensions ? [...extensions, '.svelte'] : undefined; diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 3fffda855..d19e56dcc 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -199,7 +199,10 @@ export function startServer(options?: LSOptions) { onProjectReloaded: refreshCrossFilesSemanticFeatures, watch: true, nonRecursiveWatchPattern, - watchDirectory: (patterns) => watchDirectory(patterns) + watchDirectory: (patterns) => watchDirectory(patterns), + reportConfigError(diagnostic) { + connection?.sendDiagnostics(diagnostic); + } }), normalizedWorkspaceUris, docManager diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index b6100e60c..de8e54f56 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -18,6 +18,7 @@ import { JSOrTSDocumentSnapshot } from './plugins/typescript/DocumentSnapshot'; import { isInGeneratedCode } from './plugins/typescript/features/utils'; import { convertRange, getDiagnosticTag, mapSeverity } from './plugins/typescript/utils'; import { pathToUrl, urlToPath } from './utils'; +import { groupBy } from 'lodash'; export type SvelteCheckDiagnosticSource = 'js' | 'css' | 'svelte'; @@ -188,10 +189,36 @@ export class SvelteCheck { private async getDiagnosticsForTsconfig(tsconfigPath: string) { const lsContainer = await this.getLSContainer(tsconfigPath); + const map = (diagnostic: ts.Diagnostic, range?: Range): Diagnostic => { + const file = diagnostic.file; + range ??= file + ? convertRange( + { positionAt: file.getLineAndCharacterOfPosition.bind(file) }, + diagnostic + ) + : { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }; - const noInputsFoundError = lsContainer.configErrors?.find((e) => e.code === 18003); - if (noInputsFoundError) { - throw new Error(noInputsFoundError.messageText.toString()); + return { + range: range, + severity: mapSeverity(diagnostic.category), + source: diagnostic.source, + message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), + code: diagnostic.code, + tags: getDiagnosticTag(diagnostic) + }; + }; + + if (lsContainer.configErrors) { + const grouped = groupBy( + lsContainer.configErrors, + (error) => error.file?.fileName ?? tsconfigPath + ); + + return Object.entries(grouped).map(([filePath, errors]) => ({ + filePath, + text: '', + diagnostics: errors.map((diagnostic) => map(diagnostic)) + })); } const lang = lsContainer.getService(); @@ -211,6 +238,7 @@ export class SvelteCheck { const skipDiagnosticsForFile = (options.skipLibCheck && file.isDeclarationFile) || (options.skipDefaultLibCheck && file.hasNoDefaultLib) || + lsContainer.isShimFiles(file.fileName) || // ignore JS files in node_modules /\/node_modules\/.+\.(c|m)?js$/.test(file.fileName); const snapshot = lsContainer.snapshotManager.get(file.fileName) as @@ -218,20 +246,6 @@ export class SvelteCheck { | undefined; const isKitFile = snapshot?.kitFile ?? false; const diagnostics: Diagnostic[] = []; - const map = (diagnostic: ts.Diagnostic, range?: Range) => ({ - range: - range ?? - convertRange( - { positionAt: file.getLineAndCharacterOfPosition.bind(file) }, - diagnostic - ), - severity: mapSeverity(diagnostic.category), - source: diagnostic.source, - message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), - code: diagnostic.code, - tags: getDiagnosticTag(diagnostic) - }); - if (!skipDiagnosticsForFile) { const originalDiagnostics = [ ...lang.getSyntacticDiagnostics(file.fileName), 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 92601459f..dbccee6e5 100644 --- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts @@ -98,7 +98,7 @@ describe('DiagnosticsProvider', function () { ); const newFilePath = normalizePath(path.join(testDir, 'empty-export.ts')) || ''; - await lsAndTsDocResolver.getSnapshot(newFilePath); + await lsAndTsDocResolver.getOrCreateSnapshot(newFilePath); const diagnostics1 = await plugin.getDiagnostics(document); assert.deepStrictEqual( diff --git a/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts b/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts index ecd5d4f5b..12509c1c6 100644 --- a/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts @@ -31,7 +31,11 @@ describe('FindReferencesProvider', function () { (textDocument) => new Document(textDocument.uri, textDocument.text) ); const lsConfigManager = new LSConfigManager(); - const lsAndTsDocResolver = new LSAndTSDocResolver(docManager, [testDir], lsConfigManager); + const lsAndTsDocResolver = new LSAndTSDocResolver( + docManager, + [pathToUrl(testDir)], + lsConfigManager + ); const provider = new FindReferencesProviderImpl( lsAndTsDocResolver, new FindComponentReferencesProviderImpl(lsAndTsDocResolver) diff --git a/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts b/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts index 17b6d8c35..49e146108 100644 --- a/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts @@ -25,7 +25,11 @@ describe('HoverProvider', function () { (textDocument) => new Document(textDocument.uri, textDocument.text) ); const lsConfigManager = new LSConfigManager(); - const lsAndTsDocResolver = new LSAndTSDocResolver(docManager, [testDir], lsConfigManager); + const lsAndTsDocResolver = new LSAndTSDocResolver( + docManager, + [pathToUrl(testDir)], + lsConfigManager + ); const provider = new HoverProviderImpl(lsAndTsDocResolver); const document = openDoc(filename); return { provider, document }; diff --git a/packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts b/packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts index 57ad86192..152542342 100644 --- a/packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts @@ -29,7 +29,7 @@ describe('ImplementationProvider', function () { ); const lsAndTsDocResolver = new LSAndTSDocResolver( docManager, - [testDir], + [pathToUrl(testDir)], new LSConfigManager() ); const provider = new ImplementationProviderImpl(lsAndTsDocResolver); diff --git a/packages/language-server/test/plugins/typescript/features/TypeDefinitionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/TypeDefinitionProvider.test.ts index f4e0baa44..e709bbd3c 100644 --- a/packages/language-server/test/plugins/typescript/features/TypeDefinitionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/TypeDefinitionProvider.test.ts @@ -29,7 +29,7 @@ describe('TypeDefinitionProvider', function () { ); const lsAndTsDocResolver = new LSAndTSDocResolver( docManager, - [testDir], + [pathToUrl(testDir)], new LSConfigManager() ); const provider = new TypeDefinitionProviderImpl(lsAndTsDocResolver); diff --git a/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts index 998896aa3..cdba08f4b 100644 --- a/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts @@ -33,7 +33,10 @@ describe('UpdateImportsProviderImpl', function () { new LSConfigManager(), { tsSystem: { ...ts.sys, useCaseSensitiveFileNames } } ); - const updateImportsProvider = new UpdateImportsProviderImpl(lsAndTsDocResolver); + const updateImportsProvider = new UpdateImportsProviderImpl( + lsAndTsDocResolver, + useCaseSensitiveFileNames + ); const filePath = join(updateImportTestDir, filename); const fileUri = pathToUrl(filePath); const document = docManager.openClientDocument({ diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/expectedv2.json new file mode 100644 index 000000000..ddef317a6 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/expectedv2.json @@ -0,0 +1,10 @@ +[ + { + "range": { "start": { "line": 6, "character": 8 }, "end": { "line": 6, "character": 9 } }, + "severity": 1, + "source": "ts", + "message": "Type 'string' is not assignable to type 'number'.", + "code": 2322, + "tags": [] + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/imported.ts b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/imported.ts new file mode 100644 index 000000000..bc8481bbb --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/imported.ts @@ -0,0 +1,5 @@ +/** + * + * @param cb callback because if the module resolution failed there will be a noImplicitAny error + */ +export function hi(cb: (num: number) => string) {} diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/input.svelte new file mode 100644 index 000000000..3b505cd2c --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub2.json new file mode 100644 index 000000000..310ef4967 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub2.json @@ -0,0 +1,7 @@ +{ + "include": [], + "compilerOptions": { + "composite": true + }, + "references": [{ "path": "./tsconfig_sub3.json" }] +} diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub3.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub3.json new file mode 100644 index 000000000..f96815577 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/nested/tsconfig_sub3.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "strict": true, + "paths": { + "hi2": ["./imported.ts"] + }, + "types": [] + } +} diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/expectedv2.json new file mode 100644 index 000000000..3d707cc47 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/expectedv2.json @@ -0,0 +1,10 @@ +[ + { + "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 5 } }, + "severity": 1, + "source": "ts", + "message": "Type 'string' is not assignable to type 'number'.", + "code": 2322, + "tags": [] + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/imported.ts b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/imported.ts new file mode 100644 index 000000000..bc8481bbb --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/imported.ts @@ -0,0 +1,5 @@ +/** + * + * @param cb callback because if the module resolution failed there will be a noImplicitAny error + */ +export function hi(cb: (num: number) => string) {} diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/input.svelte new file mode 100644 index 000000000..5e19507ef --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/tsconfig_sub.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/tsconfig_sub.json new file mode 100644 index 000000000..384062fc0 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/paths/tsconfig_sub.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "composite": true, + "strict": true, + "paths": { + "hi": ["./imported.ts"] + }, + "types": [] + } +} diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/tsconfig.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/tsconfig.json new file mode 100644 index 000000000..59b56bf8f --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/project-reference/tsconfig.json @@ -0,0 +1,11 @@ +{ + "include": [], + "references": [ + { + "path": "./paths/tsconfig_sub.json" + }, + { + "path": "./nested/tsconfig_sub2.json" + } + ] +} diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/tsconfig.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/tsconfig.json index f8add4155..da66f1d47 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/tsconfig.json +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/tsconfig.json @@ -1,11 +1,13 @@ { "compilerOptions": { "strict": true, + "allowJs": true, + "target": "ESNext", /** This is actually not needed, but makes the tests faster because TS does not look up other types. */ "types": ["svelte"] }, - "exclude": ["./svelte-native/**/*", "./node16/**/*"] + "exclude": ["./svelte-native/**/*", "./node16/**/*", "project-reference/**/*"] } diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/tsconfig.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/tsconfig.json new file mode 100644 index 000000000..f493a66ae --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "allowJs": true, + "target": "ESNext", + "strict": true, + /** + This is actually not needed, but makes the tests faster + because TS does not look up other types. + */ + "types": ["svelte"] + } +} diff --git a/packages/language-server/test/plugins/typescript/module-loader.test.ts b/packages/language-server/test/plugins/typescript/module-loader.test.ts index d08e42682..559eeb447 100644 --- a/packages/language-server/test/plugins/typescript/module-loader.test.ts +++ b/packages/language-server/test/plugins/typescript/module-loader.test.ts @@ -21,8 +21,11 @@ describe('createSvelteModuleLoader', () => { const moduleCacheMock = { getPackageJsonInfoCache: () => ({}) }; + const moduleResolutionHost = { ...ts.sys }; - const svelteSys = 'svelteSys'; + const svelteSys = { + ...svS.createSvelteSys(ts.sys) + }; sinon.stub(svS, 'createSvelteSys').returns(svelteSys); const compilerOptions: ts.CompilerOptions = { strict: true, paths: { '/@/*': [] } }; @@ -34,7 +37,8 @@ describe('createSvelteModuleLoader', () => { ...ts, createModuleResolutionCache: () => moduleCacheMock, resolveModuleName: resolveStub - } + }, + () => moduleResolutionHost ); return { @@ -43,7 +47,8 @@ describe('createSvelteModuleLoader', () => { resolveStub, compilerOptions, moduleResolver, - svelteSys + svelteSys, + moduleResolutionHost }; } @@ -51,132 +56,18 @@ describe('createSvelteModuleLoader', () => { return stub.getCall(stub.getCalls().length - 1); } - it('uses tsSys for normal files', async () => { - const resolvedModule: ts.ResolvedModuleFull = { - extension: ts.Extension.Ts, - resolvedFileName: 'filename.ts' - }; - const { resolveStub, moduleResolver, compilerOptions, moduleCacheMock } = - setup(resolvedModule); - const result = moduleResolver.resolveModuleNames( - ['./normal.ts'], - 'C:/somerepo/somefile.svelte', - undefined, - undefined, - undefined as any - ); - - assert.deepStrictEqual(result, [resolvedModule]); - assert.deepStrictEqual(lastCall(resolveStub).args, [ - './normal.ts', - 'C:/somerepo/somefile.svelte', - compilerOptions, - ts.sys, - moduleCacheMock, - undefined, - undefined - ]); - }); - - it('uses tsSys for normal files part of TS aliases', async () => { - const resolvedModule: ts.ResolvedModuleFull = { - extension: ts.Extension.Ts, - resolvedFileName: 'filename.ts' - }; - const { resolveStub, moduleResolver, compilerOptions, moduleCacheMock } = - setup(resolvedModule); - const result = moduleResolver.resolveModuleNames( - ['/@/normal'], - 'C:/somerepo/somefile.svelte', - undefined, - undefined, - undefined as any - ); - - assert.deepStrictEqual(result, [resolvedModule]); - assert.deepStrictEqual(lastCall(resolveStub).args, [ - '/@/normal', - 'C:/somerepo/somefile.svelte', - compilerOptions, - ts.sys, - moduleCacheMock, - undefined, - undefined - ]); - }); - - it('uses tsSys for svelte.d.ts files', async () => { - const resolvedModule: ts.ResolvedModuleFull = { - extension: ts.Extension.Dts, - resolvedFileName: 'filename.d.ts' - }; - const { resolveStub, moduleResolver, compilerOptions, moduleCacheMock } = - setup(resolvedModule); - const result = moduleResolver.resolveModuleNames( - ['./normal.ts'], - 'C:/somerepo/somefile.svelte', - undefined, - undefined, - undefined as any - ); - - assert.deepStrictEqual(result, [resolvedModule]); - assert.deepStrictEqual(lastCall(resolveStub).args, [ - './normal.ts', - 'C:/somerepo/somefile.svelte', - compilerOptions, - ts.sys, - moduleCacheMock, - undefined, - undefined - ]); - }); - - it('uses svelte module loader for virtual svelte files', async () => { + it('uses svelte script kind if resolved module is svelte file', async () => { const resolvedModule: ts.ResolvedModuleFull = { extension: ts.Extension.Ts, resolvedFileName: 'filename.svelte.ts' }; - const { resolveStub, svelteSys, moduleResolver, compilerOptions, getSvelteSnapshotStub } = - setup(resolvedModule); - resolveStub.onFirstCall().returns({ resolvedModule: undefined }); - const result = moduleResolver.resolveModuleNames( - ['./svelte.svelte'], - 'C:/somerepo/somefile.svelte', - undefined, - undefined, - undefined as any - ); + const { getSvelteSnapshotStub, moduleResolver, svelteSys } = setup(resolvedModule); - assert.deepStrictEqual(result, [ - { - extension: ts.Extension.Jsx, - resolvedFileName: 'filename.svelte', - isExternalLibraryImport: undefined - } - ]); - assert.deepStrictEqual(lastCall(resolveStub).args, [ - './svelte.svelte', - 'C:/somerepo/somefile.svelte', - compilerOptions, - svelteSys, - undefined, - undefined, - undefined - ]); - assert.deepStrictEqual(lastCall(getSvelteSnapshotStub).args, ['filename.svelte']); - }); + svelteSys.getRealSveltePathIfExists = (filename: string) => + filename === 'filename.svelte.ts' ? 'filename.svelte' : filename; - it('uses svelte module loader for virtual svelte files with TS path aliases', async () => { - const resolvedModule: ts.ResolvedModuleFull = { - extension: ts.Extension.Ts, - resolvedFileName: 'filename.svelte.ts' - }; - const { resolveStub, svelteSys, moduleResolver, compilerOptions, getSvelteSnapshotStub } = - setup(resolvedModule); - resolveStub.onFirstCall().returns({ resolvedModule: undefined }); const result = moduleResolver.resolveModuleNames( - ['/@/svelte.svelte'], + ['./normal.ts'], 'C:/somerepo/somefile.svelte', undefined, undefined, @@ -190,15 +81,6 @@ describe('createSvelteModuleLoader', () => { isExternalLibraryImport: undefined } ]); - assert.deepStrictEqual(lastCall(resolveStub).args, [ - '/@/svelte.svelte', - 'C:/somerepo/somefile.svelte', - compilerOptions, - svelteSys, - undefined, - undefined, - undefined - ]); assert.deepStrictEqual(lastCall(getSvelteSnapshotStub).args, ['filename.svelte']); }); diff --git a/packages/language-server/test/plugins/typescript/service.test.ts b/packages/language-server/test/plugins/typescript/service.test.ts index 02cac2d21..50c793c6f 100644 --- a/packages/language-server/test/plugins/typescript/service.test.ts +++ b/packages/language-server/test/plugins/typescript/service.test.ts @@ -1,16 +1,17 @@ import assert from 'assert'; import path from 'path'; +import sinon from 'sinon'; import ts from 'typescript'; +import { RelativePattern } from 'vscode-languageserver-protocol'; import { Document } from '../../../src/lib/documents'; import { GlobalSnapshotsManager } from '../../../src/plugins/typescript/SnapshotManager'; import { + LanguageServiceContainer, LanguageServiceDocumentContext, getService } from '../../../src/plugins/typescript/service'; -import { pathToUrl } from '../../../src/utils'; +import { normalizePath, pathToUrl } from '../../../src/utils'; import { createVirtualTsSystem, getRandomVirtualDirPath } from './test-utils'; -import sinon from 'sinon'; -import { RelativePattern } from 'vscode-languageserver-protocol'; describe('service', () => { const testDir = path.join(__dirname, 'testfiles'); @@ -33,7 +34,8 @@ describe('service', () => { onProjectReloaded: undefined, projectService: undefined, nonRecursiveWatchPattern: undefined, - watchDirectory: undefined + watchDirectory: undefined, + reportConfigError: undefined }; return { virtualSystem, lsDocumentContext, rootUris }; @@ -53,6 +55,11 @@ describe('service', () => { }) ); + virtualSystem.writeFile( + path.join(dirPath, 'random.svelte'), + '' + ); + const ls = await getService( path.join(dirPath, 'random.svelte'), rootUris, @@ -63,11 +70,117 @@ describe('service', () => { delete ls.compilerOptions.configFilePath; assert.deepStrictEqual(ls.compilerOptions, { - allowJs: true, allowNonTsExtensions: true, checkJs: true, strict: true, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Node10, + target: ts.ScriptTarget.ESNext + }); + }); + + it('errors if tsconfig matches no svelte files', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + virtualSystem.readDirectory = () => [path.join(dirPath, 'random.ts')]; + + virtualSystem.writeFile( + path.join(dirPath, 'tsconfig.json'), + JSON.stringify({ + include: ['**/*.ts'] + }) + ); + + virtualSystem.writeFile( + path.join(dirPath, 'random.svelte'), + '' + ); + + let called = false; + await getService(path.join(dirPath, 'random.svelte'), rootUris, { + ...lsDocumentContext, + reportConfigError: (message) => { + called = true; + assert.equal(message.uri, pathToUrl(path.join(dirPath, 'tsconfig.json'))); + } + }); + assert.ok(called); + }); + + it('do not errors if referenced tsconfig matches no svelte files', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + const tsPattern = '**/*.ts'; + const sveltePattern = '**/*.svelte'; + virtualSystem.readDirectory = (_path, _extensions, _excludes, include) => { + return include?.[0] === tsPattern + ? [path.join(dirPath, 'random.ts')] + : include?.[0] === sveltePattern + ? [path.join(dirPath, 'random.svelte')] + : []; + }; + + virtualSystem.writeFile( + path.join(dirPath, 'tsconfig.json'), + JSON.stringify({ + include: [], + references: [{ path: './tsconfig_node.json' }, { path: './tsconfig_web.json' }] + }) + ); + + virtualSystem.writeFile( + path.join(dirPath, 'tsconfig_node.json'), + JSON.stringify({ + include: [tsPattern] + }) + ); + + virtualSystem.writeFile( + path.join(dirPath, 'tsconfig_web.json'), + JSON.stringify({ + include: [sveltePattern] + }) + ); + + virtualSystem.writeFile( + path.join(dirPath, 'random.svelte'), + '' + ); + + let called = false; + const lsContainer = await getService(path.join(dirPath, 'random.svelte'), rootUris, { + ...lsDocumentContext, + reportConfigError: () => { + called = true; + } + }); + + assert.equal( + normalizePath(path.join(dirPath, 'tsconfig_web.json')), + lsContainer.tsconfigPath + ); + assert.equal(called, false, 'expected not to call reportConfigError'); + }); + + it('can loads default tsconfig', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { lsDocumentContext, rootUris } = setup(); + + const ls = await getService( + path.join(dirPath, 'random.svelte'), + rootUris, + lsDocumentContext + ); + + assert.deepStrictEqual(ls.compilerOptions, { + allowJs: true, + allowSyntheticDefaultImports: true, + allowNonTsExtensions: true, + configFilePath: undefined, declaration: false, + maxNodeModuleJsDepth: 2, module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Node10, noEmit: true, @@ -93,6 +206,11 @@ describe('service', () => { }) ); + virtualSystem.writeFile( + path.join(dirPath, 'random.svelte'), + '' + ); + const ls = await getService( path.join(dirPath, 'random.svelte'), rootUris, @@ -125,21 +243,24 @@ describe('service', () => { function createReloadTester( docContext: LanguageServiceDocumentContext, - testAfterReload: () => Promise + testAfterReload: (reloadingConfigs: string[]) => Promise ) { let _resolve: () => void; - const reloadPromise = new Promise((resolve) => { + let _reject: (e: unknown) => void; + const reloadPromise = new Promise((resolve, reject) => { _resolve = resolve; + _reject = reject; }); return { docContextWithReload: { ...docContext, - async onProjectReloaded() { + async onProjectReloaded(reloadingConfigs: string[]) { try { - await testAfterReload(); - } finally { + await testAfterReload(reloadingConfigs); _resolve(); + } catch (e) { + _reject(e); } } }, @@ -161,6 +282,11 @@ describe('service', () => { }) ); + virtualSystem.writeFile( + path.join(dirPath, 'random.svelte'), + '' + ); + const { reloadPromise, docContextWithReload } = createReloadTester( { ...lsDocumentContext, watchTsConfig: true }, testAfterReload @@ -189,6 +315,8 @@ describe('service', () => { true, 'expected to reload compilerOptions' ); + + return true; } }); @@ -197,7 +325,7 @@ describe('service', () => { const { virtualSystem, lsDocumentContext, rootUris } = setup(); const tsconfigPath = path.join(dirPath, 'tsconfig.json'); const extend = './.svelte-kit/tsconfig.json'; - const extendedConfigPathFull = path.resolve(tsconfigPath, extend); + const extendedConfigPathFull = path.resolve(path.dirname(tsconfigPath), extend); virtualSystem.writeFile( tsconfigPath, @@ -206,6 +334,11 @@ describe('service', () => { }) ); + virtualSystem.writeFile( + path.join(dirPath, 'random.svelte'), + '' + ); + const { reloadPromise, docContextWithReload } = createReloadTester( { ...lsDocumentContext, watchTsConfig: true }, testAfterReload @@ -234,23 +367,78 @@ describe('service', () => { true, 'expected to reload compilerOptions' ); + return true; } }); - it('can open client file that do not exist in fs', async () => { + it('can watch project reference tsconfig', async () => { const dirPath = getRandomVirtualDirPath(testDir); const { virtualSystem, lsDocumentContext, rootUris } = setup(); + const tsconfigPath = path.join(dirPath, 'tsconfig.json'); + const referenced = './tsconfig_node.json'; + const referencedConfigPathFull = path.resolve(path.dirname(tsconfigPath), referenced); virtualSystem.writeFile( - path.join(dirPath, 'tsconfig.json'), + tsconfigPath, + JSON.stringify({ + references: [{ path: referenced }], + include: [] + }) + ); + + virtualSystem.writeFile( + referencedConfigPathFull, JSON.stringify({ compilerOptions: { - checkJs: true, strict: true + }, + files: ['random.ts'] + }) + ); + + const { reloadPromise, docContextWithReload } = createReloadTester( + { ...lsDocumentContext, watchTsConfig: true }, + testAfterReload + ); + + const tsFilePath = path.join(dirPath, 'random.ts'); + virtualSystem.writeFile(tsFilePath, 'const a: number = null;'); + + const ls = await getService(tsFilePath, rootUris, docContextWithReload); + assert.deepStrictEqual(getSemanticDiagnosticsMessages(ls, tsFilePath), [ + "Type 'null' is not assignable to type 'number'." + ]); + + virtualSystem.writeFile( + referencedConfigPathFull, + JSON.stringify({ + compilerOptions: { + strict: false } }) ); + await reloadPromise; + + async function testAfterReload(reloadingConfigs: string[]) { + if (!reloadingConfigs.includes(referencedConfigPathFull)) { + return false; + } + const newLs = await getService(tsFilePath, rootUris, { + ...lsDocumentContext, + watchTsConfig: true + }); + + assert.deepStrictEqual(getSemanticDiagnosticsMessages(newLs, tsFilePath), []); + return true; + } + }); + + it('can open client file that do not exist in fs', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { lsDocumentContext, rootUris } = setup(); + + // don't need tsconfig because files doesn't exist in fs goes to a service with default config const ls = await getService( path.join(dirPath, 'random.svelte'), rootUris, @@ -266,6 +454,60 @@ describe('service', () => { }); }); + it('resolve module with source project reference redirect', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + const package1 = path.join(dirPath, 'package1'); + + virtualSystem.writeFile( + path.join(package1, 'tsconfig.json'), + JSON.stringify({ + references: [{ path: '../package2' }], + files: ['index.ts'] + }) + ); + + const package2 = path.join(dirPath, 'package2'); + virtualSystem.writeFile( + path.join(package2, 'tsconfig.json'), + JSON.stringify({ + compilerOptions: { + composite: true, + strict: true + }, + files: ['index.ts'] + }) + ); + + const importing = path.join(package1, 'index.ts'); + virtualSystem.writeFile(importing, 'import { hi } from "package2"; hi((a) => `${a}`);'); + + const imported = path.join(package2, 'index.ts'); + virtualSystem.writeFile(imported, 'export function hi(cb: (num: number) => string) {}'); + + const package2Link = normalizePath(path.join(package1, 'node_modules', 'package2')); + virtualSystem.realpath = (p) => { + if (normalizePath(p).startsWith(package2Link)) { + const sub = p.substring(package2Link.length); + return path.join(package2) + sub; + } + + return p; + }; + + const fileExists = virtualSystem.fileExists; + virtualSystem.fileExists = (p) => { + const realPath = virtualSystem.realpath!(p); + + return fileExists(realPath); + }; + + const ls = await getService(importing, rootUris, lsDocumentContext); + + assert.deepStrictEqual(getSemanticDiagnosticsMessages(ls, importing), []); + }); + it('skip directory watching if directory is root', async () => { const dirPath = getRandomVirtualDirPath(path.join(testDir, 'Test')); const { virtualSystem, lsDocumentContext } = setup(); @@ -322,4 +564,11 @@ describe('service', () => { sinon.assert.calledWith(watchDirectory.firstCall, []); }); + + function getSemanticDiagnosticsMessages(ls: LanguageServiceContainer, filePath: string) { + return ls + .getService() + .getSemanticDiagnostics(filePath) + .map((d) => d.messageText); + } }); diff --git a/packages/language-server/test/plugins/typescript/test-utils.ts b/packages/language-server/test/plugins/typescript/test-utils.ts index c77970256..f615f847f 100644 --- a/packages/language-server/test/plugins/typescript/test-utils.ts +++ b/packages/language-server/test/plugins/typescript/test-utils.ts @@ -6,8 +6,14 @@ import { DocumentManager, Document } from '../../../src/lib/documents'; import { FileMap } from '../../../src/lib/documents/fileCollection'; import { LSConfigManager } from '../../../src/ls-config'; import { LSAndTSDocResolver } from '../../../src/plugins'; -import { createGetCanonicalFileName, normalizePath, pathToUrl } from '../../../src/utils'; +import { + createGetCanonicalFileName, + normalizePath, + pathToUrl, + urlToPath +} from '../../../src/utils'; import { VERSION } from 'svelte/compiler'; +import { findTsConfigPath } from '../../../src/plugins/typescript/utils'; const isSvelte5Plus = Number(VERSION.split('.')[0]) >= 5; @@ -100,7 +106,7 @@ export function createVirtualTsSystem(currentDirectory: string): ts.System { ); } - const normalizedPath = normalizePath(toAbsolute(path)); + const normalizedPath = getCanonicalFileName(normalizePath(toAbsolute(path))); return Array.from(virtualFs.keys()).filter((fileName) => fileName.startsWith(normalizedPath) ); @@ -110,6 +116,11 @@ export function createVirtualTsSystem(currentDirectory: string): ts.System { return virtualSystem; function triggerWatch(normalizedPath: string, kind: ts.FileWatcherEventKind) { + // if watcher is not set yet. don't trigger it + if (!watchers.has(normalizedPath)) { + return; + } + let timeoutsOfPath = watchTimeout.get(normalizedPath); if (!timeoutsOfPath) { @@ -273,37 +284,56 @@ export async function createJsonSnapshotFormatter(dir: string) { }); } -export function serviceWarmup(suite: Mocha.Suite, testDir: string, rootUri = pathToUrl(testDir)) { +export function serviceWarmup( + suite: Mocha.Suite, + testDir: string, + rootUri = pathToUrl(testDir), + tsconfigPath: string | undefined = undefined +) { const defaultTimeout = suite.timeout(); // allow to set a higher timeout for slow machines from cli flag const warmupTimeout = Math.max(defaultTimeout, 5_000); suite.timeout(warmupTimeout); - before(async () => { + before(() => warmup(tsconfigPath)); + + suite.timeout(defaultTimeout); + + async function warmup(configFilePath: string | undefined = undefined) { const start = Date.now(); console.log('Warming up language service...'); const docManager = new DocumentManager( (textDocument) => new Document(textDocument.uri, textDocument.text) ); + const lsAndTsDocResolver = new LSAndTSDocResolver( docManager, [rootUri], new LSConfigManager() ); - const filePath = join(testDir, 'DoesNotMater.svelte'); - const document = docManager.openClientDocument({ - uri: pathToUrl(filePath), - text: ts.sys.readFile(filePath) || '' - }); + configFilePath ??= findTsConfigPath( + join(testDir, 'DoesNotMater.svelte'), + [rootUri], + ts.sys.fileExists, + createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames) + ); - await lsAndTsDocResolver.getLSAndTSDoc(document); + const ls = await lsAndTsDocResolver.getTSServiceByConfigPath( + configFilePath, + configFilePath ? dirname(configFilePath) : urlToPath(rootUri)! + ); + ls.getService(); - console.log(`Service warming up done in ${Date.now() - start}ms`); - }); + const projectReferences = ls.getResolvedProjectReferences(); - suite.timeout(defaultTimeout); + if (projectReferences.length) { + await Promise.all(projectReferences.map((ref) => warmup(ref.configFilePath))); + } + + console.log(`Service warming up done in ${Date.now() - start}ms`); + } } export function recursiveServiceWarmup( diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json index 89dd24236..8cd68407b 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json @@ -1,5 +1,7 @@ { "compilerOptions": { + "allowJs": true, + "target": "ESNext", "strict": true, /** This is actually not needed, but makes the tests faster diff --git a/packages/language-server/test/plugins/typescript/testfiles/tsconfig.json b/packages/language-server/test/plugins/typescript/testfiles/tsconfig.json index 63ec98505..03afba4d9 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/tsconfig.json +++ b/packages/language-server/test/plugins/typescript/testfiles/tsconfig.json @@ -1,5 +1,7 @@ { "compilerOptions": { + "allowJs": true, + "target": "ESNext", "strict": true, /** This is actually not needed, but makes the tests faster diff --git a/packages/svelte-check/src/writers.ts b/packages/svelte-check/src/writers.ts index 37c6823b8..af1fd89d8 100644 --- a/packages/svelte-check/src/writers.ts +++ b/packages/svelte-check/src/writers.ts @@ -57,14 +57,9 @@ export class HumanFriendlyWriter implements Writer { `${workspaceDir}${sep}${pc.green(filename)}:${line + 1}:${character + 1}\n` ); - // Show some context around diagnostic range - const codePrevLine = this.getLine(diagnostic.range.start.line - 1, text); - const codeLine = this.getCodeLine(diagnostic, text); - const codeNextLine = this.getLine(diagnostic.range.end.line + 1, text); - const code = codePrevLine + codeLine + codeNextLine; - let msg; if (this.isVerbose) { + const code = this.formatRelatedCode(diagnostic, text); msg = `${diagnostic.message} ${source}\n${pc.cyan(code)}`; } else { msg = `${diagnostic.message} ${source}`; @@ -80,6 +75,20 @@ export class HumanFriendlyWriter implements Writer { }); } + private formatRelatedCode(diagnostic: Diagnostic, text: string) { + if (!text) { + return ''; + } + + // Show some context around diagnostic range + const codePrevLine = this.getLine(diagnostic.range.start.line - 1, text); + const codeLine = this.getCodeLine(diagnostic, text); + const codeNextLine = this.getLine(diagnostic.range.end.line + 1, text); + const code = codePrevLine + codeLine + codeNextLine; + + return code; + } + private getCodeLine(diagnostic: Diagnostic, text: string) { const startOffset = offsetAt(diagnostic.range.start, text); const endOffset = offsetAt(diagnostic.range.end, text); diff --git a/packages/svelte-check/test/tsconfig.json b/packages/svelte-check/test/tsconfig.json index 15458e89c..86649d013 100644 --- a/packages/svelte-check/test/tsconfig.json +++ b/packages/svelte-check/test/tsconfig.json @@ -1,6 +1,6 @@ { - "extends": "@tsconfig/node12/tsconfig.json", "compilerOptions": { + "target": "ESNext", "moduleResolution": "node", "strict": true, "allowJs": true, From 89a15322c41c1a4ee939810740606138ae1fd55e Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 27 Aug 2024 12:49:14 +0200 Subject: [PATCH 03/25] fix: check error length --- packages/language-server/src/svelte-check.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index de8e54f56..652f2d7a7 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -208,7 +208,7 @@ export class SvelteCheck { }; }; - if (lsContainer.configErrors) { + if (lsContainer.configErrors.length > 0) { const grouped = groupBy( lsContainer.configErrors, (error) => error.file?.fileName ?? tsconfigPath From 53856cc7b031cf45020bb991b8726b714fbb5a8b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:05:11 +0200 Subject: [PATCH 04/25] breaking: vs code / language-server major (#2475) - use Svelte 4 as built-in version now - add exports map - bump vs code ls/client version to 9 - require Node 18 / VS Code 1.82 minimum - drive-by fix of __sveltets_2_ensureComponent (the previous version didn't work after all) --- package.json | 2 +- packages/language-server/package.json | 25 +- packages/language-server/src/importPackage.ts | 8 - .../features/CallHierarchyProvider.ts | 2 +- .../features/CodeActionsProvider.ts | 2 +- packages/svelte-check/package.json | 4 +- packages/svelte-vscode/package.json | 8 +- packages/svelte-vscode/src/html/autoClose.ts | 2 +- packages/svelte2tsx/package.json | 8 +- .../src/htmlxtojsx_v2/utils/node-utils.ts | 2 +- packages/svelte2tsx/svelte-shims-v4.d.ts | 4 +- packages/typescript-plugin/package.json | 6 +- .../src/language-service/call-hierarchy.ts | 2 +- pnpm-lock.yaml | 336 ++++++++++++------ 14 files changed, 267 insertions(+), 144 deletions(-) diff --git a/package.json b/package.json index 793a32730..6e450086d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "cross-env": "^7.0.2", - "prettier": "~3.2.5", + "prettier": "~3.3.3", "ts-node": "^10.0.0" }, "packageManager": "pnpm@9.3.0" diff --git a/packages/language-server/package.json b/packages/language-server/package.json index f79ba9ede..bae08937a 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,9 +1,13 @@ { "name": "svelte-language-server", - "version": "0.16.0", + "version": "0.17.0", "description": "A language server for Svelte", "main": "dist/src/index.js", "typings": "dist/src/index", + "exports": { + "./package.json": "./package.json", + ".": "./dist/src/index.js" + }, "scripts": { "test": "cross-env TS_NODE_TRANSPILE_ONLY=true mocha --require ts-node/register \"test/**/*.ts\" --exclude \"test/**/*.d.ts\"", "build": "tsc", @@ -31,14 +35,13 @@ }, "homepage": "https://github.com/sveltejs/language-tools#readme", "engines": { - "node": ">= 12.0.0" + "node": ">= 18.0.0" }, "devDependencies": { "@types/estree": "^0.0.42", "@types/lodash": "^4.14.116", "@types/mocha": "^9.1.0", - "@types/node": "^16.0.0", - "@types/prettier": "^2.2.3", + "@types/node": "^18.0.0", "@types/sinon": "^7.5.2", "cross-env": "^7.0.2", "mocha": "^9.2.0", @@ -46,23 +49,23 @@ "ts-node": "^10.0.0" }, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.25", "@vscode/emmet-helper": "2.8.4", "chokidar": "^3.4.1", "estree-walker": "^2.0.1", "fdir": "^6.2.0", "lodash": "^4.17.21", - "prettier": "~3.2.5", - "prettier-plugin-svelte": "^3.2.2", - "svelte": "^3.57.0", + "prettier": "~3.3.3", + "prettier-plugin-svelte": "^3.2.6", + "svelte": "^4.2.19", "svelte2tsx": "workspace:~", "typescript": "^5.5.2", "typescript-auto-import-cache": "^0.3.3", "vscode-css-languageservice": "~6.3.0", "vscode-html-languageservice": "~5.3.0", - "vscode-languageserver": "8.0.2", - "vscode-languageserver-protocol": "3.17.2", - "vscode-languageserver-types": "3.17.2", + "vscode-languageserver": "9.0.1", + "vscode-languageserver-protocol": "3.17.5", + "vscode-languageserver-types": "3.17.5", "vscode-uri": "~3.0.0" } } diff --git a/packages/language-server/src/importPackage.ts b/packages/language-server/src/importPackage.ts index 41e040762..7386a6c95 100644 --- a/packages/language-server/src/importPackage.ts +++ b/packages/language-server/src/importPackage.ts @@ -63,14 +63,6 @@ export function importSvelte(fromPath: string): typeof svelte { Logger.debug('Using Svelte v' + pkg.version.full, 'from', main); if (pkg.version.major === 4) { return dynamicRequire(main + '.cjs'); - } else if (pkg.version.major === 5) { - // TODO remove once Svelte 5 is released - // (we switched from compiler.cjs to compiler/index.js at some point) - try { - return dynamicRequire(main); - } catch (e) { - return dynamicRequire(main + '.cjs'); - } } else { return dynamicRequire(main); } diff --git a/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts b/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts index 3d60b40ac..2151038b9 100644 --- a/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CallHierarchyProvider.ts @@ -213,7 +213,7 @@ export class CallHierarchyProviderImpl implements CallHierarchyProvider { .provideCallHierarchyOutgoingCalls(filePath, offset) .concat( isComponentModulePosition - ? this.getOutgoingCallsForComponent(program, filePath) ?? [] + ? (this.getOutgoingCallsForComponent(program, filePath) ?? []) : [] ); diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 59def877c..c114fc4b6 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -1471,7 +1471,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { const position = inModuleScript ? originalRange.start - : this.fixPropsCodeActionRange(originalRange.start, document) ?? originalRange.start; + : (this.fixPropsCodeActionRange(originalRange.start, document) ?? originalRange.start); // fix the length of trailing indent const linesOfNewText = edit.newText.split('\n'); diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 361c2c33f..634a029f6 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -26,7 +26,7 @@ "node": ">= 18.0.0" }, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^3.4.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", @@ -52,7 +52,7 @@ "rollup": "3.7.5", "rollup-plugin-cleanup": "^3.2.0", "rollup-plugin-copy": "^3.4.0", - "svelte": "^3.57.0", + "svelte": "^4.2.19", "svelte-language-server": "workspace:*", "typescript": "^5.5.2", "vscode-languageserver": "8.0.2", diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index 27ed07e9e..a39165e17 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -37,7 +37,7 @@ "Formatters" ], "engines": { - "vscode": "^1.67.0" + "vscode": "^1.82.0" }, "activationEvents": [ "onLanguage:svelte", @@ -718,7 +718,7 @@ }, "devDependencies": { "@types/lodash": "^4.14.116", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "@types/vscode": "^1.67", "js-yaml": "^3.14.0", "tslib": "^2.4.0", @@ -729,7 +729,7 @@ "lodash": "^4.17.21", "svelte-language-server": "workspace:*", "typescript-svelte-plugin": "workspace:*", - "vscode-languageclient": "^8.0.0", - "vscode-languageserver-protocol": "3.17.2" + "vscode-languageclient": "^9.0.1", + "vscode-languageserver-protocol": "3.17.5" } } diff --git a/packages/svelte-vscode/src/html/autoClose.ts b/packages/svelte-vscode/src/html/autoClose.ts index 1dc71b2b3..281bb600f 100644 --- a/packages/svelte-vscode/src/html/autoClose.ts +++ b/packages/svelte-vscode/src/html/autoClose.ts @@ -26,7 +26,7 @@ export function activateTagClosing( updateEnabledState(); window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables); - let timeout: NodeJS.Timer | undefined = void 0; + let timeout: NodeJS.Timeout | undefined = void 0; function updateEnabledState() { isEnabled = false; diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 34d5ef7be..97f42d5ea 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -18,15 +18,15 @@ "module": "index.mjs", "types": "index.d.ts", "devDependencies": { - "@jridgewell/sourcemap-codec": "^1.4.14", - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.25", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-typescript": "^10.0.0", "@types/estree": "^0.0.42", "@types/mocha": "^9.1.0", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "@types/unist": "^2.0.3", "@types/vfile": "^3.0.2", "builtin-modules": "^3.3.0", @@ -37,7 +37,7 @@ "rollup": "3.7.5", "rollup-plugin-delete": "^2.0.0", "source-map-support": "^0.5.16", - "svelte": "~3.57.0", + "svelte": "~4.2.19", "tiny-glob": "^0.2.6", "tslib": "^2.4.0", "typescript": "^5.5.2" diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts index c523a53f2..d3c95164a 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts @@ -233,7 +233,7 @@ export function rangeWithTrailingPropertyAccess( * Get the end of the node, excluding the type annotation */ export function getEnd(node: any) { - return isTypescriptNode(node) ? node.expression.end : node.typeAnnotation?.start ?? node.end; + return isTypescriptNode(node) ? node.expression.end : (node.typeAnnotation?.start ?? node.end); } export function isTypescriptNode(node: any) { diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index e8e276255..87e365798 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -223,9 +223,9 @@ declare type ATypedSvelteComponent = { declare type ConstructorOfATypedSvelteComponent = new (args: {target: any, props?: any}) => ATypedSvelteComponent declare function __sveltets_2_ensureComponent< // @ts-ignore svelte.Component doesn't exist in Svelte 4 - T extends ConstructorOfATypedSvelteComponent | (0 extends (1 & import('svelte').Component) ? never : import('svelte').Component) | null | undefined + T extends ConstructorOfATypedSvelteComponent | (typeof import('svelte') extends { mount: any } ? import('svelte').Component : never) | null | undefined // @ts-ignore svelte.Component doesn't exist in Svelte 4 ->(type: T): NonNullable ? typeof import('svelte').SvelteComponent : T>; +>(type: T): NonNullable ? typeof import('svelte').SvelteComponent : T : T>; declare function __sveltets_2_ensureArray | Iterable>(array: T): T extends ArrayLike ? U[] : T extends Iterable ? Iterable : any[]; type __sveltets_2_PropsWithChildren = Props & diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index 9b17e9196..6928186a4 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -18,12 +18,12 @@ "author": "The Svelte Community", "license": "MIT", "devDependencies": { - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "typescript": "^5.5.2", - "svelte": "^3.57.0" + "svelte": "^4.2.19" }, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.14", + "@jridgewell/sourcemap-codec": "^1.5.0", "svelte2tsx": "workspace:~" } } diff --git a/packages/typescript-plugin/src/language-service/call-hierarchy.ts b/packages/typescript-plugin/src/language-service/call-hierarchy.ts index bfd7248eb..b16aafb0a 100644 --- a/packages/typescript-plugin/src/language-service/call-hierarchy.ts +++ b/packages/typescript-plugin/src/language-service/call-hierarchy.ts @@ -89,7 +89,7 @@ export function decorateCallHierarchy( return provideCallHierarchyOutgoingCalls(fileName, offset) .concat( program && sourceFile && isComponentModulePosition(fileName, position) - ? getOutgoingCallsForComponent(program, sourceFile) ?? [] + ? (getOutgoingCallsForComponent(program, sourceFile) ?? []) : [] ) .map((item): ts.CallHierarchyOutgoingCall | null => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ebe4e95d..d60a88176 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,17 +16,17 @@ importers: specifier: ^7.0.2 version: 7.0.3 prettier: - specifier: ~3.2.5 - version: 3.2.5 + specifier: ~3.3.3 + version: 3.3.3 ts-node: specifier: ^10.0.0 - version: 10.9.1(@types/node@16.18.32)(typescript@5.5.2) + version: 10.9.1(@types/node@18.19.46)(typescript@5.5.2) packages/language-server: dependencies: '@jridgewell/trace-mapping': - specifier: ^0.3.17 - version: 0.3.18 + specifier: ^0.3.25 + version: 0.3.25 '@vscode/emmet-helper': specifier: 2.8.4 version: 2.8.4 @@ -43,14 +43,14 @@ importers: specifier: ^4.17.21 version: 4.17.21 prettier: - specifier: ~3.2.5 - version: 3.2.5 + specifier: ~3.3.3 + version: 3.3.3 prettier-plugin-svelte: - specifier: ^3.2.2 - version: 3.2.2(prettier@3.2.5)(svelte@3.57.0) + specifier: ^3.2.6 + version: 3.2.6(prettier@3.3.3)(svelte@4.2.19) svelte: - specifier: ^3.57.0 - version: 3.57.0 + specifier: ^4.2.19 + version: 4.2.19 svelte2tsx: specifier: workspace:~ version: link:../svelte2tsx @@ -67,14 +67,14 @@ importers: specifier: ~5.3.0 version: 5.3.0 vscode-languageserver: - specifier: 8.0.2 - version: 8.0.2 + specifier: 9.0.1 + version: 9.0.1 vscode-languageserver-protocol: - specifier: 3.17.2 - version: 3.17.2 + specifier: 3.17.5 + version: 3.17.5 vscode-languageserver-types: - specifier: 3.17.2 - version: 3.17.2 + specifier: 3.17.5 + version: 3.17.5 vscode-uri: specifier: ~3.0.0 version: 3.0.8 @@ -89,11 +89,8 @@ importers: specifier: ^9.1.0 version: 9.1.1 '@types/node': - specifier: ^16.0.0 - version: 16.18.32 - '@types/prettier': - specifier: ^2.2.3 - version: 2.7.2 + specifier: ^18.0.0 + version: 18.19.46 '@types/sinon': specifier: ^7.5.2 version: 7.5.2 @@ -108,13 +105,13 @@ importers: version: 11.1.2 ts-node: specifier: ^10.0.0 - version: 10.9.1(@types/node@16.18.32)(typescript@5.5.2) + version: 10.9.1(@types/node@18.19.46)(typescript@5.5.2) packages/svelte-check: dependencies: '@jridgewell/trace-mapping': - specifier: ^0.3.17 - version: 0.3.18 + specifier: ^0.3.25 + version: 0.3.25 chokidar: specifier: ^3.4.1 version: 3.5.3 @@ -159,8 +156,8 @@ importers: specifier: ^3.4.0 version: 3.4.0 svelte: - specifier: ^3.57.0 - version: 3.57.0 + specifier: ^4.2.19 + version: 4.2.19 svelte-language-server: specifier: workspace:* version: link:../language-server @@ -192,18 +189,18 @@ importers: specifier: workspace:* version: link:../typescript-plugin vscode-languageclient: - specifier: ^8.0.0 - version: 8.1.0 + specifier: ^9.0.1 + version: 9.0.1 vscode-languageserver-protocol: - specifier: 3.17.2 - version: 3.17.2 + specifier: 3.17.5 + version: 3.17.5 devDependencies: '@types/lodash': specifier: ^4.14.116 version: 4.14.194 '@types/node': - specifier: ^16.0.0 - version: 16.18.32 + specifier: ^18.0.0 + version: 18.19.46 '@types/vscode': specifier: ^1.67 version: 1.78.0 @@ -230,11 +227,11 @@ importers: version: 3.1.2 devDependencies: '@jridgewell/sourcemap-codec': - specifier: ^1.4.14 - version: 1.4.15 + specifier: ^1.5.0 + version: 1.5.0 '@jridgewell/trace-mapping': - specifier: ^0.3.17 - version: 0.3.18 + specifier: ^0.3.25 + version: 0.3.25 '@rollup/plugin-commonjs': specifier: ^24.0.0 version: 24.1.0(rollup@3.7.5) @@ -254,8 +251,8 @@ importers: specifier: ^9.1.0 version: 9.1.1 '@types/node': - specifier: ^16.0.0 - version: 16.18.32 + specifier: ^18.0.0 + version: 18.19.46 '@types/unist': specifier: ^2.0.3 version: 2.0.6 @@ -287,8 +284,8 @@ importers: specifier: ^0.5.16 version: 0.5.21 svelte: - specifier: ~3.57.0 - version: 3.57.0 + specifier: ~4.2.19 + version: 4.2.19 tiny-glob: specifier: ^0.2.6 version: 0.2.9 @@ -302,24 +299,28 @@ importers: packages/typescript-plugin: dependencies: '@jridgewell/sourcemap-codec': - specifier: ^1.4.14 - version: 1.4.15 + specifier: ^1.5.0 + version: 1.5.0 svelte2tsx: specifier: workspace:~ version: link:../svelte2tsx devDependencies: '@types/node': - specifier: ^16.0.0 - version: 16.18.32 + specifier: ^18.0.0 + version: 18.19.46 svelte: - specifier: ^3.57.0 - version: 3.57.0 + specifier: ^4.2.19 + version: 4.2.19 typescript: specifier: ^5.5.2 version: 5.5.2 packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -333,22 +334,26 @@ packages: '@emmetio/scanner@1.0.4': resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} - '@jridgewell/resolve-uri@3.1.0': - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.1': resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.4.14': - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - '@jridgewell/trace-mapping@0.3.18': - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -480,11 +485,8 @@ packages: '@types/mri@1.1.1': resolution: {integrity: sha512-nJOuiTlsvmClSr3+a/trTSx4DTuY/VURsWGKSf/eeavh0LRMqdsK60ti0TlwM5iHiGOK3/Ibkxsbr7i9rzGreA==} - '@types/node@16.18.32': - resolution: {integrity: sha512-zpnXe4dEz6PrWz9u7dqyRoq9VxwCvoXRPy/ewhmMa1CgEyVmtL1NJPQ2MX+4pf97vetquVKkpiMx0MwI8pjNOw==} - - '@types/prettier@2.7.2': - resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} + '@types/node@18.19.46': + resolution: {integrity: sha512-vnRgMS7W6cKa1/0G3/DTtQYpVrZ8c0Xm6UkLaVFrb9jtcVC3okokW09Ki1Qdrj9ISokszD69nY4WDLRlvHlhAA==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -521,6 +523,11 @@ packages: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} @@ -559,10 +566,17 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -613,6 +627,9 @@ packages: cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -650,6 +667,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + debug@4.3.3: resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} engines: {node: '>=6.0'} @@ -674,6 +695,10 @@ packages: resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} engines: {node: '>=8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -719,6 +744,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} @@ -879,6 +907,9 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -910,6 +941,9 @@ packages: just-extend@4.2.1: resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -938,9 +972,15 @@ packages: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1037,6 +1077,9 @@ packages: periscopic@2.0.3: resolution: {integrity: sha512-FuCZe61mWxQOJAQFEfmt9FjzebRlcpFz8sFPbyaCKtdusPkMEbA9ey0eARnRav5zAhmXznhaQkKGFAPn7X9NUw==} + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -1044,14 +1087,14 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - prettier-plugin-svelte@3.2.2: - resolution: {integrity: sha512-ZzzE/wMuf48/1+Lf2Ffko0uDa6pyCfgHV6+uAhtg2U0AAXGrhCSW88vEJNAkAxW5qyrFY1y1zZ4J8TgHrjW++Q==} + prettier-plugin-svelte@3.2.6: + resolution: {integrity: sha512-Y1XWLw7vXUQQZmgv1JAEiLcErqUniAF2wO7QJsw8BVMvpLET2dI5WpEIEJx1r11iHVdSMzQxivyfrH9On9t2IQ==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} engines: {node: '>=14'} hasBin: true @@ -1140,6 +1183,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -1182,9 +1229,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte@3.57.0: - resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==} - engines: {node: '>= 8'} + svelte@4.2.19: + resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} + engines: {node: '>=16'} tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} @@ -1222,6 +1269,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + unist-util-stringify-position@3.0.3: resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} @@ -1245,19 +1295,19 @@ packages: resolution: {integrity: sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==} engines: {node: '>=14.0.0'} - vscode-jsonrpc@8.1.0: - resolution: {integrity: sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} - vscode-languageclient@8.1.0: - resolution: {integrity: sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==} - engines: {vscode: ^1.67.0} + vscode-languageclient@9.0.1: + resolution: {integrity: sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==} + engines: {vscode: ^1.82.0} vscode-languageserver-protocol@3.17.2: resolution: {integrity: sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==} - vscode-languageserver-protocol@3.17.3: - resolution: {integrity: sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==} + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} vscode-languageserver-textdocument@1.0.11: resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} @@ -1265,9 +1315,6 @@ packages: vscode-languageserver-types@3.17.2: resolution: {integrity: sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==} - vscode-languageserver-types@3.17.3: - resolution: {integrity: sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==} - vscode-languageserver-types@3.17.5: resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} @@ -1275,6 +1322,10 @@ packages: resolution: {integrity: sha512-bpEt2ggPxKzsAOZlXmCJ50bV7VrxwCS5BI4+egUmure/oI/t4OlFzi/YNtVvY24A2UDOZAgwFGgnZPwqSJubkA==} hasBin: true + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + vscode-nls@5.2.0: resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} @@ -1338,6 +1389,11 @@ packages: snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -1352,23 +1408,29 @@ snapshots: '@emmetio/scanner@1.0.4': {} - '@jridgewell/resolve-uri@3.1.0': {} + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.1': {} - '@jridgewell/sourcemap-codec@1.4.14': {} + '@jridgewell/set-array@1.2.1': {} '@jridgewell/sourcemap-codec@1.4.15': {} - '@jridgewell/trace-mapping@0.3.18': + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -1476,12 +1538,12 @@ snapshots: '@types/fs-extra@8.1.2': dependencies: - '@types/node': 16.18.32 + '@types/node': 18.19.46 '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 16.18.32 + '@types/node': 18.19.46 '@types/lodash@4.14.194': {} @@ -1491,9 +1553,9 @@ snapshots: '@types/mri@1.1.1': {} - '@types/node@16.18.32': {} - - '@types/prettier@2.7.2': {} + '@types/node@18.19.46': + dependencies: + undici-types: 5.26.5 '@types/resolve@1.20.2': {} @@ -1511,7 +1573,7 @@ snapshots: '@types/vfile@3.0.2': dependencies: - '@types/node': 16.18.32 + '@types/node': 18.19.46 '@types/unist': 2.0.6 '@types/vfile-message': 2.0.0 @@ -1524,7 +1586,7 @@ snapshots: emmet: 2.4.4 jsonc-parser: 2.3.1 vscode-languageserver-textdocument: 1.0.11 - vscode-languageserver-types: 3.17.2 + vscode-languageserver-types: 3.17.5 vscode-nls: 5.2.0 vscode-uri: 2.1.2 @@ -1532,6 +1594,8 @@ snapshots: acorn-walk@8.2.0: {} + acorn@8.12.1: {} + acorn@8.8.2: {} aggregate-error@3.1.0: @@ -1564,8 +1628,14 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-union@2.1.0: {} + axobject-query@4.1.0: {} + balanced-match@1.0.2: {} binary-extensions@2.2.0: {} @@ -1622,6 +1692,14 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + code-red@1.0.4: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.1 + acorn: 8.12.1 + estree-walker: 3.0.3 + periscopic: 3.1.0 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -1654,6 +1732,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.0 + debug@4.3.3(supports-color@8.1.1): dependencies: ms: 2.1.2 @@ -1677,6 +1760,8 @@ snapshots: rimraf: 3.0.2 slash: 3.0.0 + dequal@2.0.3: {} + diff@4.0.2: {} diff@5.0.0: {} @@ -1706,6 +1791,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.1 + fast-glob@3.2.12: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1863,6 +1952,10 @@ snapshots: dependencies: '@types/estree': 0.0.42 + is-reference@3.0.2: + dependencies: + '@types/estree': 0.0.42 + is-unicode-supported@0.1.0: {} isarray@0.0.1: {} @@ -1892,6 +1985,8 @@ snapshots: just-extend@4.2.1: {} + locate-character@3.0.0: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -1919,10 +2014,16 @@ snapshots: magic-string@0.27.0: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 make-error@1.3.6: {} + mdn-data@2.0.30: {} + merge2@1.4.1: {} micromatch@4.0.5: @@ -2034,16 +2135,22 @@ snapshots: estree-walker: 2.0.2 is-reference: 1.2.1 + periscopic@3.1.0: + dependencies: + '@types/estree': 1.0.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + picocolors@1.0.0: {} picomatch@2.3.1: {} - prettier-plugin-svelte@3.2.2(prettier@3.2.5)(svelte@3.57.0): + prettier-plugin-svelte@3.2.6(prettier@3.3.3)(svelte@4.2.19): dependencies: - prettier: 3.2.5 - svelte: 3.57.0 + prettier: 3.3.3 + svelte: 4.2.19 - prettier@3.2.5: {} + prettier@3.3.3: {} queue-microtask@1.2.3: {} @@ -2132,6 +2239,8 @@ snapshots: slash@3.0.0: {} + source-map-js@1.2.0: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -2169,7 +2278,22 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte@3.57.0: {} + svelte@4.2.19: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + '@types/estree': 1.0.1 + acorn: 8.12.1 + aria-query: 5.3.0 + axobject-query: 4.1.0 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.11 + periscopic: 3.1.0 tiny-glob@0.2.9: dependencies: @@ -2180,14 +2304,14 @@ snapshots: dependencies: is-number: 7.0.0 - ts-node@10.9.1(@types/node@16.18.32)(typescript@5.5.2): + ts-node@10.9.1(@types/node@18.19.46)(typescript@5.5.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 16.18.32 + '@types/node': 18.19.46 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 @@ -2208,6 +2332,8 @@ snapshots: typescript@5.5.2: {} + undici-types@5.26.5: {} + unist-util-stringify-position@3.0.3: dependencies: '@types/unist': 2.0.6 @@ -2237,36 +2363,38 @@ snapshots: vscode-jsonrpc@8.0.2: {} - vscode-jsonrpc@8.1.0: {} + vscode-jsonrpc@8.2.0: {} - vscode-languageclient@8.1.0: + vscode-languageclient@9.0.1: dependencies: minimatch: 5.1.6 semver: 7.5.1 - vscode-languageserver-protocol: 3.17.3 + vscode-languageserver-protocol: 3.17.5 vscode-languageserver-protocol@3.17.2: dependencies: vscode-jsonrpc: 8.0.2 vscode-languageserver-types: 3.17.2 - vscode-languageserver-protocol@3.17.3: + vscode-languageserver-protocol@3.17.5: dependencies: - vscode-jsonrpc: 8.1.0 - vscode-languageserver-types: 3.17.3 + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 vscode-languageserver-textdocument@1.0.11: {} vscode-languageserver-types@3.17.2: {} - vscode-languageserver-types@3.17.3: {} - vscode-languageserver-types@3.17.5: {} vscode-languageserver@8.0.2: dependencies: vscode-languageserver-protocol: 3.17.2 + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + vscode-nls@5.2.0: {} vscode-oniguruma@1.7.0: {} From 6a059635eaf8c6077c87b6eb0ca5b4393c72b7dc Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 27 Aug 2024 21:11:09 +0200 Subject: [PATCH 05/25] fix: add server bin to package exports --- packages/language-server/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/language-server/package.json b/packages/language-server/package.json index bae08937a..8ee68b6ed 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -6,7 +6,8 @@ "typings": "dist/src/index", "exports": { "./package.json": "./package.json", - ".": "./dist/src/index.js" + ".": "./dist/src/index.js", + "./bin/server.js": "./bin/server.js" }, "scripts": { "test": "cross-env TS_NODE_TRANSPILE_ONLY=true mocha --require ts-node/register \"test/**/*.ts\" --exclude \"test/**/*.d.ts\"", From ec5fef4b7d9ef44838522bb4e8661ed8998680c0 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:18:26 +0200 Subject: [PATCH 06/25] chore: bump magic-string (#2476) requires adjustments of mapping tests and a fix in the InlayHintProvider, because more things are mappable now --- .../typescript/features/InlayHintProvider.ts | 26 +- packages/svelte2tsx/package.json | 2 +- .../samples/action-directive/mappings.jsx | 30 +- .../samples/await-block/mappings.jsx | 62 ++- .../samples/component-props/mappings.jsx | 31 +- .../samples/each-block/mappings.jsx | 30 +- .../samples/element-attributes/mappings.jsx | 36 +- .../sourcemaps/samples/if-block/mappings.jsx | 12 +- .../samples/import-equal/mappings.jsx | 8 +- .../samples/large-sample-1/mappings.jsx | 371 +++++++++++++----- .../test/sourcemaps/samples/let/mappings.jsx | 8 +- .../samples/reactive-statements/mappings.jsx | 8 +- .../samples/reserved-variables/mappings.jsx | 22 +- .../sourcemaps/samples/slot-let/mappings.jsx | 14 +- .../sourcemaps/samples/slots/mappings.jsx | 18 +- pnpm-lock.yaml | 4 +- 16 files changed, 503 insertions(+), 179 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts b/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts index a11fb0f43..7db6f9f82 100644 --- a/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import ts, { ArrowFunction } from 'typescript'; import { CancellationToken } from 'vscode-languageserver'; import { Position, @@ -69,6 +69,7 @@ export class InlayHintProviderImpl implements InlayHintProvider { inlayHint.position !== renderFunctionReturnTypeLocation && !this.isSvelte2tsxFunctionHints(sourceFile, inlayHint) && !this.isGeneratedVariableTypeHint(sourceFile, inlayHint) && + !this.isGeneratedAsyncFunctionReturnType(sourceFile, inlayHint) && !this.isGeneratedFunctionReturnType(sourceFile, inlayHint) ) .map(async (inlayHint) => ({ @@ -254,6 +255,29 @@ export class InlayHintProviderImpl implements InlayHintProvider { ); } + /** `true` if is one of the `async () => {...}` functions svelte2tsx generates */ + private isGeneratedAsyncFunctionReturnType(sourceFile: ts.SourceFile, inlayHint: ts.InlayHint) { + if (inlayHint.kind !== ts.InlayHintKind.Type) { + return false; + } + + const expression = findContainingNode( + sourceFile, + { start: inlayHint.position, length: 0 }, + (node): node is ArrowFunction => ts.isArrowFunction(node) + ); + + if ( + !expression?.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) || + !expression.parent?.parent || + !ts.isBlock(expression.parent.parent) + ) { + return false; + } + + return this.getTypeAnnotationPosition(expression) === inlayHint.position; + } + private isGeneratedFunctionReturnType(sourceFile: ts.SourceFile, inlayHint: ts.InlayHint) { if (inlayHint.kind !== ts.InlayHintKind.Type) { return false; diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 97f42d5ea..258f5397e 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -31,7 +31,7 @@ "@types/vfile": "^3.0.2", "builtin-modules": "^3.3.0", "estree-walker": "^2.0.1", - "magic-string": "^0.27.0", + "magic-string": "^0.30.11", "mocha": "^9.2.0", "periscopic": "^2.0.2", "rollup": "3.7.5", diff --git a/packages/svelte2tsx/test/sourcemaps/samples/action-directive/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/action-directive/mappings.jsx index abaf91f05..9864b0bcc 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/action-directive/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/action-directive/mappings.jsx @@ -7,10 +7,12 @@ async•()•=>•{•{const•$$action_0•=•__sveltets_2_ensureAction(action < action/ element su ↲ #================================================================== # Order-breaking mappings ↲ [original] line 1 +↲ [original] line 1 (rest generated at line 4) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 4 + ↲ +↲ [original] line 1 (rest generated at line 3) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {const $$action_0 = __sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element')));{ svelteHTML.createElement("element", __sveltets_2_union($$action_0), { });}}{/** •{const•$$action_0•=•__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element')));{•svelteHTML.createElement("element",•__sveltets_2_union($$action_0),•{••});}}↲ [generated] line 5 @@ -30,27 +32,31 @@ async•()•=>•{•{const•$$action_0•=•__sveltets_2_ensureAction(action • ↲ [generated] subset / ↲ / ↲ -/>↲ [original] line 5 +/>↲ [original] line 5 (rest generated at line 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 6 + ↲ +/>↲ [original] line 5 (rest generated at line 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {const $$action_0 = __sveltets_2_ensureAction(action.nested.method(svelteHTML.mapElementTag('element'),(foo)));{ svelteHTML.createElement("element", __sveltets_2_union($$action_0), { });}}{/** ••{const•$$action_0•=•__sveltets_2_ensureAction(action.nested.method(svelteHTML.mapElementTag('element'),(foo)));{•svelteHTML.createElement("element",•__sveltets_2_union($$action_0),•{••});}}↲ [generated] line 7 <> action.nested.method= foo} element s{u ↲ #============================================== #=============================== # Order-breaking mappings ↲ -↲ [original] line 7 +↲ [original] line 7 (rest generated at line 8) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 8 + ↲ +↲ [original] line 7 (rest generated at line 7) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {const $$action_0 = __sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element'),($foo)));{ svelteHTML.createElement("element", __sveltets_2_union($$action_0), { });}}{/** •{const•$$action_0•=•__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element'),($foo)));{•svelteHTML.createElement("element",•__sveltets_2_union($$action_0),•{•••});}}↲ [generated] line 9 • ↲ [generated] subset / ↲ / ↲ -/>↲ [original] line 11 +/>↲ [original] line 11 (rest generated at line 10) •{const•$$action_0•=•__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element'),($foo)));{•svelteHTML.createElement("element",•__sveltets_2_union($$action_0),•{•••});}}↲ [generated] line 9 •{const•$$action_0•=•__sveltets_2_ensureAction( element",•__sveltets_2_union($$action_0),•{ });}} [generated] subset @@ -66,7 +72,9 @@ async•()•=>•{•{const•$$action_0•=•__sveltets_2_ensureAction(action ╚use:action={$foo}↲ [original] line 10 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 10 + ↲ +/>↲ [original] line 11 (rest generated at line 9) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {const $$action_0 = __sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element'),({ foo })));{ svelteHTML.createElement("element", __sveltets_2_union($$action_0), { });}}{/** •{const•$$action_0•=•__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element'),({•foo•})));{•svelteHTML.createElement("element",•__sveltets_2_union($$action_0),•{•••});}}↲ [generated] line 11 @@ -86,10 +94,12 @@ async•()•=>•{•{const•$$action_0•=•__sveltets_2_ensureAction(action • ↲ [generated] subset / ↲ / ↲ -/>↲ [original] line 15 +/>↲ [original] line 15 (rest generated at line 12) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 12 + ↲ +/>↲ [original] line 15 (rest generated at line 11) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {const $$action_0 = __sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element'),({ {/** •{const•$$action_0•=•__sveltets_2_ensureAction(action(svelteHTML.mapElementTag('element'),({•↲ [generated] line 13 diff --git a/packages/svelte2tsx/test/sourcemaps/samples/await-block/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/await-block/mappings.jsx index 35fff79b7..8e81cad42 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/await-block/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/await-block/mappings.jsx @@ -8,17 +8,23 @@ async•()•=>•{•••{•const•$$_value•=•await•(promise);{•con {t promise• value} ↲ #=========================== Order-breaking mappings { promise•t value}↲ -{#await•promise•then•value}↲ [original] line 1 +{#await•promise•then•value}↲ [original] line 1 (rest generated at line 4) async•()•=>•{•••{•const•$$_value•=•await•(promise);{•const•value•=•$$_value;•↲ [generated] line 3 •{•const•$$_value•=•await•( [generated] subset a a -{/await}↲ [original] line 3 (rest generated at line 5) +{/await}↲ [original] line 3 (rest generated at lines 5, 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", { "foo":value.bar,});} {/** -===# Originless mappings ••••••{•svelteHTML.createElement("element",•{•"foo":value.bar,});}↲ [generated] line 4 +•••• [generated] subset +↲ + ↲ +{#await•promise•then•value}↲ [original] line 1 (rest generated at line 3) + +••••••{•svelteHTML.createElement("element",•{•"foo":value.bar,});}↲ [generated] line 4 + ••{•svelteHTML.createElement("element",•{•"foo":value.bar,});}↲ [generated] subset <> element {f oo= value.bar} ↲ #============================ # Order-breaking mappings ↲ @@ -28,31 +34,39 @@ async•()•=>•{•••{•const•$$_value•=•await•(promise);{•con }}↲ [generated] line 5 { ↲ { ↲ -{/await}↲ [original] line 3 (rest generated at line 3) +{/await}↲ [original] line 3 (rest generated at lines 3, 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 6 + ↲ +{/await}↲ [original] line 3 (rest generated at lines 3, 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { {/** •••{•↲ [generated] line 7 • ↲ [generated] subset { ↲ { ↲ -{#await•promise}↲ [original] line 5 (rest generated at line 9) +{#await•promise}↲ [original] line 5 (rest generated at lines 8, 9) •••{•↲ [generated] line 7 • [generated] subset : -{:then}↲ [original] line 7 (rest generated at line 9) +{:then}↲ [original] line 7 (rest generated at lines 9, 10) •••{•↲ [generated] line 7 •{• [generated] subset a -{/await}↲ [original] line 9 (rest generated at line 11) +{/await}↲ [original] line 9 (rest generated at lines 11, 12) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", {});} {/** - # Originless mappings ╚•{•svelteHTML.createElement("element",•{});}↲ [generated] line 8 + ╚ [generated] subset + ↲ + ↲ + {#await•promise}↲ [original] line 5 (rest generated at lines 7, 9) + + ╚•{•svelteHTML.createElement("element",•{});}↲ [generated] line 8 + •{•svelteHTML.createElement("element",•{});}↲ [generated] subset < element / ↲ ↲ [original] line 6 @@ -62,17 +76,23 @@ await•(promise);↲ [generated] line 9 promise); [generated] subset promise} promise} -{#await•promise}↲ [original] line 5 (rest generated at line 7) +{#await•promise}↲ [original] line 5 (rest generated at lines 7, 8) await•(promise);↲ [generated] line 9 await•( ↲ [generated] subset { ↲ { ↲ -{:then}↲ [original] line 7 (rest generated at line 7) +{:then}↲ [original] line 7 (rest generated at lines 7, 10) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", {});} {/** - # Originless mappings ╚•{•svelteHTML.createElement("element",•{});}↲ [generated] line 10 + ╚ [generated] subset + ↲ + ↲ + {:then}↲ [original] line 7 (rest generated at lines 7, 9) + + ╚•{•svelteHTML.createElement("element",•{});}↲ [generated] line 10 + •{•svelteHTML.createElement("element",•{});}↲ [generated] subset < element / ↲ ↲ [original] line 8 @@ -81,17 +101,19 @@ await•( ↲ [generated] subset }↲ [generated] line 11 {↲ { ↲ -{/await}↲ [original] line 9 (rest generated at line 7) +{/await}↲ [original] line 9 (rest generated at lines 7, 12) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 12 + ↲ +{/await}↲ [original] line 9 (rest generated at lines 7, 11) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { {/** ••{•↲ [generated] line 13 • ↲ [generated] subset { ↲ { ↲ -{#await•$promise}↲ [original] line 11 (rest generated at line 15) +{#await•$promise}↲ [original] line 11 (rest generated at lines 14, 15) ••{•↲ [generated] line 13 •{• [generated] subset @@ -100,8 +122,14 @@ await•( ↲ [generated] subset {/await} [original] line 13 (rest generated at line 15) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", {});} {/** - # Originless mappings ╚•{•svelteHTML.createElement("element",•{});}↲ [generated] line 14 + ╚ [generated] subset + ↲ + ↲ + {#await•$promise}↲ [original] line 11 (rest generated at lines 13, 15) + + ╚•{•svelteHTML.createElement("element",•{});}↲ [generated] line 14 + •{•svelteHTML.createElement("element",•{});}↲ [generated] subset < element / ↲ ↲ [original] line 12 @@ -111,7 +139,7 @@ await•($promise);}};↲ [generated] line 15 $promise);}};↲ [generated] subset $promise} $promise} -{#await•$promise}↲ [original] line 11 (rest generated at line 13) +{#await•$promise}↲ [original] line 11 (rest generated at lines 13, 14) await•($promise);}};↲ [generated] line 15 await•( [generated] subset diff --git a/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx index 86889cddc..e19b69c73 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx @@ -7,30 +7,37 @@ async•()•=>•{••{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureCom <> Component f oo• ↲ #====================================================== Order-breaking mappings ↲ -↲ [original] line 1 +↲ [original] line 1 (rest generated at line 4) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 4 + ↲ +↲ [original] line 1 (rest generated at line 3) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { "foo":`leet`,"bar":true,}});}{/** •{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{•"foo":`leet`,"bar":true,}});}↲ [generated] line 5 < Component "f oo= leet" b ar/ ↲ # Order-breaking mappings ↲ [original] line 3 +↲ [original] line 3 (rest generated at line 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - + ╚↲ [generated] line 6 + ↲ + ↲ + ↲ [original] line 3 (rest generated at line 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { ...bar,}});} {/** •{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{•...bar,}});}↲ [generated] line 7 < Component /...bar} ↲ # Order-breaking mappings ↲ [original] line 5 +↲ [original] line 5 (rest generated at line 8) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 8 + ↲ +↲ [original] line 5 (rest generated at line 7) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { ...bar,}});} {/** •{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{••...bar,}});}↲ [generated] line 9 @@ -49,10 +56,12 @@ async•()•=>•{••{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureCom • ↲ [generated] subset / ↲ / ↲ -/>↲ [original] line 9 +/>↲ [original] line 9 (rest generated at line 10) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 10 + ↲ +/>↲ [original] line 9 (rest generated at line 9) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { foo:bar,}});/*Ωignore_startΩ*/() => bar = __sveltets_2_any(null);/*Ωignore_endΩ*/}{/** •{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{•••foo:bar,}});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 11 @@ -72,10 +81,12 @@ async•()•=>•{••{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureCom • ↲ [generated] subset / ↲ / ↲ -/>↲ [original] line 13 +/>↲ [original] line 13 (rest generated at line 12) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 12 + ↲ +/>↲ [original] line 13 (rest generated at line 11) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); const $$_tnenopmoC0 = new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { }});bar = $$_tnenopmoC0;}};{/** •{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•const•$$_tnenopmoC0•=•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{••}});bar•=•$$_tnenopmoC0;}};↲ [generated] line 13 diff --git a/packages/svelte2tsx/test/sourcemaps/samples/each-block/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/each-block/mappings.jsx index 4ae205f2d..7e934b571 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/each-block/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/each-block/mappings.jsx @@ -19,10 +19,12 @@ async•()•=>•{••for(let•item•of•__sveltets_2_ensureArray(items)){ }↲ [generated] line 5 {↲ { ↲ -{/each}↲ [original] line 3 +{/each}↲ [original] line 3 (rest generated at line 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 6 + ↲ +{/each}↲ [original] line 3 (rest generated at line 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} for(let item of __sveltets_2_ensureArray(items)){let i = 1; {/** •••for(let•item•of•__sveltets_2_ensureArray(items)){let•i•=•1;↲ [generated] line 7 @@ -41,10 +43,12 @@ async•()•=>•{••for(let•item•of•__sveltets_2_ensureArray(items)){ }↲ [generated] line 9 {↲ { ↲ -{/each}↲ [original] line 7 +{/each}↲ [original] line 7 (rest generated at line 10) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 10 + ↲ +{/each}↲ [original] line 7 (rest generated at line 9) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} for(let { id, name, qty } of __sveltets_2_ensureArray(items)){let i = 1;id; {/** ••••for(let•{•id,•name,•qty•}•of•__sveltets_2_ensureArray(items)){let•i•=•1;id;↲ [generated] line 11 @@ -63,10 +67,12 @@ async•()•=>•{••for(let•item•of•__sveltets_2_ensureArray(items)){ }↲ [generated] line 13 {↲ { ↲ -{/each}↲ [original] line 11 +{/each}↲ [original] line 11 (rest generated at line 14) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 14 + ↲ +{/each}↲ [original] line 11 (rest generated at line 13) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} for(let { id, ...rest } of __sveltets_2_ensureArray(objects)){ {/** ••for(let•{•id,•...rest•}•of•__sveltets_2_ensureArray(objects)){↲ [generated] line 15 @@ -86,10 +92,12 @@ async•()•=>•{••for(let•item•of•__sveltets_2_ensureArray(items)){ }↲ [generated] line 17 {↲ { ↲ -{/each}↲ [original] line 15 +{/each}↲ [original] line 15 (rest generated at line 18) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 18 + ↲ +{/each}↲ [original] line 15 (rest generated at line 17) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} for(let [id, ...rest] of __sveltets_2_ensureArray(items)){ {/** ••for(let•[id,•...rest]•of•__sveltets_2_ensureArray(items)){↲ [generated] line 19 @@ -109,10 +117,12 @@ async•()•=>•{••for(let•item•of•__sveltets_2_ensureArray(items)){ }↲ [generated] line 21 {↲ { ↲ -{/each}↲ [original] line 19 +{/each}↲ [original] line 19 (rest generated at line 22) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 22 + ↲ +{/each}↲ [original] line 19 (rest generated at line 21) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} for(let todo of __sveltets_2_ensureArray(todos)){ {/** ••for(let•todo•of•__sveltets_2_ensureArray(todos)){↲ [generated] line 23 diff --git a/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx index 7ef16bb3f..b5a478a40 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx @@ -6,10 +6,12 @@ async () => { { svelteHTML.createElement("element", {"foo":true,});} async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});}↲ [generated] line 3 < element f oo/ ↲ ↲ [original] line 1 +↲ [original] line 1 (rest generated at line 4) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 4 + ↲ +↲ [original] line 1 (rest generated at line 3) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", { "foo":true,});} {/** ••{•svelteHTML.createElement("element",•{•"foo":true,});}↲ [generated] line 5 @@ -28,10 +30,12 @@ async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});} •{•svelteHTML.createElement(" ↲ [generated] subset > ↲ >↲ -/>↲ [original] line 5 +/>↲ [original] line 5 (rest generated at line 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 6 + ↲ +/>↲ [original] line 5 (rest generated at line 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", { "foo":`leet`,});} {/** •{•svelteHTML.createElement("element",•{•••"foo":`leet`,});}↲ [generated] line 7 @@ -50,10 +54,12 @@ async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});} •{•svelteHTML.createElement("element",•{•••"foo":`leet`,});}↲ [generated] line 7 ↲ [generated] subset ↲ -/>↲ [original] line 9 +/>↲ [original] line 9 (rest generated at line 8) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 8 + ↲ +/>↲ [original] line 9 (rest generated at line 7) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", { foo,"bar":true,});} {/** ••{•svelteHTML.createElement("element",•{•foo,"bar":true,});}↲ [generated] line 9 @@ -72,10 +78,12 @@ async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});} •{•svelteHTML.createElement(" ↲ [generated] subset > ↲ >↲ -/>↲ [original] line 13 +/>↲ [original] line 13 (rest generated at line 10) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 10 + ↲ +/>↲ [original] line 13 (rest generated at line 9) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", { foo,});} {/** •{•svelteHTML.createElement("element",•{••foo,});}↲ [generated] line 11 @@ -94,10 +102,12 @@ async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});} •{•svelteHTML.createElement("element",•{••foo,});}↲ [generated] line 11 ↲ [generated] subset ↲ -/>↲ [original] line 17 +/>↲ [original] line 17 (rest generated at line 12) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 12 + ↲ +/>↲ [original] line 17 (rest generated at line 11) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("element", { "bind:foo":foo,});/*Ωignore_startΩ*/() => foo = __sveltets_2_any(null);/*Ωignore_endΩ*/} {/** •{•svelteHTML.createElement("element",•{••"bind:foo":foo,});/*Ωignore_startΩ*/()•=>•foo•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 13 @@ -116,10 +126,12 @@ async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});} • ↲ [generated] subset / ↲ / ↲ -/>↲ [original] line 21 +/>↲ [original] line 21 (rest generated at line 14) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 14 + ↲ +/>↲ [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 diff --git a/packages/svelte2tsx/test/sourcemaps/samples/if-block/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/if-block/mappings.jsx index 080912ca8..f76cef7ba 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/if-block/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/if-block/mappings.jsx @@ -18,19 +18,23 @@ async•()•=>•{if(foo){↲ [generated] line 3 }↲ [generated] line 5 {↲ { ↲ -{/if}↲ [original] line 3 +{/if}↲ [original] line 3 (rest generated at line 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 6 + ↲ +{/if}↲ [original] line 3 (rest generated at line 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} if($foo){ { svelteHTML.createElement("element", {});}} {/** if($foo){•{•svelteHTML.createElement("element",•{});}}↲ [generated] line 7 { $foo} < element / {↲ { $foo}{/if}↲ [original] line 5 +{#if•$foo}{/if}↲ [original] line 5 (rest generated at line 8) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 8 + ↲ +{#if•$foo}{/if}↲ [original] line 5 (rest generated at line 7) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} if(foo){ {/** if(foo){↲ [generated] line 9 diff --git a/packages/svelte2tsx/test/sourcemaps/samples/import-equal/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/import-equal/mappings.jsx index e122d928f..e1548fcb1 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/import-equal/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/import-equal/mappings.jsx @@ -19,9 +19,13 @@ ; {/** ;↲ [generated] line 6 < - [original] line 4 + [original] line 4 (rest generated at line 7) +------------------------------------------------------------------------------------------------------------------------------------------------------ */} +async () => {}; {/** +async•()•=>•{};↲ [generated] line 7 +< + [original] line 4 (rest generated at line 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} -async () => {}; return { props: /** @type {Record} */ ({}), slots: {}, events: {} }} export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) { 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 dd6cc6ee5..44c3de9a8 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx @@ -23,7 +23,7 @@ ;;↲ [generated] line 15 ; [generated] subset < -↲ [original] line 14 (rest generated at line 119) +↲ [original] line 14 (rest generated at lines 119, 120) ;;↲ [generated] line 15 ;↲ [generated] subset @@ -251,69 +251,105 @@ s ; {/** ;↲ [generated] line 118 < -↲ [original] line 109 (rest generated at line 121) +↲ [original] line 109 (rest generated at lines 119, 121, 122) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} async () => { {/** -============# Originless mappings async•()•=>•{↲ [generated] line 119 - ↲ +async•()•=>•{ [generated] subset +< +↲ [original] line 109 (rest generated at lines 118, 121, 122) + +async•()•=>•{↲ [generated] line 119 + ↲ [generated] subset ↲ -↲ [original] line 14 (rest generated at line 15) +↲ [original] line 14 (rest generated at lines 15, 120) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 120 + ↲ +↲ [original] line 14 (rest generated at lines 15, 119) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** ↲ [generated] line 121 ↲ -↲ [original] line 109 (rest generated at line 118) +↲ [original] line 109 (rest generated at lines 118, 119, 122) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 122 + ↲ +↲ [original] line 109 (rest generated at lines 118, 119, 121) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** ↲ [generated] line 123 ↲ -↲ [original] line 259 +↲ [original] line 259 (rest generated at line 124) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 124 + ↲ +↲ [original] line 259 (rest generated at line 123) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("svelte:head", {}); {/** •{•svelteHTML.createElement("svelte:head",•{});↲ [generated] line 125 s ↲ s ↲ -↲ [original] line 261 +↲ [original] line 261 (rest generated at line 126) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("title", {});selected.section.title; selected.chapter.title; } {/** - # Originless mappings ╚•{•svelteHTML.createElement("title",•{});selected.section.title;••selected.chapter.title;••••}↲ [generated] line 126 + ╚ [generated] subset + ↲ + ↲ + ↲ [original] line 261 (rest generated at line 125) + + ╚•{•svelteHTML.createElement("title",•{});selected.section.title;••selected.chapter.title;••••}↲ [generated] line 126 + •{•svelteHTML.createElement("title",•{});selected.section.title;••selected.chapter.title;••••}↲ [generated] subset < title selected.section.title}• selected.chapter.title}• / ↲ {selected.section.title}•/•{selected.chapter.title}•••Svelte•Tutorial↲ [original] line 262 + ╚{selected.section.title}•/•{selected.chapter.title}•••Svelte•Tutorial↲ [original] line 262 (rest generated at lines 127, 128) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 127 + ↲ +╚{selected.section.title}•/•{selected.chapter.title}•••Svelte•Tutorial↲ [original] line 262 (rest generated at lines 126, 128) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("meta", { "name":`twitter:title`,"content":`Svelte tutorial`,});} {/** - # Originless mappings ╚•{•svelteHTML.createElement("meta",•{•••"name":`twitter:title`,"content":`Svelte•tutorial`,});}↲ [generated] line 128 + ╚ [generated] subset + ↲ + ↲ + ╚{selected.section.title}•/•{selected.chapter.title}•••Svelte•Tutorial↲ [original] line 262 (rest generated at lines 126, 127) + + ╚•{•svelteHTML.createElement("meta",•{•••"name":`twitter:title`,"content":`Svelte•tutorial`,});}↲ [generated] line 128 + •{•svelteHTML.createElement("meta",•{•••"name":`twitter:title`,"content":`Svelte•tutorial`,});}↲ [generated] subset < meta "•"n ame= twitter:title" c ontent= Svelte•tutorial" ↲ # Order-breaking mappings ↲ [original] line 264 + ╚↲ [original] line 264 (rest generated at line 129) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("meta", { "name":`twitter:description`,"content":`${selected.section.title} / ${selected.chapter.title}`,});} {/** - # Originless mappings ╚•{•svelteHTML.createElement("meta",•{•••"name":`twitter:description`,"content":`${selected.section.title}•/•${selected.chapter.title}`,});}↲ [generated] line 129 + ╚ [generated] subset + ↲ + ↲ + ╚↲ [original] line 264 (rest generated at line 128) + + ╚•{•svelteHTML.createElement("meta",•{•••"name":`twitter:description`,"content":`${selected.section.title}•/•${selected.chapter.title}`,});}↲ [generated] line 129 + •{•svelteHTML.createElement("meta",•{•••"name":`twitter:description`,"content":`${selected.section.title}•/•${selected.chapter.title}`,});}↲ [generated] subset < meta "•"n ame= twitter:description" c ontent= {selected.section.title}•/• {selected.chapter.title}" ↲ # Order-breaking mappings ↲ [original] line 265 + ╚↲ [original] line 265 (rest generated at line 130) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("meta", { "name":`Description`,"content":`${selected.section.title} / ${selected.chapter.title}`,});} {/** - # Originless mappings ╚•{•svelteHTML.createElement("meta",•{•••"name":`Description`,"content":`${selected.section.title}•/•${selected.chapter.title}`,});}↲ [generated] line 130 + ╚ [generated] subset + ↲ + ↲ + ╚↲ [original] line 265 (rest generated at line 129) + + ╚•{•svelteHTML.createElement("meta",•{•••"name":`Description`,"content":`${selected.section.title}•/•${selected.chapter.title}`,});}↲ [generated] line 130 + •{•svelteHTML.createElement("meta",•{•••"name":`Description`,"content":`${selected.section.title}•/•${selected.chapter.title}`,});}↲ [generated] subset < meta "•"n ame= Description" c ontent= {selected.section.title}•/• {selected.chapter.title}" ↲ # Order-breaking mappings ↲ [original] line 267 +↲ [original] line 267 (rest generated at line 132) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 132 + ↲ +↲ [original] line 267 (rest generated at line 131) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { 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} >↲ -↲ [original] line 269 +↲ [original] line 269 (rest generated at line 134) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 134 + ↲ +↲ [original] line 269 (rest generated at line 133) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("div", { "class":`tutorial-outer`,}); {/** •{•svelteHTML.createElement("div",•{•"class":`tutorial-outer`,});↲ [generated] line 135 < div "c lass= tutorial-outer" ↲ # Order-breaking mappings
↲ [original] line 271 +↲ [original] line 271 (rest generated at line 136) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("div", { "class":`viewport offset-${offset}`,}); {/** - # Originless mappings ╚•{•svelteHTML.createElement("div",•{•"class":`viewport•offset-${offset}`,});↲ [generated] line 136 + ╚ [generated] subset + ↲ + ↲ + ↲ [original] line 271 (rest generated at line 135) + + ╚•{•svelteHTML.createElement("div",•{•"class":`viewport•offset-${offset}`,});↲ [generated] line 136 + •{•svelteHTML.createElement("div",•{•"class":`viewport•offset-${offset}`,});↲ [generated] subset < div "c lass= viewport•offset- {offset}" ↲ # Order-breaking mappings
↲ [original] line 272 + ╚↲ [original] line 272 (rest generated at line 137) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("div", { "class":`tutorial-text`,}); {/** - =# Originless mappings ╚╚•{•svelteHTML.createElement("div",•{•"class":`tutorial-text`,});↲ [generated] line 137 + ╚╚ [generated] subset + ↲ + ↲ + ╚↲ [original] line 272 (rest generated at line 136) + + ╚╚•{•svelteHTML.createElement("div",•{•"class":`tutorial-text`,});↲ [generated] line 137 + •{•svelteHTML.createElement("div",•{•"class":`tutorial-text`,});↲ [generated] subset < div "c lass= tutorial-text" ↲ # Order-breaking mappings
↲ [original] line 273 + ╚╚↲ [original] line 273 (rest generated at line 138) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("div", { "class":`table-of-contents`,}); {/** - ==# Originless mappings ╚╚╚•{•svelteHTML.createElement("div",•{•"class":`table-of-contents`,});↲ [generated] line 138 + ╚╚╚ [generated] subset + ↲ + ↲ + ╚╚↲ [original] line 273 (rest generated at line 137) + + ╚╚╚•{•svelteHTML.createElement("div",•{•"class":`table-of-contents`,});↲ [generated] line 138 + •{•svelteHTML.createElement("div",•{•"class":`table-of-contents`,});↲ [generated] subset < div "c lass= table-of-contents" ↲ # Order-breaking mappings
↲ [original] line 274 + ╚╚╚↲ [original] line 274 (rest generated at line 139) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_stnetnoCfOelbaT4C = __sveltets_2_ensureComponent(TableOfContents); new $$_stnetnoCfOelbaT4C({ target: __sveltets_2_any(), props: { sections,slug,selected,}});}{/** - ===# Originless mappings ╚╚╚╚••{•const•$$_stnetnoCfOelbaT4C•=•__sveltets_2_ensureComponent(TableOfContents);•new•$$_stnetnoCfOelbaT4C({•target:•__sveltets_2_any(),•props:•{••sections,slug,selected,}});}↲ [generated] line 139 + ╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚↲ [original] line 274 (rest generated at line 138) + + ╚╚╚╚••{•const•$$_stnetnoCfOelbaT4C•=•__sveltets_2_ensureComponent(TableOfContents);•new•$$_stnetnoCfOelbaT4C({•target:•__sveltets_2_any(),•props:•{••sections,slug,selected,}});}↲ [generated] line 139 + ••{•const•$$_stnetnoCfOelbaT4C•=•__sveltets_2_ensureComponent(TableOfContents);•new•$$_stnetnoCfOelbaT4C({•target:•__sveltets_2_any(),•props:•{••sections,slug,selected,}});}↲ [generated] subset <> TableOfContents ••sections}slug}selected} ↲ #============================================================ # Order-breaking mappings ↲ - ╚╚╚╚↲ [original] line 275 + ╚╚╚╚↲ [original] line 275 (rest generated at line 140) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** - ==# Originless mappings - ╚╚╚•}↲ [generated] line 140 + ╚╚╚•}↲ [generated] line 140 + ╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚↲ [original] line 275 (rest generated at line 139) + + ╚╚╚•}↲ [generated] line 140 + •}↲ [generated] subset / ↲ / ↲ - ╚╚╚
↲ [original] line 276 + ╚╚╚
↲ [original] line 276 (rest generated at lines 141, 142) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 141 + ↲ +╚╚╚
↲ [original] line 276 (rest generated at lines 140, 142) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_div3 = svelteHTML.createElement("div", { "class":`chapter-markup`,});scrollable = $$_div3; {/** - ==# Originless mappings ╚╚╚•{•const•$$_div3•=•svelteHTML.createElement("div",•{••"class":`chapter-markup`,});scrollable•=•$$_div3;↲ [generated] line 142 + ╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚
↲ [original] line 276 (rest generated at lines 140, 141) + + ╚╚╚•{•const•$$_div3•=•svelteHTML.createElement("div",•{••"class":`chapter-markup`,});scrollable•=•$$_div3;↲ [generated] line 142 + •{•const•$$_div3•=•svelteHTML.createElement("div",•{••"class":`chapter-markup`,});scrollable•=•$$_div3;↲ [generated] subset < div "•c lass= chapter-markup" scrollable} ↲ # Order-breaking mappings
↲ [original] line 278 + ╚╚╚↲ [original] line 278 (rest generated at line 143) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} chapter.html; {/** - ===# Originless mappings - ╚╚╚╚•chapter.html;↲ [generated] line 143 + ╚╚╚╚•chapter.html;↲ [generated] line 143 + ╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚↲ [original] line 278 (rest generated at line 142) + + ╚╚╚╚•chapter.html;↲ [generated] line 143 + •chapter.html;↲ [generated] subset {chapter.html}↲ { chapter.html}↲ - ╚╚╚╚{@html•chapter.html}↲ [original] line 279 + ╚╚╚╚{@html•chapter.html}↲ [original] line 279 (rest generated at lines 144, 145) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 144 + ↲ +╚╚╚╚{@html•chapter.html}↲ [original] line 279 (rest generated at lines 143, 145) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("div", { "class":`controls`,}); {/** - ===# Originless mappings ╚╚╚╚•{•svelteHTML.createElement("div",•{•"class":`controls`,});↲ [generated] line 145 + ╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚{@html•chapter.html}↲ [original] line 279 (rest generated at lines 143, 144) + + ╚╚╚╚•{•svelteHTML.createElement("div",•{•"class":`controls`,});↲ [generated] line 145 + •{•svelteHTML.createElement("div",•{•"class":`controls`,});↲ [generated] subset < div "c lass= controls" ↲ # Order-breaking mappings
↲ [original] line 281 + ╚╚╚╚↲ [original] line 281 (rest generated at line 146) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} if(chapter.app_b){ {/** - ====# Originless mappings - ╚╚╚╚╚if(chapter.app_b){↲ [generated] line 146 + ╚╚╚╚╚if(chapter.app_b){↲ [generated] line 146 + ╚╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚↲ [original] line 281 (rest generated at line 145) + + ╚╚╚╚╚if(chapter.app_b){↲ [generated] line 146 + if(chapter.app_b){↲ [generated] subset { chapter.app_b} ↲ { chapter.app_b}↲ - ╚╚╚╚╚{#if•chapter.app_b}↲ [original] line 282 + ╚╚╚╚╚{#if•chapter.app_b}↲ [original] line 282 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** ╚╚╚╚╚╚↲ [generated] line 147 @@ -429,42 +527,68 @@ s ↲ ╚╚╚╚╚╚↲ [generated] line 147 ↲ [generated] subset ↲ - ╚╚╚╚╚╚╚matches•the•expected•end•result•-->↲ [original] line 284 + ╚╚╚╚╚╚╚matches•the•expected•end•result•-->↲ [original] line 284 (rest generated at line 148) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("button", { "class":`show`,"on:click":() => completed ? reset() : complete(),}); {/** - =====# Originless mappings ╚╚╚╚╚╚••{•svelteHTML.createElement("button",•{•••"class":`show`,"on:click":()•=>•completed•?•reset()•:•complete(),});↲ [generated] line 148 + ╚╚╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚╚╚╚matches•the•expected•end•result•-->↲ [original] line 284 (rest generated at line 147) + + ╚╚╚╚╚╚••{•svelteHTML.createElement("button",•{•••"class":`show`,"on:click":()•=>•completed•?•reset()•:•complete(),});↲ [generated] line 148 + ••{•svelteHTML.createElement("button",•{•••"class":`show`,"on:click":()•=>•completed•?•reset()•:•complete(),});↲ [generated] subset <> button "•"c lass= show" c lick =()•=>•completed•?•reset()•:•complete()} ↲ #============================ # Order-breaking mappings ↲ [original] line 287 + ╚╚╚╚╚╚↲ [original] line 287 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** ╚╚╚╚╚}↲ [generated] line 151 ╚╚╚╚╚{↲ ╚╚╚╚╚{ ↲ - ╚╚╚╚╚{/if}↲ [original] line 288 + ╚╚╚╚╚{/if}↲ [original] line 288 (rest generated at lines 152, 153) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 152 + ↲ +╚╚╚╚╚{/if}↲ [original] line 288 (rest generated at lines 151, 153) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} if(selected.next){ {/** - ====# Originless mappings ╚╚╚╚╚if(selected.next){↲ [generated] line 153 + ╚╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚╚{/if}↲ [original] line 288 (rest generated at lines 151, 152) + + ╚╚╚╚╚if(selected.next){↲ [generated] line 153 + if(selected.next){↲ [generated] subset { selected.next} ↲ { selected.next}↲ ╚╚╚╚╚{#if•selected.next}↲ [original] line 290 @@ -480,68 +604,119 @@ s ↲ ╚╚╚╚╚}↲ [generated] line 155 ╚╚╚╚╚{↲ ╚╚╚╚╚{ ↲ - ╚╚╚╚╚{/if}↲ [original] line 292 + ╚╚╚╚╚{/if}↲ [original] line 292 (rest generated at line 156) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** - ===# Originless mappings ╚╚╚╚•}↲ [generated] line 156 + ╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚╚{/if}↲ [original] line 292 (rest generated at line 155) + + ╚╚╚╚•}↲ [generated] line 156 + •}↲ [generated] subset / ↲ / ↲ - ╚╚╚╚
↲ [original] line 293 + ╚╚╚╚
↲ [original] line 293 (rest generated at lines 157, 158) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 157 + ↲ +╚╚╚╚↲ [original] line 293 (rest generated at lines 156, 158) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("div", { "class":`improve-chapter`,}); {/** - ===# Originless mappings ╚╚╚╚•{•svelteHTML.createElement("div",•{•"class":`improve-chapter`,});↲ [generated] line 158 + ╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚↲ [original] line 293 (rest generated at lines 156, 157) + + ╚╚╚╚•{•svelteHTML.createElement("div",•{•"class":`improve-chapter`,});↲ [generated] line 158 + •{•svelteHTML.createElement("div",•{•"class":`improve-chapter`,});↲ [generated] subset < div "c lass= improve-chapter" ↲ # Order-breaking mappings
↲ [original] line 295 + ╚╚╚╚↲ [original] line 295 (rest generated at line 159) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("a", { "class":`no-underline`,"href":improve_link,}); } {/** - ====# Originless mappings ╚╚╚╚╚•{•svelteHTML.createElement("a",•{•••"class":`no-underline`,"href":improve_link,});•••}↲ [generated] line 159 + ╚╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚↲ [original] line 295 (rest generated at line 158) + + ╚╚╚╚╚•{•svelteHTML.createElement("a",•{•••"class":`no-underline`,"href":improve_link,});•••}↲ [generated] line 159 + •{•svelteHTML.createElement("a",•{•••"class":`no-underline`,"href":improve_link,});•••}↲ [generated] subset < a "•{c lass= no-underline" h ref= improve_link} E / ↲ # Order-breaking mappings Edit•this•chapter↲ [original] line 296 + ╚╚╚╚╚Edit•this•chapter↲ [original] line 296 (rest generated at line 160) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** - ===# Originless mappings - ╚╚╚╚•}↲ [generated] line 160 + ╚╚╚╚•}↲ [generated] line 160 + ╚╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚╚Edit•this•chapter↲ [original] line 296 (rest generated at line 159) + + ╚╚╚╚•}↲ [generated] line 160 + •}↲ [generated] subset / ↲ / ↲ - ╚╚╚╚
↲ [original] line 297 + ╚╚╚╚↲ [original] line 297 (rest generated at line 161) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** - ==# Originless mappings - ╚╚╚•}↲ [generated] line 161 + ╚╚╚•}↲ [generated] line 161 + ╚╚╚ [generated] subset + ↲ + ↲ + ╚╚╚╚↲ [original] line 297 (rest generated at line 160) + + ╚╚╚•}↲ [generated] line 161 + •}↲ [generated] subset / ↲ / ↲ - ╚╚╚↲ [original] line 298 + ╚╚╚↲ [original] line 298 (rest generated at line 162) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** - =# Originless mappings - ╚╚•}↲ [generated] line 162 + ╚╚•}↲ [generated] line 162 + ╚╚ [generated] subset + ↲ + ↲ + ╚╚╚↲ [original] line 298 (rest generated at line 161) + + ╚╚•}↲ [generated] line 162 + •}↲ [generated] subset / ↲ / ↲ - ╚╚↲ [original] line 299 + ╚╚↲ [original] line 299 (rest generated at lines 163, 164) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 163 + ↲ +╚╚↲ [original] line 299 (rest generated at lines 162, 164) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { svelteHTML.createElement("div", { "class":`tutorial-repl`,}); {/** - =# Originless mappings ╚╚•{•svelteHTML.createElement("div",•{•"class":`tutorial-repl`,});↲ [generated] line 164 + ╚╚ [generated] subset + ↲ + ↲ + ╚╚↲ [original] line 299 (rest generated at lines 162, 163) + + ╚╚•{•svelteHTML.createElement("div",•{•"class":`tutorial-repl`,});↲ [generated] line 164 + •{•svelteHTML.createElement("div",•{•"class":`tutorial-repl`,});↲ [generated] subset < div "c lass= tutorial-repl" ↲ # Order-breaking mappings
↲ [original] line 301 + ╚╚↲ [original] line 301 (rest generated at line 165) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_lpeR3C = __sveltets_2_ensureComponent(Repl); const $$_lpeR3 = new $$_lpeR3C({ target: __sveltets_2_any(), props: { "workersUrl":`workers`,svelteUrl,rollupUrl,"orientation":mobile ? 'columns' : 'rows',"fixed":mobile,"injectedJS":mapbox_setup,"relaxed":true,}});repl = $$_lpeR3;$$_lpeR3.$on("change", handle_change);}{/** - ==# Originless mappings + ╚╚╚•{•const•$$_lpeR3C•=•__sveltets_2_ensureComponent(Repl);•const•$$_lpeR3•=•new•$$_lpeR3C({•target:•__sveltets_2_any(),•props:•{•••••••••••••••"workersUrl":`workers`,svelteUrl,rollupUrl,"orientation":mobile•?•'columns'•:•'rows',"fixed":mobile,"injectedJS":mapbox_setup,"relaxed":true,}});repl•=•$$_lpeR3;$$_lpeR3.$on("change",•handle_change);}↲ [generated] line 165 + ╚╚╚ [generated] subset + ↲ + ↲ + ╚╚↲ [original] line 301 (rest generated at line 164) + ╚╚╚•{•const•$$_lpeR3C•=•__sveltets_2_ensureComponent(Repl);•const•$$_lpeR3•=•new•$$_lpeR3C({•target:•__sveltets_2_any(),•props:•{•••••••••••••••"workersUrl":`workers`,svelteUrl,rollupUrl,"orientation":mobile•?•'columns'•:•'rows',"fixed":mobile,"injectedJS":mapbox_setup,"relaxed":true,}});repl•=•$$_lpeR3;$$_lpeR3.$on("change",•handle_change);}↲ [generated] line 165 •{•const•$$_lpeR3C•=•__sveltets_2_ensureComponent(Repl);•const•$$_lpeR3•=•new•$$_lpeR3C({•target:•__sveltets_2_any(),•props:•{ [generated] subset < Repl @@ -614,28 +789,48 @@ s ↲ • ↲ [generated] subset ╚ ↲ ╚ ↲ - ╚╚╚/>↲ [original] line 312 + ╚╚╚/>↲ [original] line 312 (rest generated at line 166) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** - =# Originless mappings ╚╚•}↲ [generated] line 166 + ╚╚ [generated] subset + ↲ + ↲ + ╚╚╚/>↲ [original] line 312 (rest generated at line 165) + + ╚╚•}↲ [generated] line 166 + •}↲ [generated] subset / ↲ / ↲ - ╚╚
↲ [original] line 313 + ╚╚↲ [original] line 313 (rest generated at line 167) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} } {/** - # Originless mappings - ╚•}↲ [generated] line 167 + ╚•}↲ [generated] line 167 + ╚ [generated] subset + ↲ + ↲ + ╚╚↲ [original] line 313 (rest generated at line 166) + + ╚•}↲ [generated] line 167 + •}↲ [generated] subset / ↲ / ↲ - ╚↲ [original] line 314 + ╚↲ [original] line 314 (rest generated at lines 168, 169) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 168 + ↲ +╚↲ [original] line 314 (rest generated at lines 167, 169) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} if(mobile){ {/** - # Originless mappings ╚if(mobile){↲ [generated] line 169 + ╚ [generated] subset + ↲ + ↲ + ╚↲ [original] line 314 (rest generated at lines 167, 168) + + ╚if(mobile){↲ [generated] line 169 + if(mobile){↲ [generated] subset { mobile} ↲ { mobile}↲ ╚{#if•mobile}↲ [original] line 316 diff --git a/packages/svelte2tsx/test/sourcemaps/samples/let/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/let/mappings.jsx index 265203757..36af25336 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/let/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/let/mappings.jsx @@ -19,9 +19,13 @@ ; {/** ;↲ [generated] line 5 < - [original] line 3 + [original] line 3 (rest generated at line 6) +------------------------------------------------------------------------------------------------------------------------------------------------------ */} +async () => {}; {/** +async•()•=>•{};↲ [generated] line 6 +< + [original] line 3 (rest generated at line 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} -async () => {}; return { props: /** @type {Record} */ ({}), slots: {}, events: {} }} export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) { diff --git a/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx index 8ef14e465..348d67a39 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx @@ -41,9 +41,13 @@ ; {/** ;↲ [generated] line 9 < - [original] line 7 + [original] line 7 (rest generated at line 10) +------------------------------------------------------------------------------------------------------------------------------------------------------ */} +async () => {}; {/** +async•()•=>•{};↲ [generated] line 10 +< + [original] line 7 (rest generated at line 9) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} -async () => {}; return { props: {prop: prop}, slots: {}, events: {} }} export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) { diff --git a/packages/svelte2tsx/test/sourcemaps/samples/reserved-variables/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/reserved-variables/mappings.jsx index 9ac4a75c6..9ff7d1c21 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/reserved-variables/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/reserved-variables/mappings.jsx @@ -23,17 +23,18 @@ ; {/** ;↲ [generated] line 8 < -↲ [original] line 6 (rest generated at line 9) +↲ [original] line 6 (rest generated at lines 9, 10) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} async () => { {/** -============# Originless mappings async•()•=>•{↲ [generated] line 9 - ↲ - ↲ -↲ [original] line 6 (rest generated at line 8) +< ↲ +< ↲ +↲ [original] line 6 (rest generated at lines 8, 10) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 10 + ↲ +↲ [original] line 6 (rest generated at lines 8, 9) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} if($$slots.foo){ {/** if($$slots.foo){↲ [generated] line 11 @@ -45,10 +46,9 @@ if($$slots.foo){↲ [generated] line 11 ╚$$restProps.bar;↲ [generated] line 12 ╚$$restProps.bar}↲ ╚ $$restProps.bar}↲ - ╚{$$restProps.bar}↲ [original] line 9 + ╚{$$restProps.bar}↲ [original] line 9 (rest generated at line 13) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { ...$$props,}});} {/** - # Originless mappings ╚•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{••...$$props,}});}↲ [generated] line 13 • ...$$props,}});} [generated] subset ╚ ...$$props} @@ -61,6 +61,12 @@ if($$slots.foo){↲ [generated] line 11 ╚ ↲ ╚/>↲ [original] line 12 + ╚•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{••...$$props,}});}↲ [generated] line 13 + ╚ [generated] subset + ↲ + ↲ + ╚{$$restProps.bar}↲ [original] line 9 (rest generated at line 12) + ╚•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{••...$$props,}});}↲ [generated] line 13 •{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{ [generated] subset < Component diff --git a/packages/svelte2tsx/test/sourcemaps/samples/slot-let/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/slot-let/mappings.jsx index 2612247aa..a07171311 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/slot-let/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/slot-let/mappings.jsx @@ -7,14 +7,20 @@ async•()•=>•{•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureCompon < Component el{•l hi•hi2=hi_2}hi3 ↲ # Order-breaking mappings ↲ [original] line 1 +↲ [original] line 1 (rest generated at line 4) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} hi;hi_2;hi3; {/** -===# Originless mappings -••••hi;hi_2;hi3;↲ [generated] line 4 +••••hi;hi_2;hi3;↲ [generated] line 4 +•••• [generated] subset +↲ + ↲ +↲ [original] line 1 (rest generated at line 3) + +••••hi;hi_2;hi3;↲ [generated] line 4 + hi;hi_2;hi3;↲ [generated] subset hi}hi_2}hi3}↲ hi} hi_2} hi3}↲ -••••{hi}{hi_2}{hi3}↲ [original] line 2 +••••{hi}{hi_2}{hi3}↲ [original] line 2 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} }Component}}; {/** •}Component}};↲ [generated] line 5 diff --git a/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx index 86e0654ab..8353a45dd 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/slots/mappings.jsx @@ -7,30 +7,36 @@ async () => { { __sveltets_createSlot("default", {});} async•()•=>•{•{•__sveltets_createSlot("default",•{});}↲ [generated] line 4 < / ↲ < / ↲ -↲ [original] line 1 +↲ [original] line 1 (rest generated at line 5) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 5 + ↲ +↲ [original] line 1 (rest generated at line 4) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { __sveltets_createSlot("foo", { }); } {/** •{•__sveltets_createSlot("foo",•{•});••}↲ [generated] line 6 < f oo " an f/ ↲ #==# Order-breaking mappings < na foo" f / ↲ -fallback↲ [original] line 3 +fallback↲ [original] line 3 (rest generated at line 7) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 7 + ↲ +fallback↲ [original] line 3 (rest generated at line 6) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { __sveltets_createSlot("bar", { "foo":foo,baz,"leet":true,}); } {/** •{•__sveltets_createSlot("bar",•{••••"foo":foo,baz,"leet":true,});••}↲ [generated] line 8 < b ar " {•••f oo= foo}baz}l eet f/ ↲ #== # Order-breaking mappings < foo={foo}• bar"• baz}•leet f / ↲ -fallback↲ [original] line 5 +fallback↲ [original] line 5 (rest generated at line 9) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} {/** - +↲ [generated] line 9 + ↲ +fallback↲ [original] line 5 (rest generated at line 8) ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { __sveltets_createSlot("bar", { "foo":foo,baz,"leet":true,}); {/** •{•__sveltets_createSlot("bar",•{•••••"foo":foo,baz,"leet":true,});↲ [generated] line 10 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d60a88176..f1bc6d8f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -266,8 +266,8 @@ importers: specifier: ^2.0.1 version: 2.0.2 magic-string: - specifier: ^0.27.0 - version: 0.27.0 + specifier: ^0.30.11 + version: 0.30.11 mocha: specifier: ^9.2.0 version: 9.2.2 From 8c080cf36286984439b468e089e6e4eaacd4230e Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:21:32 +0200 Subject: [PATCH 07/25] feat: better type checking for bindings in Svelte 5 (#2477) #1392 This adds enhanced type checking for bindings in Svelte 5: We're not only passing the variable in, we're also assigning the value of the component property back to the variable. That way, we can catch type errors like the child binding having a wider type as the input we give it. It's done for Svelte 5 only because it's a) easier there b) doesn't break as much code (people who upgrade can then also upgrade the types, or use type assertions in the template, which is only possible in Svelte 5). There's one limitation: Because of how the transformation works, we cannot infer generics. In other words, we will not catch type errors for bindings that rely on a generic type. The combination of generics + bindings is probably rare enough that this is okay, and we can revisit this later to try to find a way to make it work, if it comes up. Also note that this does not affect DOM element bindings like , this is only about component bindings. --- .../typescript/features/RenameProvider.ts | 10 +++++- .../bindings-two-way-check/Legacy.svelte | 7 ++++ .../bindings-two-way-check/Runes.svelte | 6 ++++ .../RunesGeneric.svelte | 6 ++++ .../expected_svelte_5.json | 36 +++++++++++++++++++ .../bindings-two-way-check/input.svelte | 19 ++++++++++ packages/svelte2tsx/repl/index.svelte | 12 +++---- .../src/htmlxtojsx_v2/nodes/Binding.ts | 6 ++++ .../htmlxtojsx_v2/nodes/InlineComponent.ts | 1 + packages/svelte2tsx/svelte-shims-v4.d.ts | 13 +++++++ .../samples/binding-bare/expected-svelte5.js | 4 +-- .../samples/binding/expected-svelte5.js | 8 ++--- .../editing-binding/expected-svelte5.js | 2 +- 13 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts index 3b9caf86e..8340478c3 100644 --- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts @@ -463,8 +463,16 @@ export class RenameProviderImpl implements RenameProvider { const mappedLocations = await Promise.all( renameLocations.map(async (loc) => { const snapshot = await snapshots.retrieve(loc.fileName); + const text = snapshot.getFullText(); + const end = loc.textSpan.start + loc.textSpan.length; - if (!isTextSpanInGeneratedCode(snapshot.getFullText(), loc.textSpan)) { + if ( + !(snapshot instanceof SvelteDocumentSnapshot) || + (!isTextSpanInGeneratedCode(text, loc.textSpan) && + // prevent generated code for bindings from being renamed + // (it's not inside a generate comment because diagnostics should show up) + text.slice(end + 3, end + 27) !== '__sveltets_binding_value') + ) { return { ...loc, range: this.mapRangeToOriginal(snapshot, loc.textSpan), diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte new file mode 100644 index 000000000..18db75ea2 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte @@ -0,0 +1,7 @@ + + +{legacy1} +{legacy2} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte new file mode 100644 index 000000000..299be4d66 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte @@ -0,0 +1,6 @@ + + +{runes1} +{runes2} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte new file mode 100644 index 000000000..6accfc4b3 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte @@ -0,0 +1,6 @@ + + +{foo} +{bar} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json new file mode 100644 index 000000000..6b1d57c83 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json @@ -0,0 +1,36 @@ +[ + { + "code": 2322, + "message": "Type 'string | number' is not assignable to type 'string'.\n Type 'number' is not assignable to type 'string'.", + "range": { + "end": { + "character": 28, + "line": 17 + }, + "start": { + "character": 28, + "line": 17 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2322, + "message": "Type 'string | number' is not assignable to type 'string'.\n Type 'number' is not assignable to type 'string'.", + "range": { + "end": { + "character": 45, + "line": 18 + }, + "start": { + "character": 45, + "line": 18 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte new file mode 100644 index 000000000..49020f784 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/packages/svelte2tsx/repl/index.svelte b/packages/svelte2tsx/repl/index.svelte index 49517cc72..bf8061fb0 100644 --- a/packages/svelte2tsx/repl/index.svelte +++ b/packages/svelte2tsx/repl/index.svelte @@ -1,7 +1,7 @@ - - -{#if value} - -{/if} + + \ No newline at end of file diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts index 5bb17cca7..555269b61 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts @@ -131,6 +131,12 @@ export function handleBinding( if (isSvelte5Plus && element instanceof InlineComponent) { // To check if property is actually bindable element.appendToStartEnd([`${element.name}.$$bindings = '${attr.name}';`]); + // To check if the binding is also assigned to the variable (only works when there's no assertion, we can't transform that) + if (!isTypescriptNode(attr.expression)) { + element.appendToStartEnd([ + `${expressionStr} = __sveltets_binding_value(${element.originalName}, '${attr.name}');` + ]); + } } if (element instanceof Element) { diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts index b1bab058d..7459f227e 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts @@ -39,6 +39,7 @@ export class InlineComponent { private startTagEnd: number; private isSelfclosing: boolean; public child?: any; + public originalName = this.node.name; // Add const $$xxx = ... only if the variable name is actually used // in order to prevent "$$xxx is defined but never used" TS hints diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index 87e365798..5227cb041 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -254,3 +254,16 @@ declare function __sveltets_2_isomorphic_component< declare function __sveltets_2_isomorphic_component_slots< Props extends Record, Events extends Record, Slots extends Record, Exports extends Record, Bindings extends string >(klass: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }): __sveltets_2_IsomorphicComponent<__sveltets_2_PropsWithChildren, Events, Slots, Exports, Bindings>; + +type __sveltets_NonUndefined = T extends undefined ? never : T; + +declare function __sveltets_binding_value< + // @ts-ignore this is only used for Svelte 5, which knows about the Component type + Comp extends typeof import('svelte').Component, + Key extends string +>(comp: Comp, key: Key): Key extends keyof import('svelte').ComponentProps ? + // bail on unknown because it hints at a generic type which we can't properly resolve here + // remove undefined because optional properties have it, and would result in false positives + unknown extends import('svelte').ComponentProps[Key] ? any : __sveltets_NonUndefined[Key]> : any; +// Overload to ensure typings that only use old SvelteComponent class or something invalid are gracefully handled +declare function __sveltets_binding_value(comp: any, key: string): any diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js index bb934f658..43291dbae 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js @@ -1,5 +1,5 @@ { svelteHTML.createElement("input", { "type":`text`,"bind:value":value,});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/} { svelteHTML.createElement("input", { "type":`checkbox`,"bind:checked":checked,});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value,}});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`checkbox`,checked,}});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'checked';} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value,}});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';value = __sveltets_binding_value(Input, 'value');} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`checkbox`,checked,}});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'checked';checked = __sveltets_binding_value(Input, 'checked');} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js index 6c881fec9..355227003 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js @@ -2,7 +2,7 @@ { 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); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value'; Input} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value'); Input} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js index 7bb207fea..31e8b2747 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js @@ -1,3 +1,3 @@ { svelteHTML.createElement("input", { });obj = __sveltets_2_any(null);} { svelteHTML.createElement("input", { "bind:value":obj.,});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';obj = __sveltets_binding_value(Input, 'value');} \ No newline at end of file From a3a7a91d0f5a2413333d0284530eee81994486fb Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:05:54 +0200 Subject: [PATCH 08/25] fix: Svelte module resolution tweaks (#2481) Svelte files are now resolved by intercepting checks for .d.svelte.ts instead of .svelte.ts. As a consequence, we can handle sibling foo.svelte / foo.svelte.ts files and importing both. When doing import Foo from './foo.svelte this now resolves to the Svelte file (prio to #2463 it would resovle to the .svelte.ts file), and doing import Foo from './foo.svelte.js will resolve to the TS file. --- packages/language-server/.gitignore | 1 + packages/language-server/package.json | 2 +- .../src/plugins/typescript/module-loader.ts | 2 +- .../src/plugins/typescript/svelte-sys.ts | 20 +++++-- .../src/plugins/typescript/utils.ts | 10 ++-- .../bindings-two-way-check/expectedv2.json | 18 ++++++ .../exports-map-svelte/expectedv2.json | 36 ++++++++++++ .../fixtures/exports-map-svelte/input.svelte | 9 +++ .../node_modules/package/foo.svelte | 3 + .../node_modules/package/package.json | 16 ++++++ .../node_modules/package/x-types.d.ts | 2 + .../node_modules/package/x.svelte | 3 + .../node_modules/package/y.svelte | 3 + .../fixtures/exports-map-svelte/tsconfig.json | 7 +++ .../fixtures/import-precedence/a.svelte | 3 + .../fixtures/import-precedence/a.svelte.d.ts | 1 + .../fixtures/import-precedence/b.svelte | 3 + .../fixtures/import-precedence/b.svelte.ts | 1 + .../fixtures/import-precedence/c.d.svelte.ts | 1 + .../fixtures/import-precedence/c.svelte | 3 + .../fixtures/import-precedence/d.svelte | 3 + .../fixtures/import-precedence/d.svelte.js | 1 + .../import-precedence/expectedv2.json | 1 + .../fixtures/import-precedence/input.svelte | 15 +++++ .../fixtures/import-precedence/tsconfig.json | 13 +++++ .../plugins/typescript/module-loader.test.ts | 4 +- .../plugins/typescript/svelte-sys.test.ts | 14 +++-- packages/typescript-plugin/package.json | 2 +- .../typescript-plugin/src/module-loader.ts | 56 +++++++++---------- packages/typescript-plugin/src/svelte-sys.ts | 2 +- packages/typescript-plugin/src/utils.ts | 10 +++- 31 files changed, 215 insertions(+), 50 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/input.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/foo.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/package.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x-types.d.ts create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/y.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/tsconfig.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte.d.ts create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte.ts create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.d.svelte.ts create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte.js create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/input.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/tsconfig.json diff --git a/packages/language-server/.gitignore b/packages/language-server/.gitignore index 5dbbc7fbb..02c400bc6 100644 --- a/packages/language-server/.gitignore +++ b/packages/language-server/.gitignore @@ -1,3 +1,4 @@ dist/ .vscode/ node_modules/ +!test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/ \ No newline at end of file diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 8ee68b6ed..f3702c47b 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -10,7 +10,7 @@ "./bin/server.js": "./bin/server.js" }, "scripts": { - "test": "cross-env TS_NODE_TRANSPILE_ONLY=true mocha --require ts-node/register \"test/**/*.ts\" --exclude \"test/**/*.d.ts\"", + "test": "cross-env TS_NODE_TRANSPILE_ONLY=true mocha --require ts-node/register \"test/**/*.test.ts\"", "build": "tsc", "prepublishOnly": "npm run build", "watch": "tsc -w" diff --git a/packages/language-server/src/plugins/typescript/module-loader.ts b/packages/language-server/src/plugins/typescript/module-loader.ts index 915e50b26..4cbde00b8 100644 --- a/packages/language-server/src/plugins/typescript/module-loader.ts +++ b/packages/language-server/src/plugins/typescript/module-loader.ts @@ -104,7 +104,7 @@ class ImpliedNodeFormatResolver { return undefined; } - let mode = undefined; + let mode: ReturnType = undefined; if (sourceFile) { this.cacheImpliedNodeFormat(sourceFile, compilerOptions); mode = ts.getModeForResolutionAtIndex(sourceFile, importIdxInFile, compilerOptions); diff --git a/packages/language-server/src/plugins/typescript/svelte-sys.ts b/packages/language-server/src/plugins/typescript/svelte-sys.ts index a639b2a58..553bd17a3 100644 --- a/packages/language-server/src/plugins/typescript/svelte-sys.ts +++ b/packages/language-server/src/plugins/typescript/svelte-sys.ts @@ -11,6 +11,17 @@ export function createSvelteSys(tsSystem: ts.System) { function svelteFileExists(path: string) { if (isVirtualSvelteFilePath(path)) { const sveltePath = toRealSvelteFilePath(path); + + // First check if there's a `.svelte.d.ts` or `.d.svelte.ts` file, which should take precedence + const dtsPath = sveltePath.slice(0, -7) + '.svelte.d.ts'; + const dtsPathExists = fileExistsCache.get(dtsPath) ?? tsSystem.fileExists(dtsPath); + fileExistsCache.set(dtsPath, dtsPathExists); + if (dtsPathExists) return false; + + const svelteDtsPathExists = fileExistsCache.get(path) ?? tsSystem.fileExists(path); + fileExistsCache.set(path, svelteDtsPathExists); + if (svelteDtsPathExists) return false; + const sveltePathExists = fileExistsCache.get(sveltePath) ?? tsSystem.fileExists(sveltePath); fileExistsCache.set(sveltePath, sveltePathExists); @@ -33,10 +44,11 @@ export function createSvelteSys(tsSystem: ts.System) { svelteFileExists, getRealSveltePathIfExists, fileExists(path: string) { - // We need to check both .svelte and .svelte.ts/js because that's how Svelte 5 will likely mark files with runes in them + // We need to check if this is a virtual svelte file const sveltePathExists = svelteFileExists(path); - const exists = - sveltePathExists || (fileExistsCache.get(path) ?? tsSystem.fileExists(path)); + if (sveltePathExists) return true; + + const exists = fileExistsCache.get(path) ?? tsSystem.fileExists(path); fileExistsCache.set(path, exists); return exists; }, @@ -66,7 +78,7 @@ export function createSvelteSys(tsSystem: ts.System) { const realpath = tsSystem.realpath; svelteSys.realpath = function (path) { if (svelteFileExists(path)) { - return realpath(toRealSvelteFilePath(path)) + '.ts'; + return realpath(toRealSvelteFilePath(path)); } return realpath(path); }; diff --git a/packages/language-server/src/plugins/typescript/utils.ts b/packages/language-server/src/plugins/typescript/utils.ts index 62fa6359d..a8a6ce1f6 100644 --- a/packages/language-server/src/plugins/typescript/utils.ts +++ b/packages/language-server/src/plugins/typescript/utils.ts @@ -70,15 +70,17 @@ export function isSvelteFilePath(filePath: string) { } export function isVirtualSvelteFilePath(filePath: string) { - return filePath.endsWith('.svelte.ts'); + return filePath.endsWith('.d.svelte.ts'); } export function toRealSvelteFilePath(filePath: string) { - return filePath.slice(0, -'.ts'.length); + return filePath.slice(0, -11 /* 'd.svelte.ts'.length */) + 'svelte'; } -export function toVirtualSvelteFilePath(filePath: string) { - return filePath.endsWith('.ts') ? filePath : filePath + '.ts'; +export function toVirtualSvelteFilePath(svelteFilePath: string) { + return isVirtualSvelteFilePath(svelteFilePath) + ? svelteFilePath + : svelteFilePath.slice(0, -6 /* 'svelte'.length */) + 'd.svelte.ts'; } export function ensureRealSvelteFilePath(filePath: string) { diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json new file mode 100644 index 000000000..f8c8cb449 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json @@ -0,0 +1,18 @@ +[ + { + "code": -1, + "message": "Unexpected token", + "range": { + "end": { + "character": 47, + "line": 12 + }, + "start": { + "character": 47, + "line": 12 + } + }, + "severity": 1, + "source": "ts" + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/expectedv2.json new file mode 100644 index 000000000..58680d099 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/expectedv2.json @@ -0,0 +1,36 @@ +[ + { + "code": 2307, + "message": "Cannot find module 'package' or its corresponding type declarations.", + "range": { + "end": { + "character": 45, + "line": 1 + }, + "start": { + "character": 36, + "line": 1 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2307, + "message": "Cannot find module 'package/y' or its corresponding type declarations.", + "range": { + "start": { + "character": 38, + "line": 3 + }, + "end": { + "character": 49, + "line": 3 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/input.svelte new file mode 100644 index 000000000..2775a7582 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/input.svelte @@ -0,0 +1,9 @@ + + + + + diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/foo.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/foo.svelte new file mode 100644 index 000000000..e29d29c54 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/foo.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/package.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/package.json new file mode 100644 index 000000000..422ee721e --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/package.json @@ -0,0 +1,16 @@ +{ + "name": "package", + "version": "1.0.0", + "exports": { + ".": { + "svelte": "./foo.svelte" + }, + "./x": { + "types": "./x-types.d.ts", + "svelte": "./x.svelte" + }, + "./y": { + "svelte": "./y.svelte" + } + } +} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x-types.d.ts b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x-types.d.ts new file mode 100644 index 000000000..4d27ef5b0 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x-types.d.ts @@ -0,0 +1,2 @@ +declare const X: any; +export default X; \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x.svelte new file mode 100644 index 000000000..fb5dbfefa --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/x.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/y.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/y.svelte new file mode 100644 index 000000000..78f01008c --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/node_modules/package/y.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/tsconfig.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/tsconfig.json new file mode 100644 index 000000000..d5e498207 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/exports-map-svelte/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "moduleResolution": "Bundler" + } +} diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte new file mode 100644 index 000000000..1f6a1cbe4 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte.d.ts b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte.d.ts new file mode 100644 index 000000000..227cc1bb1 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/a.svelte.d.ts @@ -0,0 +1 @@ +export declare const a: boolean; diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte new file mode 100644 index 000000000..1f6a1cbe4 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte.ts b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte.ts new file mode 100644 index 000000000..1d9db10ef --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/b.svelte.ts @@ -0,0 +1 @@ +export const b = true; diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.d.svelte.ts b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.d.svelte.ts new file mode 100644 index 000000000..a12f971dd --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.d.svelte.ts @@ -0,0 +1 @@ +export declare const c: boolean; diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.svelte new file mode 100644 index 000000000..1f6a1cbe4 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/c.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte new file mode 100644 index 000000000..1f6a1cbe4 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte.js b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte.js new file mode 100644 index 000000000..ecee2a83c --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/d.svelte.js @@ -0,0 +1 @@ +export const d = true; diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/expectedv2.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/expectedv2.json @@ -0,0 +1 @@ +[] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/input.svelte new file mode 100644 index 000000000..9966c707e --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/input.svelte @@ -0,0 +1,15 @@ + + + + diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/tsconfig.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/tsconfig.json new file mode 100644 index 000000000..eef515315 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/import-precedence/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "strict": true, + "allowJs": true, + "target": "ESNext", + /** + This is actually not needed, but makes the tests faster + because TS does not look up other types. + */ + "types": ["svelte"], + "allowArbitraryExtensions": true // else .d.svelte.ts will be an error + } +} diff --git a/packages/language-server/test/plugins/typescript/module-loader.test.ts b/packages/language-server/test/plugins/typescript/module-loader.test.ts index 559eeb447..1c3561cc2 100644 --- a/packages/language-server/test/plugins/typescript/module-loader.test.ts +++ b/packages/language-server/test/plugins/typescript/module-loader.test.ts @@ -59,12 +59,12 @@ describe('createSvelteModuleLoader', () => { it('uses svelte script kind if resolved module is svelte file', async () => { const resolvedModule: ts.ResolvedModuleFull = { extension: ts.Extension.Ts, - resolvedFileName: 'filename.svelte.ts' + resolvedFileName: 'filename.d.svelte.ts' }; const { getSvelteSnapshotStub, moduleResolver, svelteSys } = setup(resolvedModule); svelteSys.getRealSveltePathIfExists = (filename: string) => - filename === 'filename.svelte.ts' ? 'filename.svelte' : filename; + filename === 'filename.d.svelte.ts' ? 'filename.svelte' : filename; const result = moduleResolver.resolveModuleNames( ['./normal.ts'], diff --git a/packages/language-server/test/plugins/typescript/svelte-sys.test.ts b/packages/language-server/test/plugins/typescript/svelte-sys.test.ts index 2da35db20..ba9a21d5a 100644 --- a/packages/language-server/test/plugins/typescript/svelte-sys.test.ts +++ b/packages/language-server/test/plugins/typescript/svelte-sys.test.ts @@ -29,18 +29,24 @@ describe('Svelte Sys', () => { } describe('#fileExists', () => { - it('should leave files with no .svelte.ts-ending as is', async () => { + it('should leave files with no .d.svelte.ts-ending as is', async () => { const { loader, fileExistsStub } = setupLoader(); loader.fileExists('../file.ts'); assert.strictEqual(fileExistsStub.getCall(0).args[0], '../file.ts'); }); - it('should convert .svelte.ts-endings', async () => { + it('should convert .d.svelte.ts-endings', async () => { const { loader, fileExistsStub } = setupLoader(); - loader.fileExists('../file.svelte.ts'); + fileExistsStub.onCall(0).returns(false); + fileExistsStub.onCall(1).returns(false); + fileExistsStub.onCall(2).returns(true); - assert.strictEqual(fileExistsStub.getCall(0).args[0], '../file.svelte'); + loader.fileExists('../file.d.svelte.ts'); + + assert.strictEqual(fileExistsStub.getCall(0).args[0], '../file.svelte.d.ts'); + assert.strictEqual(fileExistsStub.getCall(1).args[0], '../file.d.svelte.ts'); + assert.strictEqual(fileExistsStub.getCall(2).args[0], '../file.svelte'); }); }); }); diff --git a/packages/typescript-plugin/package.json b/packages/typescript-plugin/package.json index 6928186a4..766ac42c5 100644 --- a/packages/typescript-plugin/package.json +++ b/packages/typescript-plugin/package.json @@ -1,6 +1,6 @@ { "name": "typescript-svelte-plugin", - "version": "0.2.0", + "version": "0.3.0", "description": "A TypeScript Plugin providing Svelte intellisense", "main": "dist/src/index.js", "scripts": { diff --git a/packages/typescript-plugin/src/module-loader.ts b/packages/typescript-plugin/src/module-loader.ts index cc2003cba..569f073b3 100644 --- a/packages/typescript-plugin/src/module-loader.ts +++ b/packages/typescript-plugin/src/module-loader.ts @@ -3,30 +3,7 @@ import { ConfigManager } from './config-manager'; import { Logger } from './logger'; import { SvelteSnapshotManager } from './svelte-snapshots'; import { createSvelteSys } from './svelte-sys'; -import { ensureRealSvelteFilePath, isVirtualSvelteFilePath } from './utils'; - -// TODO remove when we update to typescript 5.0 -declare module 'typescript/lib/tsserverlibrary' { - interface LanguageServiceHost { - /** @deprecated supply resolveModuleNameLiterals instead for resolution that can handle newer resolution modes like nodenext */ - resolveModuleNames?( - moduleNames: string[], - containingFile: string, - reusedNames: string[] | undefined, - redirectedReference: ts.ResolvedProjectReference | undefined, - options: ts.CompilerOptions, - containingSourceFile?: ts.SourceFile - ): (ts.ResolvedModule | undefined)[]; - resolveModuleNameLiterals?( - moduleLiterals: readonly ts.StringLiteralLike[], - containingFile: string, - redirectedReference: ts.ResolvedProjectReference | undefined, - options: ts.CompilerOptions, - containingSourceFile: ts.SourceFile, - reusedNames: readonly ts.StringLiteralLike[] | undefined - ): readonly ts.ResolvedModuleWithFailedLookupLocations[]; - } -} +import { ensureRealSvelteFilePath, isSvelteFilePath, isVirtualSvelteFilePath } from './utils'; /** * Caches resolved modules. @@ -110,6 +87,8 @@ export function patchModuleLoader( if (lsHost.resolveModuleNameLiterals) { lsHost.resolveModuleNameLiterals = resolveModuleNameLiterals; } else { + // TODO do we need to keep this around? We're requiring 5.0 now, so TS doesn't need it, + // but would this break when other TS plugins are used and we no longer provide it? lsHost.resolveModuleNames = resolveModuleNames; } @@ -161,12 +140,22 @@ export function patchModuleLoader( return resolved.map((tsResolvedModule, idx) => { const moduleName = moduleNames[idx]; - if (tsResolvedModule || !ensureRealSvelteFilePath(moduleName).endsWith('.svelte')) { + if ( + !isSvelteFilePath(moduleName) || + // corresponding .d.ts files take precedence over .svelte files + tsResolvedModule?.resolvedFileName.endsWith('.d.ts') || + tsResolvedModule?.resolvedFileName.endsWith('.d.svelte.ts') + ) { return tsResolvedModule; } - return resolveSvelteModuleNameFromCache(moduleName, containingFile, compilerOptions) - .resolvedModule; + const result = resolveSvelteModuleNameFromCache( + moduleName, + containingFile, + compilerOptions + ).resolvedModule; + // .svelte takes precedence over .svelte.ts etc + return result ?? tsResolvedModule; }); } @@ -238,14 +227,20 @@ export function patchModuleLoader( return resolved.map((tsResolvedModule, idx) => { const moduleName = moduleLiterals[idx].text; + const resolvedModule = tsResolvedModule.resolvedModule; + if ( - tsResolvedModule.resolvedModule || - !ensureRealSvelteFilePath(moduleName).endsWith('.svelte') + !isSvelteFilePath(moduleName) || + // corresponding .d.ts files take precedence over .svelte files + resolvedModule?.resolvedFileName.endsWith('.d.ts') || + resolvedModule?.resolvedFileName.endsWith('.d.svelte.ts') ) { return tsResolvedModule; } - return resolveSvelteModuleNameFromCache(moduleName, containingFile, options); + const result = resolveSvelteModuleNameFromCache(moduleName, containingFile, options); + // .svelte takes precedence over .svelte.ts etc + return result.resolvedModule ? result : tsResolvedModule; }); } @@ -262,6 +257,7 @@ export function patchModuleLoader( } const resolvedModule = resolveSvelteModuleName(moduleName, containingFile, options); + moduleCache.set(moduleName, containingFile, resolvedModule); return { resolvedModule: resolvedModule diff --git a/packages/typescript-plugin/src/svelte-sys.ts b/packages/typescript-plugin/src/svelte-sys.ts index d530e0b47..75b0a6f80 100644 --- a/packages/typescript-plugin/src/svelte-sys.ts +++ b/packages/typescript-plugin/src/svelte-sys.ts @@ -30,7 +30,7 @@ export function createSvelteSys(ts: _ts, logger: Logger) { const realpath = ts.sys.realpath; svelteSys.realpath = function (path) { if (isVirtualSvelteFilePath(path)) { - return realpath(toRealSvelteFilePath(path)) + '.ts'; + return realpath(toRealSvelteFilePath(path)); } return realpath(path); }; diff --git a/packages/typescript-plugin/src/utils.ts b/packages/typescript-plugin/src/utils.ts index d189f7ad1..1b14a6dbe 100644 --- a/packages/typescript-plugin/src/utils.ts +++ b/packages/typescript-plugin/src/utils.ts @@ -8,11 +8,17 @@ export function isSvelteFilePath(filePath: string) { } export function isVirtualSvelteFilePath(filePath: string) { - return filePath.endsWith('.svelte.ts'); + return filePath.endsWith('.d.svelte.ts'); } export function toRealSvelteFilePath(filePath: string) { - return filePath.slice(0, -'.ts'.length); + return filePath.slice(0, -11 /* 'd.svelte.ts'.length */) + 'svelte'; +} + +export function toVirtualSvelteFilePath(svelteFilePath: string) { + return isVirtualSvelteFilePath(svelteFilePath) + ? svelteFilePath + : svelteFilePath.slice(0, -6 /* 'svelte'.length */) + 'd.svelte.ts'; } export function ensureRealSvelteFilePath(filePath: string) { From d99497176dcca89de69471934b05c433b64558e3 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:33:28 +0800 Subject: [PATCH 09/25] feat: allow autoImportFileExcludePatterns to ignore files outside the root (#2480) part of #2479 --- packages/language-server/src/ls-config.ts | 36 +++++++---- .../typescript/features/preferences.test.ts | 64 ++++++++++++------- .../code-action-outside-root.svelte | 4 ++ 3 files changed, 68 insertions(+), 36 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/preferences/code-action-outside-root.svelte diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index 39e098ab3..63389e7bb 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -478,16 +478,25 @@ export class LSConfigManager { }; } - getTsUserPreferences(lang: TsUserConfigLang, workspacePath: string | null): ts.UserPreferences { + getTsUserPreferences( + lang: TsUserConfigLang, + normalizedWorkspacePath: string | null + ): ts.UserPreferences { const userPreferences = this.tsUserPreferences[lang]; - if (!workspacePath || !userPreferences.autoImportFileExcludePatterns) { + if (!normalizedWorkspacePath || !userPreferences.autoImportFileExcludePatterns) { return userPreferences; } - let autoImportFileExcludePatterns = this.resolvedAutoImportExcludeCache.get(workspacePath); + let autoImportFileExcludePatterns = + this.resolvedAutoImportExcludeCache.get(normalizedWorkspacePath); if (!autoImportFileExcludePatterns) { + const version = ts.version.split('.'); + const major = parseInt(version[0]); + const minor = parseInt(version[1]); + + const gte5_4 = major > 5 || (major === 5 && minor >= 4); autoImportFileExcludePatterns = userPreferences.autoImportFileExcludePatterns.map( (p) => { // Normalization rules: https://github.com/microsoft/TypeScript/pull/49578 @@ -497,17 +506,20 @@ export class LSConfigManager { return p; } - return path.join( - workspacePath, - p.startsWith('*') - ? '/' + slashNormalized - : isRelative - ? p - : '/**/' + slashNormalized - ); + // https://github.com/microsoft/vscode/pull/202762 + // ts 5.4+ supports leading wildcards + const wildcardPrefix = gte5_4 ? '' : path.parse(normalizedWorkspacePath).root; + return p.startsWith('*') + ? wildcardPrefix + slashNormalized + : isRelative + ? path.join(normalizedWorkspacePath, p) + : wildcardPrefix + '**/' + slashNormalized; } ); - this.resolvedAutoImportExcludeCache.set(workspacePath, autoImportFileExcludePatterns); + this.resolvedAutoImportExcludeCache.set( + normalizedWorkspacePath, + autoImportFileExcludePatterns + ); } return { diff --git a/packages/language-server/test/plugins/typescript/features/preferences.test.ts b/packages/language-server/test/plugins/typescript/features/preferences.test.ts index c794dc55b..b4c35f187 100644 --- a/packages/language-server/test/plugins/typescript/features/preferences.test.ts +++ b/packages/language-server/test/plugins/typescript/features/preferences.test.ts @@ -64,7 +64,14 @@ describe('ts user preferences', function () { typescript: { ...getPreferences(), ...preferences }, javascript: { ...getPreferences(), ...preferences } }); - return new LSAndTSDocResolver(docManager, [pathToUrl(testFilesDir)], configManager); + return { + lsAndTsDocResolver: new LSAndTSDocResolver( + docManager, + [pathToUrl(testFilesDir)], + configManager + ), + configManager + }; } function getDefaultPreferences(): TsUserPreferencesConfig { @@ -79,11 +86,8 @@ describe('ts user preferences', function () { it('provides auto import completion according to preferences', async () => { const { docManager, document } = setup('code-action.svelte'); - const lsAndTsDocResolver = createLSAndTSDocResolver(docManager); - const completionProvider = new CompletionsProviderImpl( - lsAndTsDocResolver, - new LSConfigManager() - ); + const { lsAndTsDocResolver, configManager } = createLSAndTSDocResolver(docManager); + const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver, configManager); const completions = await completionProvider.getCompletions( document, @@ -102,15 +106,13 @@ describe('ts user preferences', function () { context: CodeActionContext ) { const { docManager, document } = setup(filename); - const lsAndTsDocResolver = createLSAndTSDocResolver(docManager); - const completionProvider = new CompletionsProviderImpl( - lsAndTsDocResolver, - new LSConfigManager() - ); + const { lsAndTsDocResolver, configManager } = createLSAndTSDocResolver(docManager); + const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver, configManager); + const codeActionProvider = new CodeActionsProviderImpl( lsAndTsDocResolver, completionProvider, - new LSConfigManager() + configManager ); const codeAction = await codeActionProvider.getCodeActions(document, range, context); @@ -137,7 +139,7 @@ describe('ts user preferences', function () { it('provides auto import suggestions according to preferences', async () => { const { docManager, document } = setup('code-action.svelte'); - const lsAndTsDocResolver = createLSAndTSDocResolver(docManager, { + const { lsAndTsDocResolver, configManager } = createLSAndTSDocResolver(docManager, { suggest: { autoImports: false, includeAutomaticOptionalChainCompletions: undefined, @@ -147,10 +149,7 @@ describe('ts user preferences', function () { includeCompletionsWithSnippetText: undefined } }); - const completionProvider = new CompletionsProviderImpl( - lsAndTsDocResolver, - new LSConfigManager() - ); + const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver, configManager); const completions = await completionProvider.getCompletions( document, @@ -165,14 +164,14 @@ describe('ts user preferences', function () { function setupImportModuleSpecifierEndingJs() { const { docManager, document } = setup('module-specifier-js.svelte'); - const lsAndTsDocResolver = createLSAndTSDocResolver(docManager, { + const { lsAndTsDocResolver, configManager } = createLSAndTSDocResolver(docManager, { preferences: { ...getDefaultPreferences(), importModuleSpecifierEnding: 'js' } }); - return { document, lsAndTsDocResolver }; + return { document, lsAndTsDocResolver, configManager }; } it('provides auto import for svelte component when importModuleSpecifierEnding is js', async () => { @@ -248,16 +247,13 @@ describe('ts user preferences', function () { async function testExcludeDefinitionDir(pattern: string) { const { docManager, document } = setup('code-action.svelte'); - const lsAndTsDocResolver = createLSAndTSDocResolver(docManager, { + const { lsAndTsDocResolver, configManager } = createLSAndTSDocResolver(docManager, { preferences: { ...getDefaultPreferences(), autoImportFileExcludePatterns: [pattern] } }); - const completionProvider = new CompletionsProviderImpl( - lsAndTsDocResolver, - new LSConfigManager() - ); + const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver, configManager); const completions = await completionProvider.getCompletions( document, @@ -280,4 +276,24 @@ describe('ts user preferences', function () { it('exclude auto import (**/ pattern)', async () => { await testExcludeDefinitionDir('**/definition'); }); + + it('exclude auto import outside of the root', async () => { + const { docManager, document } = setup('code-action-outside-root.svelte'); + const { lsAndTsDocResolver, configManager } = createLSAndTSDocResolver(docManager, { + preferences: { + ...getDefaultPreferences(), + autoImportFileExcludePatterns: ['definitions.ts'] + } + }); + const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver, configManager); + + const completions = await completionProvider.getCompletions( + document, + Position.create(4, 7) + ); + + const item = completions?.items.find((item) => item.label === 'blubb'); + + assert.equal(item, undefined); + }); }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/preferences/code-action-outside-root.svelte b/packages/language-server/test/plugins/typescript/testfiles/preferences/code-action-outside-root.svelte new file mode 100644 index 000000000..cb38cfe93 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/preferences/code-action-outside-root.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file From 399ad6a08d03b14f46a40f1a1483a051d08a8ff2 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:38:11 +0200 Subject: [PATCH 10/25] fix: detect `' + ); + + virtualSystem.writeFile( + path.join(dirPath, 'random.ts'), + 'import {} from "./random.svelte"' + ); + + let called = false; + const service = await getService(path.join(dirPath, 'random.svelte'), rootUris, { + ...lsDocumentContext, + reportConfigError: (message) => { + called = true; + assert.deepStrictEqual([], message.diagnostics); + } + }); + + assert.equal( + normalizePath(path.join(dirPath, 'tsconfig.json')), + normalizePath(service.tsconfigPath) + ); + assert.ok(called); + }); + + it('does not errors if referenced tsconfig matches no svelte files', async () => { const dirPath = getRandomVirtualDirPath(testDir); const { virtualSystem, lsDocumentContext, rootUris } = setup(); @@ -565,10 +604,63 @@ describe('service', () => { sinon.assert.calledWith(watchDirectory.firstCall, []); }); + it('assigns files to service with the file in the program', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + const tsconfigPath = path.join(dirPath, 'tsconfig.json'); + virtualSystem.writeFile( + tsconfigPath, + JSON.stringify({ + compilerOptions: { + noImplicitOverride: true + }, + include: ['src/*.ts'] + }) + ); + + const referencedFile = path.join(dirPath, 'anotherPackage', 'index.svelte'); + const tsFilePath = path.join(dirPath, 'src', 'random.ts'); + + virtualSystem.readDirectory = () => [tsFilePath]; + virtualSystem.writeFile( + referencedFile, + '' + ); + virtualSystem.writeFile(tsFilePath, 'import "../anotherPackage/index.svelte";'); + + const document = new Document( + pathToUrl(referencedFile), + virtualSystem.readFile(referencedFile)! + ); + document.openedByClient = true; + const ls = await getService(referencedFile, rootUris, lsDocumentContext); + ls.updateSnapshot(document); + + assert.equal(normalizePath(ls.tsconfigPath), normalizePath(tsconfigPath)); + + const noImplicitOverrideErrorCode = 4114; + const findError = (ls: LanguageServiceContainer) => + ls + .getService() + .getSemanticDiagnostics(referencedFile) + .find((f) => f.code === noImplicitOverrideErrorCode); + + assert.ok(findError(ls)); + + virtualSystem.writeFile(tsFilePath, ''); + ls.updateTsOrJsFile(tsFilePath); + + const ls2 = await getService(referencedFile, rootUris, lsDocumentContext); + ls2.updateSnapshot(document); + + assert.deepStrictEqual(findError(ls2), undefined); + }); + function getSemanticDiagnosticsMessages(ls: LanguageServiceContainer, filePath: string) { return ls .getService() .getSemanticDiagnostics(filePath) - .map((d) => d.messageText); + .map((d) => ts.flattenDiagnosticMessageText(d.messageText, '\n')); } }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json index 6d3385d79..04d6ed3d5 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/different-ts-service/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "allowJs": true, /** This is actually not needed, but makes the tests faster because TS does not look up other types. From e132b5667a87a7d233adef795b3e99ecc85e6aad Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:48:20 +0200 Subject: [PATCH 18/25] fix: make no Svelte files found a warning (#2507) There were reports that this was too overzealous as some people use this potentially not knowing whether or not this is a Svelte-projects, too. Therefore only issue a warning instead of an error. --- .../src/plugins/typescript/service.ts | 8 +++++--- packages/language-server/src/svelte-check.ts | 16 +++++++++++++--- .../svelte2tsx/processInstanceScriptContent.ts | 8 +++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index f1b70aa09..0e0d0f27a 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -1,6 +1,7 @@ import { dirname, join, resolve, basename } from 'path'; import ts from 'typescript'; import { + DiagnosticSeverity, PublishDiagnosticsParams, RelativePattern, TextDocumentContentChangeEvent @@ -803,7 +804,7 @@ async function createLanguageService( const excludeText = JSON.stringify(exclude); const svelteConfigDiagnostics: ts.Diagnostic[] = [ { - category: ts.DiagnosticCategory.Error, + category: ts.DiagnosticCategory.Warning, code: TsconfigSvelteDiagnostics.NO_SVELTE_INPUT, file: undefined, start: undefined, @@ -977,13 +978,14 @@ async function createLanguageService( diagnostics: svelteConfigDiagnostics.map((d) => ({ message: d.messageText as string, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, - severity: ts.DiagnosticCategory.Error, + severity: DiagnosticSeverity.Warning, source: 'svelte' })) }); - projectConfig.errors = projectConfig.errors + const new_errors = projectConfig.errors .filter((e) => !codes.includes(e.code)) .concat(svelteConfigDiagnostics); + projectConfig.errors.splice(0, projectConfig.errors.length, ...new_errors); } // https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624 diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 2d3e11ff3..75a62b173 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -208,19 +208,23 @@ export class SvelteCheck { }; }; - if (lsContainer.configErrors.length > 0) { + if ( + lsContainer.configErrors.some((error) => error.category === ts.DiagnosticCategory.Error) + ) { return reportConfigError(); } const lang = lsContainer.getService(); - if (lsContainer.configErrors.length > 0) { + if ( + lsContainer.configErrors.some((error) => error.category === ts.DiagnosticCategory.Error) + ) { return reportConfigError(); } const files = lang.getProgram()?.getSourceFiles() || []; const options = lang.getProgram()?.getCompilerOptions() || {}; - return await Promise.all( + const diagnostics = await Promise.all( files.map((file) => { const uri = pathToUrl(file.fileName); const doc = this.docManager.get(uri); @@ -314,6 +318,12 @@ export class SvelteCheck { }) ); + if (lsContainer.configErrors.length) { + diagnostics.push(...reportConfigError()); + } + + return diagnostics; + function reportConfigError() { const grouped = groupBy( lsContainer.configErrors, diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index e226c2b1b..50d0e5530 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -49,7 +49,13 @@ export function processInstanceScriptContent( const tsAst = ts.createSourceFile( 'component.ts.svelte', scriptContent, - ts.ScriptTarget.Latest, + ts.JSDocParsingMode + ? { + languageVersion: ts.ScriptTarget.Latest, + // Exists since TS 5.3 + jsDocParsingMode: ts.JSDocParsingMode.ParseNone + } + : ts.ScriptTarget.Latest, true, ts.ScriptKind.TS ); From e74f1d712b66873c06590fd47dcfcfc69aead7db Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 19 Sep 2024 13:51:34 +0200 Subject: [PATCH 19/25] chore: revert accidental commit --- .../src/svelte2tsx/processInstanceScriptContent.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index 50d0e5530..e226c2b1b 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -49,13 +49,7 @@ export function processInstanceScriptContent( const tsAst = ts.createSourceFile( 'component.ts.svelte', scriptContent, - ts.JSDocParsingMode - ? { - languageVersion: ts.ScriptTarget.Latest, - // Exists since TS 5.3 - jsDocParsingMode: ts.JSDocParsingMode.ParseNone - } - : ts.ScriptTarget.Latest, + ts.ScriptTarget.Latest, true, ts.ScriptKind.TS ); From 81019d91c38effe61c2ac4b8e8f830d8a6d1f148 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:06:10 +0200 Subject: [PATCH 20/25] fix: revert additional two-way-binding checks (#2508) This reverts commit 8c080cf36286984439b468e089e6e4eaacd4230e. This reverts #2477. Sadly, the idea didn't work out, as shown by two opened bug reports: - #2506: A type union can be narrowed on the input, but not on the way back out - #2494: A generic nested within a bound value is not properly resolved and not falling back to `any` (in #2477 we thought of the generic case, but only for when the generic is the whole value type, not when it's nested) For these reasons I don't see a way to properly implement #1392 at the moment. --- .../typescript/features/RenameProvider.ts | 10 +----- .../bindings-two-way-check/Legacy.svelte | 7 ---- .../bindings-two-way-check/Runes.svelte | 6 ---- .../RunesGeneric.svelte | 6 ---- .../expected_svelte_5.json | 36 ------------------- .../bindings-two-way-check/expectedv2.json | 18 ---------- .../bindings-two-way-check/input.svelte | 19 ---------- packages/svelte2tsx/repl/index.svelte | 12 +++---- .../src/htmlxtojsx_v2/nodes/Binding.ts | 6 ---- .../htmlxtojsx_v2/nodes/InlineComponent.ts | 1 - packages/svelte2tsx/svelte-shims-v4.d.ts | 13 ------- .../samples/binding-bare/expected-svelte5.js | 4 +-- .../samples/binding/expected-svelte5.js | 8 ++--- .../editing-binding/expected-svelte5.js | 2 +- 14 files changed, 14 insertions(+), 134 deletions(-) delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json delete mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts index 8340478c3..3b9caf86e 100644 --- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts @@ -463,16 +463,8 @@ export class RenameProviderImpl implements RenameProvider { const mappedLocations = await Promise.all( renameLocations.map(async (loc) => { const snapshot = await snapshots.retrieve(loc.fileName); - const text = snapshot.getFullText(); - const end = loc.textSpan.start + loc.textSpan.length; - if ( - !(snapshot instanceof SvelteDocumentSnapshot) || - (!isTextSpanInGeneratedCode(text, loc.textSpan) && - // prevent generated code for bindings from being renamed - // (it's not inside a generate comment because diagnostics should show up) - text.slice(end + 3, end + 27) !== '__sveltets_binding_value') - ) { + if (!isTextSpanInGeneratedCode(snapshot.getFullText(), loc.textSpan)) { return { ...loc, range: this.mapRangeToOriginal(snapshot, loc.textSpan), diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte deleted file mode 100644 index 18db75ea2..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Legacy.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -{legacy1} -{legacy2} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte deleted file mode 100644 index 299be4d66..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/Runes.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - -{runes1} -{runes2} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte deleted file mode 100644 index 6accfc4b3..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/RunesGeneric.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - -{foo} -{bar} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json deleted file mode 100644 index 6b1d57c83..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expected_svelte_5.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "code": 2322, - "message": "Type 'string | number' is not assignable to type 'string'.\n Type 'number' is not assignable to type 'string'.", - "range": { - "end": { - "character": 28, - "line": 17 - }, - "start": { - "character": 28, - "line": 17 - } - }, - "severity": 1, - "source": "ts", - "tags": [] - }, - { - "code": 2322, - "message": "Type 'string | number' is not assignable to type 'string'.\n Type 'number' is not assignable to type 'string'.", - "range": { - "end": { - "character": 45, - "line": 18 - }, - "start": { - "character": 45, - "line": 18 - } - }, - "severity": 1, - "source": "ts", - "tags": [] - } -] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json deleted file mode 100644 index f8c8cb449..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/expectedv2.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "code": -1, - "message": "Unexpected token", - "range": { - "end": { - "character": 47, - "line": 12 - }, - "start": { - "character": 47, - "line": 12 - } - }, - "severity": 1, - "source": "ts" - } -] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte deleted file mode 100644 index 49020f784..000000000 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings-two-way-check/input.svelte +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - diff --git a/packages/svelte2tsx/repl/index.svelte b/packages/svelte2tsx/repl/index.svelte index bf8061fb0..49517cc72 100644 --- a/packages/svelte2tsx/repl/index.svelte +++ b/packages/svelte2tsx/repl/index.svelte @@ -1,7 +1,7 @@ - - - \ No newline at end of file + +{#if value} + +{/if} diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts index 555269b61..5bb17cca7 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts @@ -131,12 +131,6 @@ export function handleBinding( if (isSvelte5Plus && element instanceof InlineComponent) { // To check if property is actually bindable element.appendToStartEnd([`${element.name}.$$bindings = '${attr.name}';`]); - // To check if the binding is also assigned to the variable (only works when there's no assertion, we can't transform that) - if (!isTypescriptNode(attr.expression)) { - element.appendToStartEnd([ - `${expressionStr} = __sveltets_binding_value(${element.originalName}, '${attr.name}');` - ]); - } } if (element instanceof Element) { diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts index 7459f227e..b1bab058d 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts @@ -39,7 +39,6 @@ export class InlineComponent { private startTagEnd: number; private isSelfclosing: boolean; public child?: any; - public originalName = this.node.name; // Add const $$xxx = ... only if the variable name is actually used // in order to prevent "$$xxx is defined but never used" TS hints diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index f01cf6d18..70e6df540 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -253,16 +253,3 @@ declare function __sveltets_2_isomorphic_component< declare function __sveltets_2_isomorphic_component_slots< Props extends Record, Events extends Record, Slots extends Record, Exports extends Record, Bindings extends string >(klass: {props: Props, events: Events, slots: Slots, exports?: Exports, bindings?: Bindings }): __sveltets_2_IsomorphicComponent<__sveltets_2_PropsWithChildren, Events, Slots, Exports, Bindings>; - -type __sveltets_NonUndefined = T extends undefined ? never : T; - -declare function __sveltets_binding_value< - // @ts-ignore this is only used for Svelte 5, which knows about the Component type - Comp extends typeof import('svelte').Component, - Key extends string ->(comp: Comp, key: Key): Key extends keyof import('svelte').ComponentProps ? - // bail on unknown because it hints at a generic type which we can't properly resolve here - // remove undefined because optional properties have it, and would result in false positives - unknown extends import('svelte').ComponentProps[Key] ? any : __sveltets_NonUndefined[Key]> : any; -// Overload to ensure typings that only use old SvelteComponent class or something invalid are gracefully handled -declare function __sveltets_binding_value(comp: any, key: string): any diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js index 43291dbae..bb934f658 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expected-svelte5.js @@ -1,5 +1,5 @@ { svelteHTML.createElement("input", { "type":`text`,"bind:value":value,});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/} { svelteHTML.createElement("input", { "type":`checkbox`,"bind:checked":checked,});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value,}});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';value = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`checkbox`,checked,}});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'checked';checked = __sveltets_binding_value(Input, 'checked');} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value,}});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`checkbox`,checked,}});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'checked';} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js index 355227003..6c881fec9 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expected-svelte5.js @@ -2,7 +2,7 @@ { 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); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value');} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';test = __sveltets_binding_value(Input, 'value'); Input} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { "type":`text`,value:test,}});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value'; Input} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js index 31e8b2747..7bb207fea 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js @@ -1,3 +1,3 @@ { svelteHTML.createElement("input", { });obj = __sveltets_2_any(null);} { svelteHTML.createElement("input", { "bind:value":obj.,});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';obj = __sveltets_binding_value(Input, 'value');} \ No newline at end of file + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} \ No newline at end of file From 35af691b80537e8b12a908682c31314da97f55d9 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Thu, 26 Sep 2024 02:41:25 +0800 Subject: [PATCH 21/25] perf: auto import cache for svelte-kit language service proxy (#2513) #2464 getPackageJsonsVisibleToFile and getGlobalTypingsCacheLocation are singleton so this should definitely be fine. The proxy language service has the exact list of entry files and the auto-typing also doesn't add any export so it should be fine to just proxy these methods. --- .../src/language-service/sveltekit.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/packages/typescript-plugin/src/language-service/sveltekit.ts b/packages/typescript-plugin/src/language-service/sveltekit.ts index 8a3994c5d..9b5ee4fca 100644 --- a/packages/typescript-plugin/src/language-service/sveltekit.ts +++ b/packages/typescript-plugin/src/language-service/sveltekit.ts @@ -10,6 +10,22 @@ interface KitSnapshot { addedCode: InternalHelpers.AddedCode[]; } +declare module 'typescript/lib/tsserverlibrary' { + interface LanguageServiceHost { + /** @internal */ getCachedExportInfoMap?(): unknown; + /** @internal */ getModuleSpecifierCache?(): unknown; + /** @internal */ getGlobalTypingsCacheLocation?(): string | undefined; + /** @internal */ getSymlinkCache?(files: readonly ts.SourceFile[]): unknown; + /** @internal */ getPackageJsonsVisibleToFile?( + fileName: string, + rootDir?: string + ): readonly unknown[]; + /** @internal */ getPackageJsonAutoImportProvider?(): ts.Program | undefined; + + /** @internal*/ getModuleResolutionCache?(): ts.ModuleResolutionCache; + } +} + const cache = new WeakMap< ts.server.PluginCreateInfo, { @@ -719,6 +735,51 @@ function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, lo ? (...args: Parameters>) => originalLanguageServiceHost.realpath!(...args) : undefined; + + getProjectReferences = originalLanguageServiceHost.getProjectReferences + ? () => originalLanguageServiceHost.getProjectReferences!() + : undefined; + + getParsedCommandLine = originalLanguageServiceHost.getParsedCommandLine + ? (fileName: string) => originalLanguageServiceHost.getParsedCommandLine!(fileName) + : undefined; + + getCachedExportInfoMap = originalLanguageServiceHost.getCachedExportInfoMap + ? () => originalLanguageServiceHost.getCachedExportInfoMap!() + : undefined; + + getModuleSpecifierCache = originalLanguageServiceHost.getModuleSpecifierCache + ? () => originalLanguageServiceHost.getModuleSpecifierCache!() + : undefined; + + getGlobalTypingsCacheLocation = originalLanguageServiceHost.getGlobalTypingsCacheLocation + ? () => originalLanguageServiceHost.getGlobalTypingsCacheLocation!() + : undefined; + + getSymlinkCache = originalLanguageServiceHost.getSymlinkCache + ? (...args: any[]) => + originalLanguageServiceHost.getSymlinkCache!( + // @ts-ignore + ...args + ) + : undefined; + + getPackageJsonsVisibleToFile = originalLanguageServiceHost.getPackageJsonsVisibleToFile + ? (...args: any[]) => + originalLanguageServiceHost.getPackageJsonsVisibleToFile!( + // @ts-ignore + ...args + ) + : undefined; + + getPackageJsonAutoImportProvider = + originalLanguageServiceHost.getPackageJsonAutoImportProvider + ? () => originalLanguageServiceHost.getPackageJsonAutoImportProvider!() + : undefined; + + getModuleResolutionCache = originalLanguageServiceHost.getModuleResolutionCache + ? () => originalLanguageServiceHost.getModuleResolutionCache!() + : undefined; } // Ideally we'd create a full Proxy of the language service, but that seems to have cache issues From 837b61fa57cb80b0e4c531e5d7c61d0000707ce7 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:36:42 +0200 Subject: [PATCH 22/25] breaking(svelte5): only generate function component shape in runes mode (#2517) When a component is in runes mode and not using slots or events, adjust the output to only create the function type that mimics the underlying real shape of components in Svelte 5. This is a breaking change because previously the type was enhanced such that it also had the legacy class shape. As a result, users now may need to switch to `typeof Component` when using the component inside types. Sadly, due to a combination of requirements and TypeScript limitations, we need to always create both a legacy class component and function component type. - Constraints: Need to support Svelte 4 class component types, therefore we need to use __sveltets_2_ensureComponent to transform function components to classes - Limitations: TypeScript is not able to preserve generics during said transformation (i.e. there's no way to express keeping the generic etc) TODO Svelte 6/7: Switch this around and not use new Component in svelte2tsx anymore, which means we can remove the legacy class component. We need something like _ensureFnComponent then. --- .../features/SemanticTokensProvider.ts | 48 +++++++---- .../features/SemanticTokensProvider.test.ts | 7 -- .../fixtures/bindings/RunesGeneric.svelte | 7 ++ .../fixtures/bindings/expected_svelte_5.json | 84 +++++++++++++++++-- .../fixtures/bindings/expectedv2.json | 50 +++++++++-- .../fixtures/bindings/input.svelte | 7 +- .../src/svelte2tsx/addComponentExport.ts | 59 +++++++++---- packages/svelte2tsx/src/svelte2tsx/index.ts | 2 +- .../src/svelte2tsx/nodes/ComponentEvents.ts | 4 + .../src/svelte2tsx/nodes/ExportedNames.ts | 2 +- packages/svelte2tsx/svelte-shims-v4.d.ts | 38 ++++++++- .../expected/TestRunes.svelte.d.ts | 18 +--- .../expected/TestRunes.svelte.d.ts | 8 +- .../expected/TestRunes.svelte.d.ts | 18 +--- packages/svelte2tsx/test/helpers.ts | 5 +- .../expectedv2.ts | 8 +- .../runes-best-effort-types.v5/expectedv2.ts | 5 +- .../samples/runes-bindable.v5/expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../runes-only-export.v5/expectedv2.ts | 5 +- .../svelte2tsx/samples/runes.v5/expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../ts-runes-bindable.v5/expectedv2.ts | 5 +- .../ts-runes-generics.v5/expectedv2.ts | 4 +- .../ts-runes-with-slot.v5/expectedv2.ts | 4 +- .../samples/ts-runes.v5/expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- .../expectedv2.ts | 5 +- 30 files changed, 285 insertions(+), 148 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte diff --git a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts index 9af4f4239..2103371f2 100644 --- a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts @@ -66,29 +66,23 @@ export class SemanticTokensProviderImpl implements SemanticTokensProvider { continue; } - const originalPosition = this.mapToOrigin( + const original = this.map( textDocument, tsDoc, generatedOffset, generatedLength, - encodedClassification + encodedClassification, + classificationType ); - if (!originalPosition) { - continue; - } - - const [line, character, length] = originalPosition; // remove identifiers whose start and end mapped to the same location, // like the svelte2tsx inserted render function, // or reversed like Component.$on - if (length <= 0) { + if (!original || original[2] <= 0) { continue; } - const modifier = this.getTokenModifierFromClassification(encodedClassification); - - data.push([line, character, length, classificationType, modifier]); + data.push(original); } const sorted = data.sort((a, b) => { @@ -103,17 +97,20 @@ export class SemanticTokensProviderImpl implements SemanticTokensProvider { return builder.build(); } - private mapToOrigin( + private map( document: Document, snapshot: SvelteDocumentSnapshot, generatedOffset: number, generatedLength: number, - token: number - ): [line: number, character: number, length: number, start: number] | undefined { + encodedClassification: number, + classificationType: number + ): + | [line: number, character: number, length: number, token: number, modifier: number] + | undefined { const text = snapshot.getFullText(); if ( isInGeneratedCode(text, generatedOffset, generatedOffset + generatedLength) || - (token === 2817 /* top level function */ && + (encodedClassification === 2817 /* top level function */ && text.substring(generatedOffset, generatedOffset + generatedLength) === 'render') ) { return; @@ -132,7 +129,26 @@ export class SemanticTokensProviderImpl implements SemanticTokensProvider { const startOffset = document.offsetAt(startPosition); const endOffset = document.offsetAt(endPosition); - return [startPosition.line, startPosition.character, endOffset - startOffset, startOffset]; + // Ensure components in the template get no semantic highlighting + if ( + (classificationType === 0 || + classificationType === 5 || + classificationType === 7 || + classificationType === 10) && + snapshot.svelteNodeAt(startOffset)?.type === 'InlineComponent' && + (document.getText().charCodeAt(startOffset - 1) === /* < */ 60 || + document.getText().charCodeAt(startOffset - 1) === /* / */ 47) + ) { + return; + } + + return [ + startPosition.line, + startPosition.character, + endOffset - startOffset, + classificationType, + this.getTokenModifierFromClassification(encodedClassification) + ]; } /** diff --git a/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts b/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts index 7acb27099..a942f32f8 100644 --- a/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts @@ -191,13 +191,6 @@ describe('SemanticTokensProvider', function () { type: TokenType.variable, modifiers: [TokenModifier.declaration, TokenModifier.local, TokenModifier.readonly] }, - { - line: 12, - character: 5, - length: 'Imported'.length, - type: isSvelte5Plus ? TokenType.type : TokenType.class, - modifiers: isSvelte5Plus ? [TokenModifier.readonly] : [] - }, { line: 12, character: 23, diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte new file mode 100644 index 000000000..0b9ff0c17 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json index 72c8185db..8aca1d067 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json @@ -5,11 +5,11 @@ "range": { "end": { "character": 20, - "line": 25 + "line": 26 }, "start": { "character": 7, - "line": 25 + "line": 26 } }, "severity": 1, @@ -22,11 +22,11 @@ "range": { "end": { "character": 21, - "line": 26 + "line": 27 }, "start": { "character": 12, - "line": 26 + "line": 27 } }, "severity": 1, @@ -39,11 +39,11 @@ "range": { "end": { "character": 21, - "line": 26 + "line": 27 }, "start": { "character": 7, - "line": 26 + "line": 27 } }, "severity": 1, @@ -56,11 +56,79 @@ "range": { "end": { "character": 17, - "line": 27 + "line": 28 }, "start": { "character": 8, - "line": 27 + "line": 28 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2322, + "message": "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\nTo mark a property as bindable: 'let { readonly = $bindable() } = $props()'", + "range": { + "end": { + "character": 27, + "line": 30 + }, + "start": { + "character": 14, + "line": 30 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2353, + "message": "Object literal may only specify known properties, and 'only_bind' does not exist in type '$$ComponentProps'.", + "range": { + "end": { + "character": 28, + "line": 31 + }, + "start": { + "character": 19, + "line": 31 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2322, + "message": "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\nTo mark a property as bindable: 'let { only_bind = $bindable() } = $props()'", + "range": { + "end": { + "character": 28, + "line": 31 + }, + "start": { + "character": 14, + "line": 31 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2353, + "message": "Object literal may only specify known properties, and 'only_bind' does not exist in type '$$ComponentProps'.", + "range": { + "end": { + "character": 24, + "line": 32 + }, + "start": { + "character": 15, + "line": 32 } }, "severity": 1, diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json index e441ca7e2..da73688bf 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expectedv2.json @@ -1,15 +1,32 @@ [ + { + "code": 2344, + "message": "Type 'typeof Runes__SvelteComponent_' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof Runes__SvelteComponent_' provides no match for the signature '(...args: any): any'.", + "range": { + "end": { + "character": 41, + "line": 12 + }, + "start": { + "character": 29, + "line": 12 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, { "code": 2353, "message": "Object literal may only specify known properties, and 'can_bind' does not exist in type '{ only_bind?: (() => boolean) | undefined; }'.", "range": { "end": { "character": 20, - "line": 20 + "line": 21 }, "start": { "character": 12, - "line": 20 + "line": 21 } }, "severity": 1, @@ -22,11 +39,11 @@ "range": { "end": { "character": 16, - "line": 21 + "line": 22 }, "start": { "character": 8, - "line": 21 + "line": 22 } }, "severity": 1, @@ -39,11 +56,11 @@ "range": { "end": { "character": 16, - "line": 22 + "line": 23 }, "start": { "character": 8, - "line": 22 + "line": 23 } }, "severity": 1, @@ -56,11 +73,28 @@ "range": { "end": { "character": 20, - "line": 25 + "line": 26 }, "start": { "character": 12, - "line": 25 + "line": 26 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + }, + { + "code": 2353, + "message": "Object literal may only specify known properties, and 'readonly' does not exist in type '{ only_bind?: (() => boolean) | undefined; }'.", + "range": { + "end": { + "character": 27, + "line": 30 + }, + "start": { + "character": 19, + "line": 30 } }, "severity": 1, diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte index 55de44f2d..dd51d79f5 100644 --- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte @@ -1,6 +1,7 @@ @@ -26,3 +27,7 @@ + + + + diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts index 4cbf2b980..fc9550b77 100644 --- a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts +++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts @@ -5,15 +5,16 @@ import { ExportedNames } from './nodes/ExportedNames'; import { ComponentDocumentation } from './nodes/ComponentDocumentation'; import { Generics } from './nodes/Generics'; import { surroundWithIgnoreComments } from '../utils/ignore'; +import { ComponentEvents } from './nodes/ComponentEvents'; export interface AddComponentExportPara { str: MagicString; canHaveAnyProp: boolean; /** - * If true, not fallback to `any` + * If strictEvents true, not fallback to `any` * -> all unknown events will throw a type error * */ - strictEvents: boolean; + events: ComponentEvents; isTsFile: boolean; usesAccessors: boolean; exportedNames: ExportedNames; @@ -41,7 +42,7 @@ export function addComponentExport(params: AddComponentExportPara) { } function addGenericsComponentExport({ - strictEvents, + events, canHaveAnyProp, exportedNames, componentDocumentation, @@ -70,7 +71,7 @@ class __sveltets_Render${genericsDef} { return ${props(true, canHaveAnyProp, exportedNames, `render${genericsRef}()`)}.props; } events() { - return ${events(strictEvents, `render${genericsRef}()`)}.events; + return ${_events(events.hasStrictEvents() || exportedNames.usesRunes(), `render${genericsRef}()`)}.events; } slots() { return render${genericsRef}().slots; @@ -94,15 +95,29 @@ ${ if (isSvelte5) { // Don't add props/events/slots type exports in dts mode for now, maybe someone asks for it to be back, // but it's safer to not do it for now to have more flexibility in the future. + let eventsSlotsType = []; + if (events.hasEvents() || !exportedNames.usesRunes()) { + eventsSlotsType.push(`$$events?: ${returnType('events')}`); + } + if (usesSlots) { + eventsSlotsType.push(`$$slots?: ${returnType('slots')}`); + eventsSlotsType.push(`children?: any`); + } const propsType = !canHaveAnyProp && exportedNames.hasNoProps() - ? `{$$events?: ${returnType('events')}${usesSlots ? `, $$slots?: ${returnType('slots')}, children?: any` : ''}}` - : `${returnType('props')} & {$$events?: ${returnType('events')}${usesSlots ? `, $$slots?: ${returnType('slots')}, children?: any` : ''}}`; + ? `{${eventsSlotsType.join(', ')}}` + : `${returnType('props')} & {${eventsSlotsType.join(', ')}}`; + const bindingsType = `ReturnType<__sveltets_Render${generics.toReferencesAnyString()}['bindings']>`; + + // Sadly, due to a combination of requirements and TypeScript limitations, we need to always create both a legacy class component and function component type. + // - Constraints: Need to support Svelte 4 class component types, therefore we need to use __sveltets_2_ensureComponent to transform function components to classes + // - Limitations: TypeScript is not able to preserve generics during said transformation (i.e. there's no way to express keeping the generic etc) + // TODO Svelte 6/7: Switch this around and not use new Component in svelte2tsx anymore, which means we can remove the legacy class component. We need something like _ensureFnComponent then. statement += `\ninterface $$IsomorphicComponent {\n` + ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` + ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` + - ` z_$$bindings?: ReturnType<__sveltets_Render${generics.toReferencesAnyString()}['bindings']>;\n` + + ` z_$$bindings?: ${bindingsType};\n` + `}\n` + `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` + surroundWithIgnoreComments( @@ -137,7 +152,7 @@ ${ } function addSimpleComponentExport({ - strictEvents, + events, isTsFile, canHaveAnyProp, exportedNames, @@ -154,7 +169,7 @@ function addSimpleComponentExport({ isTsFile, canHaveAnyProp, exportedNames, - events(strictEvents, 'render()') + _events(events.hasStrictEvents(), 'render()') ); const doc = componentDocumentation.getFormatted(); @@ -162,7 +177,11 @@ function addSimpleComponentExport({ let statement: string; if (mode === 'dts') { - if (isSvelte5) { + if (isSvelte5 && exportedNames.usesRunes() && !usesSlots && !events.hasEvents()) { + statement = + `\n${doc}const ${className || '$$Component'} = __sveltets_2_fn_component(render());\n` + + `export default ${className || '$$Component'};`; + } else if (isSvelte5) { // Inline definitions from Svelte shims; else dts files will reference the globals which will be unresolved statement = `\ninterface $$__sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { @@ -223,12 +242,18 @@ declare function $$__sveltets_2_isomorphic_component< } } else { if (isSvelte5) { - statement = - `\n${doc}const ${className || '$$Component'} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` + - surroundWithIgnoreComments( - `type ${className || '$$Component'} = InstanceType;\n` - ) + - `export default ${className || '$$Component'};`; + if (exportedNames.usesRunes() && !usesSlots && !events.hasEvents()) { + statement = + `\n${doc}const ${className || '$$Component'} = __sveltets_2_fn_component(render());\n` + + `export default ${className || '$$Component'};`; + } else { + statement = + `\n${doc}const ${className || '$$Component'} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` + + surroundWithIgnoreComments( + `type ${className || '$$Component'} = InstanceType;\n` + ) + + `export default ${className || '$$Component'};`; + } } else { statement = `\n\n${doc}export default class${ @@ -281,7 +306,7 @@ function addTypeExport( } } -function events(strictEvents: boolean, renderStr: string) { +function _events(strictEvents: boolean, renderStr: string) { return strictEvents ? renderStr : `__sveltets_2_with_any_event(${renderStr})`; } diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index e6410d661..91e0d6b5a 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -435,7 +435,7 @@ export function svelte2tsx( addComponentExport({ str, canHaveAnyProp: !exportedNames.uses$$Props && (uses$$props || uses$$restProps), - strictEvents: events.hasStrictEvents(), // TODO in Svelte 6 we should also apply strictEvents in runes mode + events, isTsFile: options?.isTsFile, exportedNames, usesAccessors, diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts index b2bb18e8e..a83dc9df0 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts @@ -77,6 +77,10 @@ export class ComponentEvents { this.componentEventsInterface.setComponentEventsInterface(node, this.str, astOffset); } + hasEvents(): boolean { + return this.eventsClass.events.size > 0; + } + hasStrictEvents(): boolean { return this.componentEventsInterface.isPresent() || this.strictEvents; } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index d461ecd66..3f5ec412a 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -793,7 +793,7 @@ export class ExportedNames { } hasPropsRune() { - return this.isSvelte5Plus && (this.$props.type || this.$props.comment); + return this.isSvelte5Plus && !!(this.$props.type || this.$props.comment); } checkGlobalsForRunes(globals: string[]) { diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index 70e6df540..71bfbac6d 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -220,11 +220,36 @@ declare type ATypedSvelteComponent = { * ``` */ declare type ConstructorOfATypedSvelteComponent = new (args: {target: any, props?: any}) => ATypedSvelteComponent +// Usage note: Cannot properly transform generic function components to class components due to TypeScript limitations declare function __sveltets_2_ensureComponent< - // @ts-ignore svelte.Component doesn't exist in Svelte 4 - T extends ConstructorOfATypedSvelteComponent | (typeof import('svelte') extends { mount: any } ? import('svelte').Component : never) | null | undefined - // @ts-ignore svelte.Component doesn't exist in Svelte 4 ->(type: T): NonNullable> ? typeof import('svelte').SvelteComponent : T : T>; + T extends + | ConstructorOfATypedSvelteComponent + | (typeof import('svelte') extends { mount: any } + ? // @ts-ignore svelte.Component doesn't exist in Svelte 4 + import('svelte').Component + : never) + | null + | undefined +>( + type: T +): NonNullable< + T extends ConstructorOfATypedSvelteComponent + ? T + : typeof import('svelte') extends { mount: any } + ? // @ts-ignore svelte.Component doesn't exist in Svelte 4 + T extends import('svelte').Component< + infer Props extends Record, + infer Exports extends Record, + infer Bindings extends string + > + ? new ( + options: import('svelte').ComponentConstructorOptions + ) => import('svelte').SvelteComponent & + Exports & { $$bindings: Bindings } + : never + : never +>; + declare function __sveltets_2_ensureArray | Iterable>(array: T): T extends ArrayLike ? U[] : T extends Iterable ? Iterable : any[]; type __sveltets_2_PropsWithChildren = Props & @@ -240,6 +265,11 @@ declare function __sveltets_2_runes_constructor(render: {props declare function __sveltets_$$bindings(...bindings: Bindings): Bindings[number]; +declare function __sveltets_2_fn_component< + Props extends Record, Exports extends Record, Bindings extends string + // @ts-ignore Svelte 5 only +>(klass: {props: Props, exports?: Exports, bindings?: Bindings }): import('svelte').Component; + interface __sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { new (options: import('svelte').ComponentConstructorOptions): import('svelte').SvelteComponent & { $$bindings?: Bindings } & Exports; (internal: unknown, props: Props extends Record ? {$$events?: Events, $$slots?: Slots} : Props & {$$events?: Events, $$slots?: Slots}): Exports & { $set?: any, $on?: any }; diff --git a/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts index 6c3b7250e..04808233f 100644 --- a/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts @@ -1,23 +1,7 @@ -interface $$__sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { - new (options: import('svelte').ComponentConstructorOptions): import('svelte').SvelteComponent & { - $$bindings?: Bindings; - } & Exports; - (internal: unknown, props: Props & { - $$events?: Events; - $$slots?: Slots; - }): Exports & { - $set?: any; - $on?: any; - }; - z_$$bindings?: Bindings; -} -declare const TestRunes: $$__sveltets_2_IsomorphicComponent<{ +declare const TestRunes: import("svelte").Component<{ foo: string; bar?: number; }, { - [evt: string]: CustomEvent; -}, {}, { baz: () => void; }, "bar">; -type TestRunes = InstanceType; export default TestRunes; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts index 6f01eae42..296b24ab4 100644 --- a/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts @@ -3,9 +3,7 @@ declare class __sveltets_Render, K extends keyof T foo: T; bar?: K; }; - events(): {} & { - [evt: string]: CustomEvent; - }; + events(): {}; slots(): {}; bindings(): "bar"; exports(): { @@ -16,9 +14,7 @@ interface $$IsomorphicComponent { new , K extends keyof T>(options: import('svelte').ComponentConstructorOptions['props']>>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']>; } & ReturnType<__sveltets_Render['exports']>; - , K extends keyof T>(internal: unknown, props: ReturnType<__sveltets_Render['props']> & { - $$events?: ReturnType<__sveltets_Render['events']>; - }): ReturnType<__sveltets_Render['exports']>; + , K extends keyof T>(internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>; z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; } declare const TestRunes: $$IsomorphicComponent; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts index 6c3b7250e..04808233f 100644 --- a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts @@ -1,23 +1,7 @@ -interface $$__sveltets_2_IsomorphicComponent = any, Events extends Record = any, Slots extends Record = any, Exports = {}, Bindings = string> { - new (options: import('svelte').ComponentConstructorOptions): import('svelte').SvelteComponent & { - $$bindings?: Bindings; - } & Exports; - (internal: unknown, props: Props & { - $$events?: Events; - $$slots?: Slots; - }): Exports & { - $set?: any; - $on?: any; - }; - z_$$bindings?: Bindings; -} -declare const TestRunes: $$__sveltets_2_IsomorphicComponent<{ +declare const TestRunes: import("svelte").Component<{ foo: string; bar?: number; }, { - [evt: string]: CustomEvent; -}, {}, { baz: () => void; }, "bar">; -type TestRunes = InstanceType; export default TestRunes; diff --git a/packages/svelte2tsx/test/helpers.ts b/packages/svelte2tsx/test/helpers.ts index ceec3b03d..7fa3f5acf 100644 --- a/packages/svelte2tsx/test/helpers.ts +++ b/packages/svelte2tsx/test/helpers.ts @@ -230,7 +230,10 @@ export function test_samples(dir: string, transform: TransformSampleFn, js: 'js' if (sample.name.endsWith('.v5') && !isSvelte5Plus) continue; const svelteFile = sample.find_file('*.svelte'); - const expectedFile = isSvelte5Plus ? `expected-svelte5.${js}` : `expectedv2.${js}`; + const expectedFile = + isSvelte5Plus && !sample.name.endsWith('.v5') + ? `expected-svelte5.${js}` + : `expectedv2.${js}`; const config = { filename: svelteFile, sampleName: sample.name, diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts index 6406a56c2..5c395efb6 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script3.v5/expectedv2.ts @@ -9,7 +9,7 @@ async () => { { svelteHTML.createElement("h1", {}); world; }}; -return { props: {world: world}, slots: {}, events: {} }} - -export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(['world'], __sveltets_2_with_any_event(render()))) { -} \ No newline at end of file +return { props: {world: world}, exports: {}, bindings: "", slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(['world'], __sveltets_2_with_any_event(render()))); +/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; +/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts index 4b9dd9437..b5c446abe 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts index c4de2d23e..5c4ac5f23 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$bindings('b'), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts index 05b04da66..8263d1173 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts @@ -9,6 +9,5 @@ async () => { state; derived;}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts index cd5d6aca0..0a77ec5d6 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-only-export.v5/expectedv2.ts @@ -8,6 +8,5 @@ async () => { x;}; return { props: /** @type {Record} */ ({}), exports: /** @type {{foo: typeof foo}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts index 06e1497b4..328328888 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes.v5/expectedv2.ts @@ -8,6 +8,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts index fe4d98e31..6eceb26ee 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune-no-changes.v5/expectedv2.ts @@ -8,6 +8,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {{snapshot: typeof snapshot}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts index b5014a5d2..eeb3e8864 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/sveltekit-autotypes-$props-rune.v5/expectedv2.ts @@ -6,6 +6,5 @@ ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {{snapshot: typeof snapshot}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts index eab76fea5..faa1d533b 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts index 90775addb..ce3a575fe 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts @@ -5,6 +5,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {}, bindings: __sveltets_$$bindings('b', 'c'), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts index b7e2a8765..d87b3a3ee 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts @@ -12,7 +12,7 @@ class __sveltets_Render { return render().props; } events() { - return __sveltets_2_with_any_event(render()).events; + return render().events; } slots() { return render().slots; @@ -23,7 +23,7 @@ class __sveltets_Render { interface $$IsomorphicComponent { new (options: import('svelte').ComponentConstructorOptions['props']>>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']> } & ReturnType<__sveltets_Render['exports']>; - (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {$$events?: ReturnType<__sveltets_Render['events']>}): ReturnType<__sveltets_Render['exports']>; + (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>; z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; } const Input__SvelteComponent_: $$IsomorphicComponent = null as any; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts index 68212de3d..d34195b64 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-with-slot.v5/expectedv2.ts @@ -16,7 +16,7 @@ class __sveltets_Render { return render().props; } events() { - return __sveltets_2_with_any_event(render()).events; + return render().events; } slots() { return render().slots; @@ -27,7 +27,7 @@ class __sveltets_Render { interface $$IsomorphicComponent { new (options: import('svelte').ComponentConstructorOptions['props']>& {children?: any}>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']> } & ReturnType<__sveltets_Render['exports']>; - (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {$$events?: ReturnType<__sveltets_Render['events']>, $$slots?: ReturnType<__sveltets_Render['slots']>, children?: any}): ReturnType<__sveltets_Render['exports']>; + (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {$$slots?: ReturnType<__sveltets_Render['slots']>, children?: any}): ReturnType<__sveltets_Render['exports']>; z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; } const Input__SvelteComponent_: $$IsomorphicComponent = null as any; 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 12e191193..8e4065235 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts @@ -7,6 +7,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Input__SvelteComponent_; \ No newline at end of file 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 522d58fd2..df7501a9a 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 @@ -6,6 +6,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: any }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts index 04db7b55e..ae77eca3f 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts @@ -6,6 +6,5 @@ ; async () => {}; return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: typeof snapshot }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} -const Page__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_with_any_event(render())); -/*Ωignore_startΩ*/type Page__SvelteComponent_ = InstanceType; -/*Ωignore_endΩ*/export default Page__SvelteComponent_; \ No newline at end of file +const Page__SvelteComponent_ = __sveltets_2_fn_component(render()); +export default Page__SvelteComponent_; \ No newline at end of file From 7c76ec560698e9db6343329bd978d5a24cd03a90 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:46:16 +0800 Subject: [PATCH 23/25] fix: check project files update more aggressively before assigning service (#2518) #2516 Most of the time, the didOpen request is earlier than the watcher event. So if the file doesn't exist in the GlobalSnapshotManager we manually invoke the project file update check. This won't cause 2 project files check because if the file already is a project file we won't check project files. --- .../src/plugins/typescript/service.ts | 22 ++++++++++++--- .../test/plugins/typescript/service.test.ts | 27 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 0e0d0f27a..caf8f0350 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -40,7 +40,7 @@ export interface LanguageServiceContainer { deleteSnapshot(filePath: string): void; invalidateModuleCache(filePath: string[]): void; scheduleProjectFileUpdate(watcherNewFiles: string[]): void; - ensureProjectFileUpdates(): void; + ensureProjectFileUpdates(newFile?: string): void; updateTsOrJsFile(fileName: string, changes?: TextDocumentContentChangeEvent[]): void; /** * Checks if a file is present in the project. @@ -225,7 +225,7 @@ export async function getService( service: LanguageServiceContainer, triedTsConfig: Set ): Promise { - service.ensureProjectFileUpdates(); + service.ensureProjectFileUpdates(path); if (service.snapshotManager.isProjectFile(path)) { return service; } @@ -648,9 +648,23 @@ async function createLanguageService( } } - function ensureProjectFileUpdates(): void { + function ensureProjectFileUpdates(newFile?: string): void { const info = parsedTsConfigInfo.get(tsconfigPath); - if (!info || !info.pendingProjectFileUpdate) { + if (!info) { + return; + } + + if ( + newFile && + !info.pendingProjectFileUpdate && + // no global snapshots yet when initial load pending + !snapshotManager.isProjectFile(newFile) && + !docContext.globalSnapshotsManager.get(newFile) + ) { + scheduleProjectFileUpdate([newFile]); + } + + if (!info.pendingProjectFileUpdate) { return; } const projectFileCountBefore = snapshotManager.getProjectFileNames().length; diff --git a/packages/language-server/test/plugins/typescript/service.test.ts b/packages/language-server/test/plugins/typescript/service.test.ts index 71db7f064..e0aa9ffd4 100644 --- a/packages/language-server/test/plugins/typescript/service.test.ts +++ b/packages/language-server/test/plugins/typescript/service.test.ts @@ -657,6 +657,33 @@ describe('service', () => { assert.deepStrictEqual(findError(ls2), undefined); }); + it('assigns newly created files to the right service before the watcher trigger', async () => { + const dirPath = getRandomVirtualDirPath(testDir); + const { virtualSystem, lsDocumentContext, rootUris } = setup(); + + const tsconfigPath = path.join(dirPath, 'tsconfig.json'); + virtualSystem.writeFile( + tsconfigPath, + JSON.stringify({ + compilerOptions: {} + }) + ); + + const svelteFilePath = path.join(dirPath, 'random.svelte'); + + virtualSystem.writeFile(svelteFilePath, ''); + + const ls = await getService(svelteFilePath, rootUris, lsDocumentContext); + + assert.equal(normalizePath(ls.tsconfigPath), normalizePath(tsconfigPath)); + + const svelteFilePath2 = path.join(dirPath, 'random2.svelte'); + virtualSystem.writeFile(svelteFilePath2, ''); + + const ls2 = await getService(svelteFilePath2, rootUris, lsDocumentContext); + assert.equal(normalizePath(ls2.tsconfigPath), normalizePath(tsconfigPath)); + }); + function getSemanticDiagnosticsMessages(ls: LanguageServiceContainer, filePath: string) { return ls .getService() From fc2144b82a34298f9046d929b94009f06e144749 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 26 Sep 2024 03:31:53 -0700 Subject: [PATCH 24/25] chore: upgrade to chokidar 4 (#2502) Also remove long-obsolete sapper handling --- packages/language-server/package.json | 2 +- .../src/lib/FallbackWatcher.ts | 7 +- packages/svelte-check/package.json | 2 +- packages/svelte-check/src/index.ts | 76 +++++++++++++------ pnpm-lock.yaml | 22 +++++- 5 files changed, 72 insertions(+), 37 deletions(-) diff --git a/packages/language-server/package.json b/packages/language-server/package.json index f3702c47b..2963ffe11 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -52,7 +52,7 @@ "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@vscode/emmet-helper": "2.8.4", - "chokidar": "^3.4.1", + "chokidar": "^4.0.1", "estree-walker": "^2.0.1", "fdir": "^6.2.0", "lodash": "^4.17.21", diff --git a/packages/language-server/src/lib/FallbackWatcher.ts b/packages/language-server/src/lib/FallbackWatcher.ts index 81ec47349..4af7e71ff 100644 --- a/packages/language-server/src/lib/FallbackWatcher.ts +++ b/packages/language-server/src/lib/FallbackWatcher.ts @@ -25,12 +25,7 @@ export class FallbackWatcher { this.watcher = watch( workspacePaths.map((workspacePath) => join(workspacePath, recursivePatterns)), { - ignored: (path: string) => - gitOrNodeModules.test(path) && - // Handle Sapper's alias mapping - !path.includes('src/node_modules') && - !path.includes('src\\node_modules'), - + ignored: gitOrNodeModules, // typescript would scan the project files on init. // We only need to know what got updated. ignoreInitial: true, diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 634a029f6..b49d294a2 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^3.4.1", + "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index 866d23bf3..f2aeb79fb 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -32,27 +32,7 @@ async function openAllDocuments( ) { const offset = workspaceUri.fsPath.length + 1; // We support a very limited subset of glob patterns: You can only have ** at the end or the start - const ignored: Array<(path: string) => boolean> = filePathsToIgnore.map((i) => { - if (i.endsWith('**')) i = i.slice(0, -2); - - if (i.startsWith('**')) { - i = i.slice(2); - - if (i.includes('*')) - throw new Error( - 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' - ); - - return (path) => path.includes(i); - } - - if (i.includes('*')) - throw new Error( - 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' - ); - - return (path) => path.startsWith(i); - }); + const ignored = createIgnored(filePathsToIgnore); const isIgnored = (path: string) => { path = path.slice(offset); for (const i of ignored) { @@ -84,6 +64,30 @@ async function openAllDocuments( } } +function createIgnored(filePathsToIgnore: string[]): Array<(path: string) => boolean> { + return filePathsToIgnore.map((i) => { + if (i.endsWith('**')) i = i.slice(0, -2); + + if (i.startsWith('**')) { + i = i.slice(2); + + if (i.includes('*')) + throw new Error( + 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' + ); + + return (path) => path.includes(i); + } + + if (i.includes('*')) + throw new Error( + 'Invalid svelte-check --ignore pattern: Only ** at the start or end is supported' + ); + + return (path) => path.startsWith(i); + }); +} + async function getDiagnostics( workspaceUri: URI, writer: Writer, @@ -149,10 +153,32 @@ class DiagnosticsWatcher { filePathsToIgnore: string[], ignoreInitialAdd: boolean ) { - watch(`${workspaceUri.fsPath}/**/*.{svelte,d.ts,ts,js,jsx,tsx,mjs,cjs,mts,cts}`, { - ignored: ['node_modules', 'vite.config.{js,ts}.timestamp-*'] - .concat(filePathsToIgnore) - .map((ignore) => path.join(workspaceUri.fsPath, ignore)), + const fileEnding = /\.(svelte|d\.ts|ts|js|jsx|tsx|mjs|cjs|mts|cts)$/; + const viteConfigRegex = /vite\.config\.(js|ts)\.timestamp-/; + const userIgnored = createIgnored(filePathsToIgnore); + const offset = workspaceUri.fsPath.length + 1; + + watch(workspaceUri.fsPath, { + ignored: (path, stats) => { + if ( + path.includes('node_modules') || + path.includes('.git') || + (stats?.isFile() && (!fileEnding.test(path) || viteConfigRegex.test(path))) + ) { + return true; + } + + if (userIgnored.length !== 0) { + path = path.slice(offset); + for (const i of userIgnored) { + if (i(path)) { + return true; + } + } + } + + return false; + }, ignoreInitial: ignoreInitialAdd }) .on('add', (path) => this.updateDocument(path, true)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1bc6d8f0..f7d19793f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,8 +31,8 @@ importers: specifier: 2.8.4 version: 2.8.4 chokidar: - specifier: ^3.4.1 - version: 3.5.3 + specifier: ^4.0.1 + version: 4.0.1 estree-walker: specifier: ^2.0.1 version: 2.0.2 @@ -113,8 +113,8 @@ importers: specifier: ^0.3.25 version: 0.3.25 chokidar: - specifier: ^3.4.1 - version: 3.5.3 + specifier: ^4.0.1 + version: 4.0.1 fdir: specifier: ^6.2.0 version: 6.2.0 @@ -620,6 +620,10 @@ packages: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.1: + resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} + engines: {node: '>= 14.16.0'} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -1108,6 +1112,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.0.1: + resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==} + engines: {node: '>= 14.16.0'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1684,6 +1692,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.1: + dependencies: + readdirp: 4.0.1 + clean-stack@2.2.0: {} cliui@7.0.4: @@ -2162,6 +2174,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.0.1: {} + require-directory@2.1.1: {} resolve@1.22.2: From 4dfb988a3f7223f9d1dfe2bd5a9ff9edb6520a89 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 27 Sep 2024 22:56:44 +0200 Subject: [PATCH 25/25] fix: relax component constructor type (#2524) Since , we're no longer adding the `[evt: string]: CustomEvent` index signature to components in runes mode not using `createEventDispatcher`. This revealed a type bug in our `ATypedSvelteComponent` type. It was to restricting, because TypeScript will resolve the empty event object to a handler with callback type `never`, which then means the `__sveltets_2_ensureComponent` generic no longer picks the right type, resulting in generics not being resolved properly. Fix this by relaxing the constraints. Fixes #2523 --- .../generics-runes.v5/ValueComponent.svelte | 6 ++++++ .../generics-runes.v5/expectedv2.json | 19 +++++++++++++++++++ .../fixtures/generics-runes.v5/input.svelte | 11 +++++++++++ packages/svelte2tsx/svelte-shims-v4.d.ts | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/ValueComponent.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/input.svelte diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/ValueComponent.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/ValueComponent.svelte new file mode 100644 index 000000000..7d2dd5ebc --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/ValueComponent.svelte @@ -0,0 +1,6 @@ + + +{value} +{defaultValue} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/expectedv2.json new file mode 100644 index 000000000..90f0fd771 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/expectedv2.json @@ -0,0 +1,19 @@ +[ + { + "code": 2322, + "message": "Type 'number' is not assignable to type 'string'.", + "range": { + "end": { + "character": 36, + "line": 10 + }, + "start": { + "character": 24, + "line": 10 + } + }, + "severity": 1, + "source": "ts", + "tags": [] + } +] diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/input.svelte new file mode 100644 index 000000000..d2442ff89 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/generics-runes.v5/input.svelte @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts index 71bfbac6d..2d36a539d 100644 --- a/packages/svelte2tsx/svelte-shims-v4.d.ts +++ b/packages/svelte2tsx/svelte-shims-v4.d.ts @@ -207,7 +207,7 @@ declare type ATypedSvelteComponent = { */ $$slot_def: any; - $on(event: string, handler: ((e: any) => any) | null | undefined): () => void; + $on(event: string, handler: any): () => void; } /** * Ambient type only used for intellisense, DO NOT USE IN YOUR PROJECT.