diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 7933acf96..eafda89b6 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -64,8 +64,8 @@ "svelte2tsx": "workspace:~", "typescript": "^5.8.2", "typescript-auto-import-cache": "^0.3.5", - "vscode-css-languageservice": "~6.3.2", - "vscode-html-languageservice": "~5.3.2", + "vscode-css-languageservice": "~6.3.5", + "vscode-html-languageservice": "~5.4.0", "vscode-languageserver": "9.0.1", "vscode-languageserver-protocol": "3.17.5", "vscode-languageserver-types": "3.17.5", diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 581a46d8d..244ae4e12 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -1,3 +1,4 @@ +import { internalHelpers } from 'svelte2tsx'; import ts from 'typescript'; import { CancellationToken, @@ -24,6 +25,7 @@ import { } from '../../../lib/documents'; import { LSConfigManager } from '../../../ls-config'; import { + createGetCanonicalFileName, flatten, getIndent, isNotNullOrUndefined, @@ -37,6 +39,7 @@ import { import { CodeActionsProvider } from '../../interfaces'; import { DocumentSnapshot, SvelteDocumentSnapshot } from '../DocumentSnapshot'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; +import { LanguageServiceContainer } from '../service'; import { changeSvelteComponentName, convertRange, @@ -44,6 +47,7 @@ import { toGeneratedSvelteComponentName } from '../utils'; import { CompletionsProviderImpl } from './CompletionProvider'; +import { DiagnosticCode } from './DiagnosticsProvider'; import { findClosestContainingNode, FormatCodeBasis, @@ -53,15 +57,12 @@ import { isTextSpanInGeneratedCode, SnapshotMap } from './utils'; -import { DiagnosticCode } from './DiagnosticsProvider'; -import { createGetCanonicalFileName } from '../../../utils'; -import { LanguageServiceContainer } from '../service'; -import { internalHelpers } from 'svelte2tsx'; /** * TODO change this to protocol constant if it's part of the protocol */ export const SORT_IMPORT_CODE_ACTION_KIND = 'source.sortImports'; +export const ADD_MISSING_IMPORTS_CODE_ACTION_KIND = 'source.addMissingImports'; interface RefactorArgs { type: 'refactor'; @@ -121,6 +122,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ); } + if (context.only?.[0] === ADD_MISSING_IMPORTS_CODE_ACTION_KIND) { + return await this.addMissingImports(document, cancellationToken); + } + // for source action command (all source.xxx) // vscode would show different source code action kinds to choose from if (context.only?.[0] === CodeActionKind.Source) { @@ -130,7 +135,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { document, cancellationToken, /**skipDestructiveCodeActions */ true - )) + )), + ...(await this.addMissingImports(document, cancellationToken)) ]; } @@ -377,7 +383,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { if (editForThisFile?.edits.length) { const [first] = editForThisFile.edits; first.newText = - getNewScriptStartTag(this.configManager.getConfig()) + + getNewScriptStartTag(this.configManager.getConfig(), formatCodeBasis.newLine) + formatCodeBasis.baseIndent + first.newText.trimStart(); @@ -1553,4 +1559,48 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { private async getLSAndTSDoc(document: Document) { return this.lsAndTsDocResolver.getLSAndTSDoc(document); } + + private async addMissingImports( + document: Document, + cancellationToken?: CancellationToken + ): Promise { + // Re-introduce LS/TSDoc resolution and diagnostic check + const { lang, tsDoc } = await this.getLSAndTSDoc(document); + if (cancellationToken?.isCancellationRequested) { + return []; + } + + // Check if there are any relevant "cannot find name" diagnostics + const diagnostics = lang.getSemanticDiagnostics(tsDoc.filePath); + const hasMissingImports = diagnostics.some( + (diag) => + (diag.code === DiagnosticCode.CANNOT_FIND_NAME || + diag.code === DiagnosticCode.CANNOT_FIND_NAME_X_DID_YOU_MEAN_Y) && + // Ensure the diagnostic is not in generated code + !isTextSpanInGeneratedCode(tsDoc.getFullText(), { + start: diag.start ?? 0, + length: diag.length ?? 0 + }) + ); + + // Only return the action if there are potential imports to add + if (!hasMissingImports) { + return []; + } + + // If imports might be needed, create the deferred action + const codeAction = CodeAction.create( + FIX_IMPORT_FIX_DESCRIPTION, + ADD_MISSING_IMPORTS_CODE_ACTION_KIND + ); + + const data: QuickFixAllResolveInfo = { + uri: document.uri, + fixName: FIX_IMPORT_FIX_NAME, + fixId: FIX_IMPORT_FIX_ID + }; + codeAction.data = data; + + return [codeAction]; + } } diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index 48b6db53f..bc1764d87 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -595,7 +595,8 @@ export class CompletionsProviderImpl implements CompletionsProvider ({ label: name, kind: CompletionItemKind.Property, - textEdit: TextEdit.replace(this.cloneRange(replacementRange), name) + textEdit: TextEdit.replace(this.cloneRange(replacementRange), name), + commitCharacters: [] })); } @@ -1131,9 +1132,17 @@ export class CompletionsProviderImpl implements CompletionsProvider${newLine}` + `${getNewScriptStartTag(config, newLine)}${newText}${newLine}` ); } diff --git a/packages/language-server/src/plugins/typescript/features/utils.ts b/packages/language-server/src/plugins/typescript/features/utils.ts index a8b0229e3..954ef03bc 100644 --- a/packages/language-server/src/plugins/typescript/features/utils.ts +++ b/packages/language-server/src/plugins/typescript/features/utils.ts @@ -429,10 +429,10 @@ export function findChildOfKind(node: ts.Node, kind: ts.SyntaxKind): ts.Node | u } } -export function getNewScriptStartTag(lsConfig: Readonly) { +export function getNewScriptStartTag(lsConfig: Readonly, newLine: string) { const lang = lsConfig.svelte.defaultScriptLanguage; const scriptLang = lang === 'none' ? '' : ` lang="${lang}"`; - return `${ts.sys.newLine}`; + return `${newLine}`; } export function checkRangeMappingWithGeneratedSemi( diff --git a/packages/language-server/src/plugins/typescript/module-loader.ts b/packages/language-server/src/plugins/typescript/module-loader.ts index 6c96ec172..a681c658a 100644 --- a/packages/language-server/src/plugins/typescript/module-loader.ts +++ b/packages/language-server/src/plugins/typescript/module-loader.ts @@ -164,8 +164,13 @@ export function createSvelteModuleLoader( >(); const impliedNodeFormatResolver = new ImpliedNodeFormatResolver(tsSystem); - const failedPathToContainingFile = new FileMap(); - const failedLocationInvalidated = new FileSet(); + const resolutionWithFailedLookup = new Set< + ts.ResolvedModuleWithFailedLookupLocations & { + files?: Set; + } + >(); + const failedLocationInvalidated = new FileSet(tsSystem.useCaseSensitiveFileNames); + const pendingFailedLocationCheck = new FileSet(tsSystem.useCaseSensitiveFileNames); return { svelteFileExists: svelteSys.svelteFileExists, @@ -179,16 +184,7 @@ export function createSvelteModuleLoader( deleteUnresolvedResolutionsFromCache: (path: string) => { svelteSys.deleteFromCache(path); moduleCache.deleteUnresolvedResolutionsFromCache(path); - - const previousTriedButFailed = failedPathToContainingFile.get(path); - - if (!previousTriedButFailed) { - return; - } - - for (const containingFile of previousTriedButFailed) { - failedLocationInvalidated.add(containingFile); - } + pendingFailedLocationCheck.add(path); tsModuleCache.clear(); typeReferenceCache.clear(); @@ -197,7 +193,8 @@ export function createSvelteModuleLoader( resolveTypeReferenceDirectiveReferences, mightHaveInvalidatedResolutions, clearPendingInvalidations, - getModuleResolutionCache: () => tsModuleCache + getModuleResolutionCache: () => tsModuleCache, + invalidateFailedLocationResolution }; function resolveModuleNames( @@ -222,11 +219,7 @@ export function createSvelteModuleLoader( options ); - resolvedModule?.failedLookupLocations?.forEach((failedLocation) => { - const failedPaths = failedPathToContainingFile.get(failedLocation) ?? new FileSet(); - failedPaths.add(containingFile); - failedPathToContainingFile.set(failedLocation, failedPaths); - }); + cacheResolutionWithFailedLookup(resolvedModule, containingFile); moduleCache.set(moduleName, containingFile, resolvedModule?.resolvedModule); return resolvedModule?.resolvedModule; @@ -333,5 +326,46 @@ export function createSvelteModuleLoader( function clearPendingInvalidations() { moduleCache.clearPendingInvalidations(); failedLocationInvalidated.clear(); + pendingFailedLocationCheck.clear(); + } + + function cacheResolutionWithFailedLookup( + resolvedModule: ts.ResolvedModuleWithFailedLookupLocations & { + files?: Set; + }, + containingFile: string + ) { + if (!resolvedModule.failedLookupLocations?.length) { + return; + } + + // The resolvedModule object will be reused in different files. A bit hacky, but TypeScript also does this. + // https://github.com/microsoft/TypeScript/blob/11e79327598db412a161616849041487673fadab/src/compiler/resolutionCache.ts#L1103 + resolvedModule.files ??= new Set(); + resolvedModule.files.add(containingFile); + resolutionWithFailedLookup.add(resolvedModule); + } + + function invalidateFailedLocationResolution() { + resolutionWithFailedLookup.forEach((resolvedModule) => { + if ( + !resolvedModule.resolvedModule || + !resolvedModule.files || + !resolvedModule.failedLookupLocations + ) { + return; + } + for (const location of resolvedModule.failedLookupLocations) { + if (pendingFailedLocationCheck.has(location)) { + moduleCache.delete(resolvedModule.resolvedModule.resolvedFileName); + resolvedModule.files?.forEach((file) => { + failedLocationInvalidated.add(file); + }); + break; + } + } + }); + + pendingFailedLocationCheck.clear(); } } diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index a45f04ecd..7ceec495e 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -89,10 +89,10 @@ declare module 'typescript' { resolutionDiagnostics?: ts.Diagnostic[]; /** * @internal - * Used to issue a diagnostic if typings for a non-relative import couldn't be found - * while respecting package.json `exports`, but were found when disabling `exports`. + * Used to issue a better diagnostic when an unresolvable module may + * have been resolvable under different module resolution settings. */ - node10Result?: string; + alternateResult?: string; } } @@ -527,10 +527,11 @@ async function createLanguageService( function invalidateModuleCache(filePaths: string[]) { for (const filePath of filePaths) { - svelteModuleLoader.deleteFromModuleCache(filePath); - svelteModuleLoader.deleteUnresolvedResolutionsFromCache(filePath); + const normalizedPath = normalizePath(filePath); + svelteModuleLoader.deleteFromModuleCache(normalizedPath); + svelteModuleLoader.deleteUnresolvedResolutionsFromCache(normalizedPath); - scheduleUpdate(filePath); + scheduleUpdate(normalizedPath); } } @@ -974,6 +975,7 @@ async function createLanguageService( return; } + svelteModuleLoader.invalidateFailedLocationResolution(); const oldProgram = project?.program; let program: ts.Program | undefined; try { @@ -981,15 +983,14 @@ async function createLanguageService( } finally { // mark as clean even if the update fails, at least we can still try again next time there is a change dirty = false; + compilerHost = undefined; + svelteModuleLoader.clearPendingInvalidations(); } - svelteModuleLoader.clearPendingInvalidations(); if (project) { project.program = program; } - compilerHost = undefined; - if (!skipSvelteInputCheck) { const svelteConfigDiagnostics = checkSvelteInput(program, projectConfig); const codes = svelteConfigDiagnostics.map((d) => d.code); diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index f1aa52ca0..af5f92a60 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -45,7 +45,10 @@ import { debounceThrottle, isNotNullOrUndefined, normalizeUri, urlToPath } from import { FallbackWatcher } from './lib/FallbackWatcher'; import { configLoader } from './lib/documents/configLoader'; import { setIsTrusted } from './importPackage'; -import { SORT_IMPORT_CODE_ACTION_KIND } from './plugins/typescript/features/CodeActionsProvider'; +import { + SORT_IMPORT_CODE_ACTION_KIND, + ADD_MISSING_IMPORTS_CODE_ACTION_KIND +} from './plugins/typescript/features/CodeActionsProvider'; import { createLanguageServices } from './plugins/css/service'; import { FileSystemProvider } from './plugins/css/FileSystemProvider'; @@ -270,6 +273,7 @@ export function startServer(options?: LSOptions) { CodeActionKind.QuickFix, CodeActionKind.SourceOrganizeImports, SORT_IMPORT_CODE_ACTION_KIND, + ADD_MISSING_IMPORTS_CODE_ACTION_KIND, ...(clientSupportApplyEditCommand ? [CodeActionKind.Refactor] : []) ].filter( clientSupportedCodeActionKinds && diff --git a/packages/language-server/test/plugins/css/CSSPlugin.test.ts b/packages/language-server/test/plugins/css/CSSPlugin.test.ts index 5e9c4bf32..9a82c53ff 100644 --- a/packages/language-server/test/plugins/css/CSSPlugin.test.ts +++ b/packages/language-server/test/plugins/css/CSSPlugin.test.ts @@ -72,8 +72,8 @@ describe('CSS Plugin', () => { kind: 'markdown', value: "Specifies the height of the content area, padding area or border area \\(depending on 'box\\-sizing'\\) of certain boxes\\.\n\n" + - '(Edge 12, Firefox 1, Safari 1, Chrome 1, IE 4, Opera 7)\n\n' + - 'Syntax: auto | <length> | <percentage> | min\\-content | max\\-content | fit\\-content | fit\\-content\\(<length\\-percentage>\\)\n\n' + + '![Baseline icon](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCA1NDAgMzAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzdHlsZT4KICAgIC5ncmVlbi1zaGFwZSB7CiAgICAgIGZpbGw6ICNDNEVFRDA7IC8qIExpZ2h0IG1vZGUgKi8KICAgIH0KCiAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBkYXJrKSB7CiAgICAgIC5ncmVlbi1zaGFwZSB7CiAgICAgICAgZmlsbDogIzEyNTIyNTsgLyogRGFyayBtb2RlICovCiAgICAgIH0KICAgIH0KICA8L3N0eWxlPgogIDxwYXRoIGQ9Ik00MjAgMzBMMzkwIDYwTDQ4MCAxNTBMMzkwIDI0MEwzMzAgMTgwTDMwMCAyMTBMMzkwIDMwMEw1NDAgMTUwTDQyMCAzMFoiIGNsYXNzPSJncmVlbi1zaGFwZSIvPgogIDxwYXRoIGQ9Ik0xNTAgMEwzMCAxMjBMNjAgMTUwTDE1MCA2MEwyMTAgMTIwTDI0MCA5MEwxNTAgMFoiIGNsYXNzPSJncmVlbi1zaGFwZSIvPgogIDxwYXRoIGQ9Ik0zOTAgMEw0MjAgMzBMMTUwIDMwMEwwIDE1MEwzMCAxMjBMMTUwIDI0MEwzOTAgMFoiIGZpbGw9IiMxRUE0NDYiLz4KPC9zdmc+) _Widely available across major browsers (Baseline since 2015)_\n\n' + + 'Syntax: auto | <length\\-percentage \\[0,∞\\]> | min\\-content | max\\-content | fit\\-content | fit\\-content\\(<length\\-percentage \\[0,∞\\]>\\) | <calc\\-size\\(\\)> | <anchor\\-size\\(\\)>\n\n' + '[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/height)' }, range: Range.create(0, 12, 0, 24) @@ -105,7 +105,8 @@ describe('CSS Plugin', () => { documentation: { kind: 'markdown', value: - 'Defines character set of the document\\.\n\n(Edge 12, Firefox 1, Safari 4, Chrome 2, IE 5, Opera 9)\n\n' + + 'Defines character set of the document\\.\n\n' + + '![Baseline icon](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCA1NDAgMzAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzdHlsZT4KICAgIC5ncmVlbi1zaGFwZSB7CiAgICAgIGZpbGw6ICNDNEVFRDA7IC8qIExpZ2h0IG1vZGUgKi8KICAgIH0KCiAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBkYXJrKSB7CiAgICAgIC5ncmVlbi1zaGFwZSB7CiAgICAgICAgZmlsbDogIzEyNTIyNTsgLyogRGFyayBtb2RlICovCiAgICAgIH0KICAgIH0KICA8L3N0eWxlPgogIDxwYXRoIGQ9Ik00MjAgMzBMMzkwIDYwTDQ4MCAxNTBMMzkwIDI0MEwzMzAgMTgwTDMwMCAyMTBMMzkwIDMwMEw1NDAgMTUwTDQyMCAzMFoiIGNsYXNzPSJncmVlbi1zaGFwZSIvPgogIDxwYXRoIGQ9Ik0xNTAgMEwzMCAxMjBMNjAgMTUwTDE1MCA2MEwyMTAgMTIwTDI0MCA5MEwxNTAgMFoiIGNsYXNzPSJncmVlbi1zaGFwZSIvPgogIDxwYXRoIGQ9Ik0zOTAgMEw0MjAgMzBMMTUwIDMwMEwwIDE1MEwzMCAxMjBMMTUwIDI0MEwzOTAgMFoiIGZpbGw9IiMxRUE0NDYiLz4KPC9zdmc+) _Widely available across major browsers (Baseline since 2015)_\n\n' + '[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/@charset)' }, textEdit: TextEdit.insert(Position.create(0, 7), '@charset'), @@ -344,6 +345,38 @@ describe('CSS Plugin', () => { }, newText: 'hwb(240 0% -25400%)' } + }, + { + label: 'lab(3880.51% 6388.69 -8701.22)', + textEdit: { + newText: 'lab(3880.51% 6388.69 -8701.22)', + range: { + end: { + character: 21, + line: 0 + }, + start: { + character: 17, + line: 0 + } + } + } + }, + { + label: 'lch(3880.51% 10794.75 306.29)', + textEdit: { + newText: 'lch(3880.51% 10794.75 306.29)', + range: { + end: { + character: 21, + line: 0 + }, + start: { + character: 17, + line: 0 + } + } + } } ]); }); diff --git a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts index 4a45b6310..eff038a11 100644 --- a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts +++ b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts @@ -35,7 +35,10 @@ describe('HTML Plugin', () => { assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 2)), { contents: { kind: 'markdown', - value: 'The h1 element represents a section heading.\n\n[MDN Reference](https://developer.mozilla.org/docs/Web/HTML/Element/Heading_Elements)' + value: + 'The h1 element represents a section heading.\n\n' + + '![Baseline icon](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCA1NDAgMzAwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzdHlsZT4KICAgIC5ncmVlbi1zaGFwZSB7CiAgICAgIGZpbGw6ICNDNEVFRDA7IC8qIExpZ2h0IG1vZGUgKi8KICAgIH0KCiAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBkYXJrKSB7CiAgICAgIC5ncmVlbi1zaGFwZSB7CiAgICAgICAgZmlsbDogIzEyNTIyNTsgLyogRGFyayBtb2RlICovCiAgICAgIH0KICAgIH0KICA8L3N0eWxlPgogIDxwYXRoIGQ9Ik00MjAgMzBMMzkwIDYwTDQ4MCAxNTBMMzkwIDI0MEwzMzAgMTgwTDMwMCAyMTBMMzkwIDMwMEw1NDAgMTUwTDQyMCAzMFoiIGNsYXNzPSJncmVlbi1zaGFwZSIvPgogIDxwYXRoIGQ9Ik0xNTAgMEwzMCAxMjBMNjAgMTUwTDE1MCA2MEwyMTAgMTIwTDI0MCA5MEwxNTAgMFoiIGNsYXNzPSJncmVlbi1zaGFwZSIvPgogIDxwYXRoIGQ9Ik0zOTAgMEw0MjAgMzBMMTUwIDMwMEwwIDE1MEwzMCAxMjBMMTUwIDI0MEwzOTAgMFoiIGZpbGw9IiMxRUE0NDYiLz4KPC9zdmc+) _Widely available across major browsers (Baseline since 2015)_\n\n' + + '[MDN Reference](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/Heading_Elements)' }, range: Range.create(0, 1, 0, 3) diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts index e622ae1c2..a07288542 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts @@ -1,5 +1,7 @@ import * as assert from 'assert'; import * as path from 'path'; +import { VERSION } from 'svelte/compiler'; +import { internalHelpers } from 'svelte2tsx'; import ts from 'typescript'; import { CancellationTokenSource, @@ -12,17 +14,16 @@ import { import { Document, DocumentManager } from '../../../../src/lib/documents'; import { LSConfigManager } from '../../../../src/ls-config'; import { + ADD_MISSING_IMPORTS_CODE_ACTION_KIND, CodeActionsProviderImpl, SORT_IMPORT_CODE_ACTION_KIND } from '../../../../src/plugins/typescript/features/CodeActionsProvider'; import { CompletionsProviderImpl } from '../../../../src/plugins/typescript/features/CompletionProvider'; +import { DiagnosticCode } from '../../../../src/plugins/typescript/features/DiagnosticsProvider'; import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver'; import { __resetCache } from '../../../../src/plugins/typescript/service'; import { pathToUrl } from '../../../../src/utils'; import { recursiveServiceWarmup } from '../test-utils'; -import { DiagnosticCode } from '../../../../src/plugins/typescript/features/DiagnosticsProvider'; -import { VERSION } from 'svelte/compiler'; -import { internalHelpers } from 'svelte2tsx'; const testDir = path.join(__dirname, '..'); const indent = ' '.repeat(4); @@ -2229,4 +2230,84 @@ describe('CodeActionsProvider', function () { after(() => { __resetCache(); }); + + it('provides source action for adding all missing imports', async () => { + const { provider, document } = setup('codeaction-custom-fix-all-component5.svelte'); + + const range = Range.create(Position.create(4, 1), Position.create(4, 15)); + + // Request the specific source action + const codeActions = await provider.getCodeActions(document, range, { + diagnostics: [], // Diagnostics might not be needed here if we only want the source action by kind + only: [ADD_MISSING_IMPORTS_CODE_ACTION_KIND] + }); + + assert.ok(codeActions.length > 0, 'No code actions found'); + + // Find the action by its kind + const addImportsAction = codeActions.find((action) => action.data); + + // Ensure the action was found and has data (as it's now deferred) + assert.ok(addImportsAction, 'Add missing imports action should be found'); + assert.ok( + addImportsAction.data, + 'Add missing imports action should have data for resolution' + ); + + // Resolve the action to get the edits + const resolvedAction = await provider.resolveCodeAction(document, addImportsAction); + + // Assert the edits on the resolved action + assert.ok(resolvedAction.edit, 'Resolved action should have an edit'); + (resolvedAction.edit?.documentChanges?.[0])?.edits.forEach( + (edit) => (edit.newText = harmonizeNewLines(edit.newText)) + ); + + assert.deepStrictEqual(resolvedAction.edit, { + documentChanges: [ + { + edits: [ + { + newText: + `\n${indent}import FixAllImported from \"./importing/FixAllImported.svelte\";\n` + + `${indent}import FixAllImported2 from \"./importing/FixAllImported2.svelte\";\n`, + range: { + start: { + character: 18, + line: 0 + }, + end: { + character: 18, + line: 0 + } + } + } + ], + textDocument: { + uri: getUri('codeaction-custom-fix-all-component5.svelte'), + version: null + } + } + ] + }); + + // Optional: Verify the kind and title remain correct on the resolved action + assert.strictEqual(resolvedAction.kind, ADD_MISSING_IMPORTS_CODE_ACTION_KIND); + assert.strictEqual(resolvedAction.title, 'Add all missing imports'); + }); + + it('provides source action for adding all missing imports only when imports are missing', async () => { + const { provider, document } = setup('codeaction-custom-fix-all-component6.svelte'); + + const codeActions = await provider.getCodeActions( + document, + Range.create(Position.create(1, 4), Position.create(1, 5)), + { + diagnostics: [], // No diagnostics = no missing imports + only: [ADD_MISSING_IMPORTS_CODE_ACTION_KIND] + } + ); + + assert.deepStrictEqual(codeActions, []); + }); }); diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts index e06fe7278..f0f3ea933 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -298,6 +298,7 @@ describe('CompletionProviderImpl', function () { assert.deepStrictEqual(item, { label: 'custom-element', kind: CompletionItemKind.Property, + commitCharacters: [], textEdit: { range: { start: { line: 0, character: 1 }, end: { line: 0, character: 2 } }, newText: 'custom-element' 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 dbccee6e5..b6227bfb7 100644 --- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts @@ -84,7 +84,7 @@ describe('DiagnosticsProvider', function () { try { const diagnostics3 = await plugin.getDiagnostics(document); - assert.deepStrictEqual(diagnostics3.length, 1); + assert.deepStrictEqual(diagnostics3.length, 0); await lsAndTsDocResolver.deleteSnapshot(newTsFilePath); } finally { unlinkSync(newTsFilePath); diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component5.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component5.svelte new file mode 100644 index 000000000..15f6d8fd7 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component5.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component6.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component6.svelte new file mode 100644 index 000000000..6c32ebc5e --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component6.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts index b3ef9233c..bd8947c64 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts @@ -43,6 +43,7 @@ export class Element { private isSelfclosing: boolean; public tagName: string; public child?: any; + private tagNameEnd: number; // Add const $$xxx = ... only if the variable name is actually used // in order to prevent "$$xxx is defined but never used" TS hints @@ -74,7 +75,7 @@ export class Element { this.startTagStart = this.node.start; this.startTagEnd = this.computeStartTagEnd(); - const tagEnd = this.startTagStart + this.node.name.length + 1; + const tagEnd = (this.tagNameEnd = this.startTagStart + this.node.name.length + 1); // Ensure deleted characters are mapped to the attributes object so we // get autocompletion when triggering it on a whitespace. if (/\s/.test(str.original.charAt(tagEnd))) { @@ -205,7 +206,19 @@ export class Element { } if (this.isSelfclosing) { - transform(this.str, this.startTagStart, this.startTagEnd, [ + // The transformation is the whole start tag + <, ex: ' && + (transformEnd === this.tagNameEnd || transformEnd === this.tagNameEnd + 1) + ) { + transformEnd = this.startTagStart; + this.str.remove(this.startTagStart, this.startTagStart + 1); + } + + transform(this.str, this.startTagStart, transformEnd, [ // Named slot transformations go first inside a outer block scope because //
means "use the x of let:x", and without a separate // block scope this would give a "used before defined" error @@ -295,7 +308,7 @@ export class Element { default: { createElementStatement = [ `${createElement}("`, - [this.node.start + 1, this.node.start + 1 + this.node.name.length], + [this.node.start + 1, this.tagNameEnd], `"${addActions()}, {` ]; break; diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts index 66f6d0818..2be599802 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts @@ -38,6 +38,7 @@ export class InlineComponent { private startTagStart: number; private startTagEnd: number; private isSelfclosing: boolean; + private tagNameEnd: number; public child?: any; // Add const $$xxx = ... only if the variable name is actually used @@ -64,7 +65,7 @@ export class InlineComponent { this.startTagStart = this.node.start; this.startTagEnd = this.computeStartTagEnd(); - const tagEnd = this.startTagStart + this.node.name.length + 1; + const tagEnd = (this.tagNameEnd = this.startTagStart + this.node.name.length + 1); // Ensure deleted characters are mapped to the attributes object so we // get autocompletion when triggering it on a whitespace. if (/\s/.test(str.original.charAt(tagEnd))) { @@ -227,7 +228,7 @@ export class InlineComponent { if (endStart === -1) { // Can happen in loose parsing mode when there's no closing tag endStart = this.node.end; - this.startTagEnd = this.node.end - 1; + this.startTagEnd = Math.max(this.node.end - 1, this.tagNameEnd); } else { endStart += this.node.start; } @@ -238,7 +239,17 @@ export class InlineComponent { } this.endTransformation.push('}'); - transform(this.str, this.startTagStart, this.startTagEnd, [ + let transformationEnd = this.startTagEnd; + + // The transformation is the whole start tag + <, ex: , dontAddTypeDef: boolean, - omitTyped = false + onlyTyped = false ): string[] { return names .map(([key, value]) => { - if (omitTyped && value.type) return; + if (onlyTyped && !value.type) return; // Important to not use shorthand props for rename functionality return `${dontAddTypeDef && value.doc ? `\n${value.doc}` : ''}${ value.identifierText || key diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts index 7d95d1c73..dd2ebaceb 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts @@ -86,6 +86,14 @@ export class HoistableInterfaces { if (ts.isInterfaceDeclaration(node)) { this.module_types.add(node.name.text); } + + if (ts.isEnumDeclaration(node)) { + this.module_types.add(node.name.text); + } + + if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) { + this.module_types.add(node.name.text); + } } analyzeInstanceScriptNode(node: ts.Node) { @@ -158,6 +166,24 @@ export class HoistableInterfaces { } }); + node.heritageClauses?.forEach((clause) => { + clause.types.forEach((type) => { + if (ts.isIdentifier(type.expression)) { + const type_name = type.expression.text; + if (!generics.includes(type_name)) { + type_dependencies.add(type_name); + } + } + + this.collectTypeDependencies( + type, + type_dependencies, + value_dependencies, + generics + ); + }); + }); + if (this.module_types.has(interface_name)) { // shadowed; we can't hoist this.disallowed_types.add(interface_name); @@ -229,6 +255,14 @@ export class HoistableInterfaces { if (ts.isEnumDeclaration(node)) { this.disallowed_values.add(node.name.text); } + + // namespace declaration should not be in the instance script. + // Only adding the top-level name to the disallowed list, + // so that at least there won't a confusing error message of "can't find namespace Foo" + if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) { + this.disallowed_types.add(node.name.text); + this.disallowed_values.add(node.name.text); + } } analyze$propsRune( @@ -239,7 +273,7 @@ export class HoistableInterfaces { if (node.initializer.typeArguments?.length > 0 || node.type) { const generic_arg = node.initializer.typeArguments?.[0] || node.type; if (ts.isTypeReferenceNode(generic_arg)) { - const name = this.getEntityNameText(generic_arg.typeName); + const name = this.getEntityNameRoot(generic_arg.typeName); const interface_node = this.interface_map.get(name); if (interface_node) { this.props_interface.name = name; @@ -394,13 +428,13 @@ export class HoistableInterfaces { ) { const walk = (node: ts.Node) => { if (ts.isTypeReferenceNode(node)) { - const type_name = this.getEntityNameText(node.typeName); + const type_name = this.getEntityNameRoot(node.typeName); if (!generics.includes(type_name)) { type_dependencies.add(type_name); } } else if (ts.isTypeQueryNode(node)) { // Handle 'typeof' expressions: e.g., foo: typeof bar - value_dependencies.add(this.getEntityNameText(node.exprName)); + value_dependencies.add(this.getEntityNameRoot(node.exprName)); } ts.forEachChild(node, walk); @@ -410,15 +444,16 @@ export class HoistableInterfaces { } /** - * Retrieves the full text of an EntityName (handles nested names). + * Retrieves the top-level variable/namespace of an EntityName (handles nested names). + * ex: `foo.bar.baz` -> `foo` * @param entity_name The EntityName to extract text from. - * @returns The full name as a string. + * @returns The top-level name as a string. */ - private getEntityNameText(entity_name: ts.EntityName): string { + private getEntityNameRoot(entity_name: ts.EntityName): string { if (ts.isIdentifier(entity_name)) { return entity_name.text; } else { - return this.getEntityNameText(entity_name.left) + '.' + entity_name.right.text; + return this.getEntityNameRoot(entity_name.left); } } } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts index 4f022260b..9daeb78e5 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import ts from 'typescript'; -import { moveNode } from '../utils/tsAst'; +import { getTopLevelImports, moveNode } from '../utils/tsAst'; /** * move imports to top of script so they appear outside our render function @@ -25,7 +25,7 @@ export function handleFirstInstanceImport( hasModuleScript: boolean, str: MagicString ) { - const imports = tsAst.statements.filter(ts.isImportDeclaration).sort((a, b) => a.end - b.end); + const imports = getTopLevelImports(tsAst); const firstImport = imports[0]; if (!firstImport) { return; diff --git a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts index a189c75b5..d8c11ac64 100644 --- a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts +++ b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts @@ -273,3 +273,7 @@ function isNewGroup(sourceFile: ts.SourceFile, topLevelImportDecl: ts.Node, scan return false; } + +export function getTopLevelImports(sourceFile: ts.SourceFile): ts.ImportDeclaration[] { + return sourceFile.statements.filter(ts.isImportDeclaration).sort((a, b) => a.end - b.end); +} diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-component-no-attr.v5/expected.error.json b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-component-no-attr.v5/expected.error.json new file mode 100644 index 000000000..05090efbb --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-component-no-attr.v5/expected.error.json @@ -0,0 +1,20 @@ +{ + "code": "expected_token", + "message": "Expected token >\nhttps://svelte.dev/e/expected_token", + "filename": "(unknown)", + "start": { + "line": 7, + "column": 2, + "character": 138 + }, + "end": { + "line": 7, + "column": 2, + "character": 138 + }, + "position": [ + 138, + 138 + ], + "frame": " 5:
\n 6: \n ^\n 8: \n 9: +
+ + +\nhttps://svelte.dev/e/expected_token", + "filename": "(unknown)", + "start": { + "line": 7, + "column": 2, + "character": 138 + }, + "end": { + "line": 7, + "column": 2, + "character": 138 + }, + "position": [ + 138, + 138 + ], + "frame": " 5:
\n 6: \n ^\n 8: \n 9: +
+ + + ; - let foo = true; -; const hoistable1/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => { + const hoistable1/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => { { svelteHTML.createElement("div", {}); } };return __sveltets_2_any(0)}; const hoistable2/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => { { svelteHTML.createElement("div", {});foo; } -};return __sveltets_2_any(0)};;function $$render() { +};return __sveltets_2_any(0)}; + let foo = true; +;;function $$render() { async () => { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/expectedv2.ts new file mode 100644 index 000000000..1063eae01 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/expectedv2.ts @@ -0,0 +1,12 @@ +/// +; + import {} from 'svelte' + const foo/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {};return __sveltets_2_any(0)}; +;;function $$render() { +async () => { + +}; +return { props: /** @type {Record} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__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/snippet-module-hoist-5.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/input.svelte new file mode 100644 index 000000000..6321ca3bf --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/input.svelte @@ -0,0 +1,5 @@ + + +{#snippet foo()}{/snippet} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/expectedv2.ts new file mode 100644 index 000000000..39bb66302 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/expectedv2.ts @@ -0,0 +1,12 @@ +/// +; + const _foo/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {};return __sveltets_2_any(0)}; + export const foo = _foo; +;;function $$render() { +async () => { + +}; +return { props: /** @type {Record} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__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/snippet-module-hoist-6.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/input.svelte new file mode 100644 index 000000000..8a2e6f5ed --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/input.svelte @@ -0,0 +1,5 @@ + + +{#snippet _foo()}{/snippet} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts index d05d48969..23428b25c 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts @@ -20,7 +20,7 @@ ; async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});} }; -return { props: {} as Record, exports: {Foo: Foo,bar: bar,RenamedFoo: RenameFoo,renamedbar: renamebar} as any as { name1: string,name2: string,name3: string,name4: string,renamed1: string,renamed2: string,Foo: typeof Foo,bar: typeof bar,baz: string,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: string }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +return { props: {} as Record, exports: {name1: name1,name2: name2,name3: name3,name4: name4,renamed1: rename1,renamed2: rename2,baz: baz,renamedbaz: renamebaz} as any as { name1: string,name2: string,name3: string,name4: string,renamed1: string,renamed2: string,Foo: typeof Foo,bar: typeof bar,baz: string,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: string }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} const Input__SvelteComponent_ = __sveltets_2_fn_component($$render()); type Input__SvelteComponent_ = ReturnType; export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/expectedv2.ts new file mode 100644 index 000000000..f063d70b8 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/expectedv2.ts @@ -0,0 +1,19 @@ +/// +;; + interface A { + type: string; + };; + + interface Props extends A { + a: string; + };function $$render() { + + + + const { }: Props = $props(); +; +async () => {}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component($$render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/input.svelte new file mode 100644 index 000000000..dec767819 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/expectedv2.ts new file mode 100644 index 000000000..22f05c3f8 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/expectedv2.ts @@ -0,0 +1,17 @@ +/// +;; +type Props = { + data: {cfg: string}; +};;function $$render() { + + +let { data }: Props = $props(); + +type A = typeof data.cfg; +type B = (typeof data)['cfg']; +; +async () => {}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component($$render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/input.svelte new file mode 100644 index 000000000..42e0fbb2a --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/expectedv2.ts new file mode 100644 index 000000000..ed54ba6dc --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/expectedv2.ts @@ -0,0 +1,20 @@ +/// +;function $$render() { + + const a = 1; + +interface A { + Abc: typeof a +} + +interface Abc { + foo: A.Abc +} + +let {}: Abc = $props(); +; +async () => {}; +return { props: {} as any as Abc, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component($$render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/input.svelte new file mode 100644 index 000000000..c2532ebe6 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/input.svelte @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/expectedv2.ts new file mode 100644 index 000000000..1bcbfe8c5 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/expectedv2.ts @@ -0,0 +1,20 @@ +/// +;function $$render() { + +const a = 1; + +namespace A { + export type Abc = typeof a +} + +interface Abc { + foo: A.Abc +} + +let {}: Abc = $props(); +; +async () => {}; +return { props: {} as any as Abc, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component($$render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/input.svelte new file mode 100644 index 000000000..897e08c65 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/input.svelte @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/expectedv2.ts new file mode 100644 index 000000000..1525d0cc2 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/expectedv2.ts @@ -0,0 +1,20 @@ +/// +; + namespace A { + export type Abd = number + } +;;function $$render() { + +interface A { + Abc: number +} + +let {Abc}: A = $props() +; +async () => { + +}; +return { props: {} as any as A, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component($$render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/input.svelte new file mode 100644 index 000000000..1617746f0 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/input.svelte @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/expectedv2.ts new file mode 100644 index 000000000..27b661d11 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/expectedv2.ts @@ -0,0 +1,19 @@ +/// +; + enum A { + } +;;function $$render() { + +interface A { + Abc: number +} + +let {Abc}: A = $props() +; +async () => { + +}; +return { props: {} as any as A, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component($$render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/input.svelte new file mode 100644 index 000000000..6d555bb69 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/input.svelte @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/expectedv2.ts new file mode 100644 index 000000000..60855f1fb --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/expectedv2.ts @@ -0,0 +1,36 @@ +/// +;function $$render() { + + interface WithItems { + items: T[]; + } + + interface Props extends WithItems { + prop: T; + }; + let { prop }: Props = $props(); +; +async () => {}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +class __sveltets_Render { + props() { + return $$render().props; + } + events() { + return $$render().events; + } + slots() { + return $$render().slots; + } + bindings() { return __sveltets_$$bindings(''); } + exports() { return {}; } +} + +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']> & {}): ReturnType<__sveltets_Render['exports']>; + z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; +} +const Input__SvelteComponent_: $$IsomorphicComponent = null as any; +/*Ω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/ts-runes-hoistable-props-false-8.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/input.svelte new file mode 100644 index 000000000..11461621f --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/input.svelte @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/expectedv2.ts new file mode 100644 index 000000000..39b62f5b3 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/expectedv2.ts @@ -0,0 +1,31 @@ +/// +;function $$render() { + + interface Props extends T { + }; + let { a }: Props = $props(); +; +async () => {}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +class __sveltets_Render { + props() { + return $$render().props; + } + events() { + return $$render().events; + } + slots() { + return $$render().slots; + } + bindings() { return __sveltets_$$bindings(''); } + exports() { return {}; } +} + +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']> & {}): ReturnType<__sveltets_Render['exports']>; + z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; +} +const Input__SvelteComponent_: $$IsomorphicComponent = null as any; +/*Ω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/ts-runes-hoistable-props-false-9.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/input.svelte new file mode 100644 index 000000000..fd8a6f6f1 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/input.svelte @@ -0,0 +1,5 @@ + \ 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 c788dc05e..b2051ddd0 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,7 +6,7 @@ let { form, data }:/*Ωignore_startΩ*/$$ComponentProps/*Ωignore_endΩ*/ = $props(); ; async () => {}; -return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: any }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +return { props: {} as any as $$ComponentProps, exports: {snapshot: snapshot} as any as { snapshot: any }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} const Page__SvelteComponent_ = __sveltets_2_fn_component($$render()); type Page__SvelteComponent_ = ReturnType; 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 4a33a614f..ce2a45ef9 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 @@ -5,7 +5,7 @@ let { form, data }: $$ComponentProps = $props(); ; async () => {}; -return { props: {} as any as $$ComponentProps, exports: {snapshot: snapshot} as any as { snapshot: typeof snapshot }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: typeof snapshot }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} const Page__SvelteComponent_ = __sveltets_2_fn_component($$render()); type Page__SvelteComponent_ = ReturnType; export default Page__SvelteComponent_; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 905264a94..26e32643a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,11 +64,11 @@ importers: specifier: ^0.3.5 version: 0.3.5 vscode-css-languageservice: - specifier: ~6.3.2 - version: 6.3.2 + specifier: ~6.3.5 + version: 6.3.5 vscode-html-languageservice: - specifier: ~5.3.2 - version: 5.3.2 + specifier: ~5.4.0 + version: 5.4.0 vscode-languageserver: specifier: 9.0.1 version: 9.0.1 @@ -80,7 +80,7 @@ importers: version: 3.17.5 vscode-uri: specifier: ~3.0.0 - version: 3.0.8 + version: 3.1.0 devDependencies: '@types/estree': specifier: ^0.0.42 @@ -181,7 +181,7 @@ importers: version: 3.17.2 vscode-uri: specifier: ~3.0.0 - version: 3.0.8 + version: 3.1.0 packages/svelte-vscode: dependencies: @@ -1302,11 +1302,11 @@ packages: vfile-message@3.1.4: resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} - vscode-css-languageservice@6.3.2: - resolution: {integrity: sha512-GEpPxrUTAeXWdZWHev1OJU9lz2Q2/PPBxQ2TIRmLGvQiH3WZbqaNoute0n0ewxlgtjzTW3AKZT+NHySk5Rf4Eg==} + vscode-css-languageservice@6.3.5: + resolution: {integrity: sha512-ehEIMXYPYEz/5Svi2raL9OKLpBt5dSAdoCFoLpo0TVFKrVpDemyuQwS3c3D552z/qQCg3pMp8oOLMObY6M3ajQ==} - vscode-html-languageservice@5.3.2: - resolution: {integrity: sha512-3MgFQqVG+iQVNG7QI/slaoL7lJpne0nssX082kjUF1yn/YJa8BWCLeCJjM0YpTlp8A7JT1+J22mk4qSPx3NjSQ==} + vscode-html-languageservice@5.4.0: + resolution: {integrity: sha512-9/cbc90BSYCghmHI7/VbWettHZdC7WYpz2g5gBK6UDUI1MkZbM773Q12uAYJx9jzAiNHPpyo6KzcwmcnugncAQ==} vscode-jsonrpc@8.0.2: resolution: {integrity: sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==} @@ -1359,8 +1359,8 @@ packages: vscode-uri@2.1.2: resolution: {integrity: sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==} - vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} @@ -2372,19 +2372,19 @@ snapshots: '@types/unist': 2.0.6 unist-util-stringify-position: 3.0.3 - vscode-css-languageservice@6.3.2: + vscode-css-languageservice@6.3.5: dependencies: '@vscode/l10n': 0.0.18 vscode-languageserver-textdocument: 1.0.12 vscode-languageserver-types: 3.17.5 - vscode-uri: 3.0.8 + vscode-uri: 3.1.0 - vscode-html-languageservice@5.3.2: + vscode-html-languageservice@5.4.0: dependencies: '@vscode/l10n': 0.0.18 vscode-languageserver-textdocument: 1.0.12 vscode-languageserver-types: 3.17.5 - vscode-uri: 3.0.8 + vscode-uri: 3.1.0 vscode-jsonrpc@8.0.2: {} @@ -2437,7 +2437,7 @@ snapshots: vscode-uri@2.1.2: {} - vscode-uri@3.0.8: {} + vscode-uri@3.1.0: {} which@2.0.2: dependencies: pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy