diff --git a/packages/language-server/README.md b/packages/language-server/README.md index 0862842b9..0c08cc614 100644 --- a/packages/language-server/README.md +++ b/packages/language-server/README.md @@ -135,6 +135,10 @@ Enable signature help (parameter hints) for JS/TS. _Default_: `true` Enable semantic tokens (semantic highlight) for TypeScript. _Default_: `true` +#### `svelte.plugin.typescript.workspaceSymbols.enable` + +Enable workspace symbols for TypeScript. You can disable this if the language server client you're using doesn't deduplicate results from the TSServer. _Default_: `true`. + ##### `svelte.plugin.css.enable` Enable the CSS plugin. _Default_: `true` diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index abe59c736..1d82adebf 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -21,7 +21,8 @@ const defaultLSConfig: LSConfig = { codeActions: { enable: true }, selectionRange: { enable: true }, signatureHelp: { enable: true }, - semanticTokens: { enable: true } + semanticTokens: { enable: true }, + workspaceSymbols: { enable: true } }, css: { enable: true, @@ -105,6 +106,9 @@ export interface LSTypescriptConfig { semanticTokens: { enable: boolean; }; + workspaceSymbols: { + enable: boolean; + }; } export interface LSCSSConfig { @@ -205,6 +209,7 @@ export interface TSUserConfig { inlayHints?: TsInlayHintsConfig; referencesCodeLens?: TsReferenceCodeLensConfig; implementationsCodeLens?: TsImplementationCodeLensConfig; + workspaceSymbols?: TsWorkspaceSymbolsConfig; } /** @@ -280,6 +285,10 @@ export interface TsImplementationCodeLensConfig { showOnInterfaceMethods?: boolean | undefined; } +export interface TsWorkspaceSymbolsConfig { + excludeLibrarySymbols?: boolean; +} + export type TsUserConfigLang = 'typescript' | 'javascript'; /** @@ -509,7 +518,9 @@ export class LSConfigManager { organizeImportsTypeOrder: this.withDefaultAsUndefined( config.preferences?.organizeImports?.typeOrder, 'auto' - ) + ), + + excludeLibrarySymbolsInNavTo: config.workspaceSymbols?.excludeLibrarySymbols ?? true }; } diff --git a/packages/language-server/src/plugins/PluginHost.ts b/packages/language-server/src/plugins/PluginHost.ts index 8a4c3974d..f251582c0 100644 --- a/packages/language-server/src/plugins/PluginHost.ts +++ b/packages/language-server/src/plugins/PluginHost.ts @@ -34,7 +34,8 @@ import { TextDocumentIdentifier, TextEdit, WorkspaceEdit, - InlayHint + InlayHint, + WorkspaceSymbol } from 'vscode-languageserver'; import { DocumentManager, getNodeIfIsInHTMLStartTag } from '../lib/documents'; import { Logger } from '../logger'; @@ -697,6 +698,18 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { ); } + async getWorkspaceSymbols( + query: string, + token: CancellationToken + ): Promise { + return await this.execute( + 'getWorkspaceSymbols', + [query, token], + ExecuteMode.FirstNonNull, + 'high' + ); + } + onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): void { for (const support of this.plugins) { support.onWatchFileChanges?.(onWatchFileChangesParas); diff --git a/packages/language-server/src/plugins/interfaces.ts b/packages/language-server/src/plugins/interfaces.ts index 73c7bbbbd..c050b2387 100644 --- a/packages/language-server/src/plugins/interfaces.ts +++ b/packages/language-server/src/plugins/interfaces.ts @@ -35,7 +35,8 @@ import { SymbolInformation, TextDocumentIdentifier, TextEdit, - WorkspaceEdit + WorkspaceEdit, + WorkspaceSymbol } from 'vscode-languageserver-types'; import { Document } from '../lib/documents'; @@ -251,6 +252,13 @@ export interface DocumentHighlightProvider { ): Resolvable; } +export interface WorkspaceSymbolsProvider { + getWorkspaceSymbols( + query: string, + cancellationToken?: CancellationToken + ): Resolvable; +} + export interface OnWatchFileChanges { onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): void; } @@ -282,7 +290,8 @@ type ProviderBase = DiagnosticsProvider & CallHierarchyProvider & FoldingRangeProvider & CodeLensProvider & - DocumentHighlightProvider; + DocumentHighlightProvider & + WorkspaceSymbolsProvider; export type LSProvider = ProviderBase & BackwardsCompatibleDefinitionsProvider; diff --git a/packages/language-server/src/plugins/typescript/SnapshotManager.ts b/packages/language-server/src/plugins/typescript/SnapshotManager.ts index 85b152e49..13cbb9ddd 100644 --- a/packages/language-server/src/plugins/typescript/SnapshotManager.ts +++ b/packages/language-server/src/plugins/typescript/SnapshotManager.ts @@ -290,6 +290,16 @@ export class SnapshotManager { } } + allFilesAreJsOrDts() { + for (const doc of this.documents.values()) { + if (doc.scriptKind === ts.ScriptKind.TS || doc.scriptKind === ts.ScriptKind.TSX) { + return false; + } + } + + return true; + } + dispose() { this.globalSnapshotsManager.removeChangeListener(this.onSnapshotChange); } diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts index 3c8203b25..1b5781699 100644 --- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts @@ -28,7 +28,8 @@ import { SymbolInformation, SymbolKind, TextDocumentContentChangeEvent, - WorkspaceEdit + WorkspaceEdit, + WorkspaceSymbol } from 'vscode-languageserver'; import { Document, @@ -65,7 +66,8 @@ import { SignatureHelpProvider, TypeDefinitionProvider, UpdateImportsProvider, - UpdateTsOrJsFile + UpdateTsOrJsFile, + WorkspaceSymbolsProvider } from '../interfaces'; import { LSAndTSDocResolver } from './LSAndTSDocResolver'; import { ignoredBuildDirectories } from './SnapshotManager'; @@ -103,6 +105,7 @@ import { } from './utils'; import { CallHierarchyProviderImpl } from './features/CallHierarchyProvider'; import { CodeLensProviderImpl } from './features/CodeLensProvider'; +import { WorkspaceSymbolsProviderImpl } from './features/WorkspaceSymbolProvider'; export class TypeScriptPlugin implements @@ -126,6 +129,7 @@ export class TypeScriptPlugin CallHierarchyProvider, FoldingRangeProvider, CodeLensProvider, + WorkspaceSymbolsProvider, OnWatchFileChanges, CompletionsProvider, UpdateTsOrJsFile @@ -154,6 +158,7 @@ export class TypeScriptPlugin private readonly callHierarchyProvider: CallHierarchyProviderImpl; private readonly codLensProvider: CodeLensProviderImpl; private readonly documentHeightProvider: DocumentHighlightProviderImpl; + private readonly workspaceSymbolsProvider: WorkspaceSymbolsProvider; constructor( configManager: LSConfigManager, @@ -214,6 +219,10 @@ export class TypeScriptPlugin this.configManager ); this.documentHeightProvider = new DocumentHighlightProviderImpl(this.lsAndTsDocResolver); + this.workspaceSymbolsProvider = new WorkspaceSymbolsProviderImpl( + this.lsAndTsDocResolver, + configManager + ); } async getDiagnostics( @@ -701,6 +710,16 @@ export class TypeScriptPlugin return this.documentHeightProvider.findDocumentHighlight(document, position); } + async getWorkspaceSymbols( + query: string, + cancellationToken?: CancellationToken + ): Promise { + if (!this.featureEnabled('workspaceSymbols')) { + return null; + } + return this.workspaceSymbolsProvider.getWorkspaceSymbols(query, cancellationToken); + } + private featureEnabled(feature: keyof LSTypescriptConfig) { return ( this.configManager.enabled('typescript.enable') && diff --git a/packages/language-server/src/plugins/typescript/features/WorkspaceSymbolProvider.ts b/packages/language-server/src/plugins/typescript/features/WorkspaceSymbolProvider.ts new file mode 100644 index 000000000..e9b0931e7 --- /dev/null +++ b/packages/language-server/src/plugins/typescript/features/WorkspaceSymbolProvider.ts @@ -0,0 +1,213 @@ +import { internalHelpers } from 'svelte2tsx'; +import ts from 'typescript'; +import { CancellationToken } from 'vscode-languageserver-protocol'; +import { SymbolKind, SymbolTag, WorkspaceSymbol } from 'vscode-languageserver-types'; +import { mapLocationToOriginal } from '../../../lib/documents'; +import { LSConfigManager } from '../../../ls-config'; +import { isNotNullOrUndefined } from '../../../utils'; +import { WorkspaceSymbolsProvider } from '../../interfaces'; +import { DocumentSnapshot, SvelteDocumentSnapshot } from '../DocumentSnapshot'; +import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; +import { forAllServices, LanguageServiceContainer } from '../service'; +import { + convertRange, + isGeneratedSvelteComponentName, + isInScript, + isSvelteFilePath +} from '../utils'; +import { isInGeneratedCode, SnapshotMap } from './utils'; + +export class WorkspaceSymbolsProviderImpl implements WorkspaceSymbolsProvider { + constructor(lsAndTsDocResolver: LSAndTSDocResolver, configManager: LSConfigManager) { + this.configManager = configManager; + this.lsAndTsDocResolver = lsAndTsDocResolver; + } + + private readonly configManager: LSConfigManager; + private readonly lsAndTsDocResolver: LSAndTSDocResolver; + + async getWorkspaceSymbols( + query: string, + cancellationToken?: CancellationToken + ): Promise { + const allServices: LanguageServiceContainer[] = []; + await forAllServices((service) => { + allServices.push(service); + }); + + const symbols = new Map>(); + + // The config only exists for typescript. No javascript counterpart. + const preference = this.configManager.getTsUserPreferences('typescript', null); + + for (const ls of allServices) { + if (cancellationToken?.isCancellationRequested) { + return null; + } + const service = ls.getService(); + const projectItems = service.getNavigateToItems( + query, + /* maxResultCount */ 256, + /* fileName */ undefined, + /* excludeDtsFiles */ ls.snapshotManager.allFilesAreJsOrDts(), + preference.excludeLibrarySymbolsInNavTo + ); + + const snapshots = new SnapshotMap(this.lsAndTsDocResolver, ls); + for (const item of projectItems) { + if ( + this.isGeneratedName(item) || + (item.kind === ts.ScriptElementKind.alias && !item.containerName) + ) { + continue; + } + const seen = symbols.get(item.name); + if (!seen) { + symbols.set(item.name, [ + [ + item, + this.mapWorkspaceSymbol(item, await snapshots.retrieve(item.fileName)) + ] + ]); + continue; + } + + let skip = false; + for (const [seenItem] of seen) { + if (this.navigateToItemIsEqualTo(seenItem, item)) { + skip = true; + break; + } + } + + if (skip) { + continue; + } + const snapshot = await snapshots.retrieve(item.fileName); + if ( + snapshot instanceof SvelteDocumentSnapshot && + isInGeneratedCode(snapshot.getFullText(), item.textSpan.start) + ) { + continue; + } + seen.push([ + item, + this.mapWorkspaceSymbol(item, await snapshots.retrieve(item.fileName)) + ]); + } + } + + return Array.from(symbols.values()) + .flatMap((items) => items.map(([_, symbol]) => symbol)) + .filter(isNotNullOrUndefined); + } + + private isGeneratedName(item: ts.NavigateToItem) { + if (!isSvelteFilePath(item.fileName)) { + return false; + } + + return ( + item.name === internalHelpers.renderName || + item.name.startsWith('__sveltets_') || + item.name.startsWith('$$') + ); + } + + private mapWorkspaceSymbol( + item: ts.NavigateToItem, + snapshot: DocumentSnapshot + ): WorkspaceSymbol | undefined { + let location = mapLocationToOriginal(snapshot, convertRange(snapshot, item.textSpan)); + if (location.range.start.line < 0) { + if (isGeneratedSvelteComponentName(item.name)) { + location = { + uri: snapshot.getURL(), + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 1 } + } + }; + } else { + return undefined; + } + } + + return { + kind: this.convertSymbolKindForWorkspaceSymbol(item.kind), + name: this.getLabel(item), + containerName: + snapshot instanceof SvelteDocumentSnapshot && + (item.containerName === internalHelpers.renderName || !item.containerName) + ? isInScript(location.range.start, snapshot) + ? 'script' + : undefined + : item.containerName, + location, + tags: item.kindModifiers?.includes('deprecated') ? [SymbolTag.Deprecated] : undefined + }; + } + + /** + * + * https://github.com/microsoft/TypeScript/blob/81c951894e93bdc37c6916f18adcd80de76679bc/src/server/session.ts#L2878 + */ + private navigateToItemIsEqualTo(a: ts.NavigateToItem, b: ts.NavigateToItem): boolean { + if (a === b) { + return true; + } + if (!a || !b) { + return false; + } + return ( + a.containerKind === b.containerKind && + a.containerName === b.containerName && + a.fileName === b.fileName && + a.isCaseSensitive === b.isCaseSensitive && + a.kind === b.kind && + a.kindModifiers === b.kindModifiers && + a.matchKind === b.matchKind && + a.name === b.name && + a.textSpan.start === b.textSpan.start && + a.textSpan.length === b.textSpan.length + ); + } + + /** + * Don't reuse our symbolKindFromString function, this should the same as the one in vscode + * so that vscode deduplicate the symbols from svelte and the typescript server. + * https://github.com/microsoft/vscode/blob/18ed64835ec8f8227dbd8562d2d9fd9fa339abbb/extensions/typescript-language-features/src/languageFeatures/workspaceSymbols.ts#L17 + */ + private convertSymbolKindForWorkspaceSymbol(kind: string) { + switch (kind) { + case ts.ScriptElementKind.memberFunctionElement: + return SymbolKind.Method; + case ts.ScriptElementKind.enumElement: + return SymbolKind.Enum; + case ts.ScriptElementKind.enumMemberElement: + return SymbolKind.EnumMember; + case ts.ScriptElementKind.functionElement: + return SymbolKind.Function; + case ts.ScriptElementKind.classElement: + return SymbolKind.Class; + case ts.ScriptElementKind.interfaceElement: + return SymbolKind.Interface; + case ts.ScriptElementKind.typeElement: + return SymbolKind.Class; + case ts.ScriptElementKind.memberVariableElement: + case ts.ScriptElementKind.memberGetAccessorElement: + case ts.ScriptElementKind.memberSetAccessorElement: + return SymbolKind.Field; + default: + return SymbolKind.Variable; + } + } + + private getLabel(item: ts.NavigateToItem) { + const label = item.name; + if (item.kind === 'method' || item.kind === 'function') { + return label + '()'; + } + return label; + } +} diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index af5f92a60..c08b9790b 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -326,7 +326,8 @@ export function startServer(options?: LSOptions) { }, documentHighlightProvider: evt.initializationOptions?.configuration?.svelte?.plugin?.svelte - ?.documentHighlight?.enable ?? true + ?.documentHighlight?.enable ?? true, + workspaceSymbolProvider: true } }; }); @@ -500,6 +501,8 @@ export function startServer(options?: LSOptions) { pluginHost.findDocumentHighlight(evt.textDocument, evt.position) ); + connection.onWorkspaceSymbol((evt, token) => pluginHost.getWorkspaceSymbols(evt.query, token)); + const diagnosticsManager = new DiagnosticsManager( connection.sendDiagnostics, docManager, diff --git a/packages/language-server/test/plugins/typescript/features/WorkspaceSymbolsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/WorkspaceSymbolsProvider.test.ts new file mode 100644 index 000000000..28c1a4fbc --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/WorkspaceSymbolsProvider.test.ts @@ -0,0 +1,150 @@ +import assert from 'assert'; +import path from 'path'; +import ts from 'typescript'; +import { WorkspaceSymbol } from 'vscode-languageserver-protocol'; +import { Document, DocumentManager } from '../../../../src/lib/documents'; +import { LSConfigManager } from '../../../../src/ls-config'; +import { LSAndTSDocResolver } from '../../../../src/plugins'; +import { WorkspaceSymbolsProviderImpl } from '../../../../src/plugins/typescript/features/WorkspaceSymbolProvider'; +import { pathToUrl } from '../../../../src/utils'; +import { serviceWarmup } from '../test-utils'; + +const testDir = path.join(__dirname, '..'); + +describe('WorkspaceSymbolsProvider', function () { + serviceWarmup(this, testDir, pathToUrl(testDir)); + + function getFullPath(filename: string) { + return path.join(testDir, 'testfiles', 'workspace-symbols', filename); + } + function getUri(filename: string) { + return pathToUrl(getFullPath(filename)); + } + + function setup(filename: string) { + const docManager = new DocumentManager( + (textDocument) => new Document(textDocument.uri, textDocument.text) + ); + const lsConfigManager = new LSConfigManager(); + const lsAndTsDocResolver = new LSAndTSDocResolver( + docManager, + [pathToUrl(testDir)], + lsConfigManager + ); + const provider = new WorkspaceSymbolsProviderImpl(lsAndTsDocResolver, lsConfigManager); + const filePath = getFullPath(filename); + const document = docManager.openClientDocument({ + uri: pathToUrl(filePath), + text: ts.sys.readFile(filePath) + }); + return { provider, document, docManager, lsAndTsDocResolver }; + } + + it('should return workspace symbols', async () => { + const { provider, document, lsAndTsDocResolver } = setup('workspace-symbols.svelte'); + await lsAndTsDocResolver.getLSAndTSDoc(document); + + const symbols = await provider.getWorkspaceSymbols('longName'); + assert.deepStrictEqual(symbols, [ + { + containerName: 'script', + kind: 12, + location: { + range: { + end: { + character: 5, + line: 3 + }, + start: { + character: 4, + line: 2 + } + }, + uri: getUri('workspace-symbols.svelte') + }, + name: 'longLongName()', + tags: undefined + }, + { + containerName: '', + kind: 11, + location: { + range: { + end: { + character: 1, + line: 5 + }, + start: { + character: 0, + line: 3 + } + }, + uri: getUri('imported.ts') + }, + name: 'longLongName2', + tags: [1] + }, + { + containerName: 'longLongName2', + kind: 8, + location: { + range: { + end: { + character: 26, + line: 4 + }, + start: { + character: 4, + line: 4 + } + }, + uri: getUri('imported.ts') + }, + name: 'longLongName3', + tags: undefined + }, + { + containerName: undefined, + kind: 13, + location: { + range: { + end: { + character: 28, + line: 8 + }, + start: { + character: 15, + line: 8 + } + }, + uri: getUri('workspace-symbols.svelte') + }, + name: 'longLongName4', + tags: undefined + } + ]); + }); + + it('filter out generated symbols', async () => { + const { provider, document, lsAndTsDocResolver } = setup('workspace-symbols.svelte'); + await lsAndTsDocResolver.getLSAndTSDoc(document); + + const symbols = await provider.getWorkspaceSymbols('_'); + assert.deepStrictEqual( + // Filter out the generated component class/const/type. + // The unfiltered result is slightly different in svelte 4 and svelte 5, + // and there is a maxResultCount limit, so it's not always present. + onlyInWorkspaceSymbolsDir(symbols)?.filter( + (v) => v.name !== 'WorkspaceSymbols__SvelteComponent_' + ), + [] + ); + + const symbols2 = await provider.getWorkspaceSymbols('$'); + assert.deepStrictEqual(onlyInWorkspaceSymbolsDir(symbols2), []); + }); + + function onlyInWorkspaceSymbolsDir(symbols: WorkspaceSymbol[] | null) { + return symbols?.filter((f) => f.location.uri.includes('workspace-symbols')); + } +}); diff --git a/packages/language-server/test/plugins/typescript/testfiles/workspace-symbols/imported.ts b/packages/language-server/test/plugins/typescript/testfiles/workspace-symbols/imported.ts new file mode 100644 index 000000000..924df1d33 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/workspace-symbols/imported.ts @@ -0,0 +1,6 @@ +/** + * @deprecated + */ +export interface longLongName2 { + longLongName3: string; +} \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/workspace-symbols/workspace-symbols.svelte b/packages/language-server/test/plugins/typescript/testfiles/workspace-symbols/workspace-symbols.svelte new file mode 100644 index 000000000..7f5677f3b --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/workspace-symbols/workspace-symbols.svelte @@ -0,0 +1,11 @@ + + + + +{#each [''] as longLongName4} + {longLongName4} +{/each}} \ No newline at end of file diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json index b9aa0f31d..6ad7151b1 100644 --- a/packages/svelte-vscode/package.json +++ b/packages/svelte-vscode/package.json @@ -179,6 +179,12 @@ "title": "TypeScript: Semantic Tokens", "description": "Enable semantic tokens (semantic highlight) for TypeScript." }, + "svelte.plugin.typescript.workspaceSymbols.enable": { + "type": "boolean", + "default": true, + "title": "TypeScript: Workspace Symbols", + "description": "Enable workspace symbols for TypeScript." + }, "svelte.plugin.css.enable": { "type": "boolean", "default": true, diff --git a/packages/typescript-plugin/src/language-service/call-hierarchy.ts b/packages/typescript-plugin/src/language-service/call-hierarchy.ts index b16aafb0a..e858538aa 100644 --- a/packages/typescript-plugin/src/language-service/call-hierarchy.ts +++ b/packages/typescript-plugin/src/language-service/call-hierarchy.ts @@ -8,6 +8,7 @@ import { isSvelteFilePath, offsetOfGeneratedComponentExport } from '../utils'; +import { internalHelpers } from 'svelte2tsx'; const ENSURE_COMPONENT_HELPER = '__sveltets_2_ensureComponent'; @@ -76,7 +77,7 @@ export function decorateCallHierarchy( .find( (statement): statement is ts.FunctionDeclaration => typescript.isFunctionDeclaration(statement) && - statement.name?.getText() === 'render' + statement.name?.getText() === internalHelpers.renderName ) ?.name?.getStart() : -1; @@ -181,7 +182,7 @@ export function decorateCallHierarchy( return toComponentCallHierarchyItem(snapshot, item.file); } - if (item.name === 'render') { + if (item.name === internalHelpers.renderName) { const end = item.selectionSpan.start + item.selectionSpan.length; const renderFunction = sourceFile.statements.find( (statement) => diff --git a/packages/typescript-plugin/src/language-service/navigate-to-items.ts b/packages/typescript-plugin/src/language-service/navigate-to-items.ts index 3d47bf44d..357ca525e 100644 --- a/packages/typescript-plugin/src/language-service/navigate-to-items.ts +++ b/packages/typescript-plugin/src/language-service/navigate-to-items.ts @@ -1,6 +1,12 @@ import type ts from 'typescript/lib/tsserverlibrary'; -import { isGeneratedSvelteComponentName, isNotNullOrUndefined, isSvelteFilePath } from '../utils'; +import { + isGeneratedSvelteComponentName, + isNotNullOrUndefined, + isSvelteFilePath, + isNoTextSpanInGeneratedCode +} from '../utils'; import { SvelteSnapshotManager } from '../svelte-snapshots'; +import { internalHelpers } from 'svelte2tsx'; export function decorateNavigateToItems( ls: ts.LanguageService, @@ -18,15 +24,18 @@ export function decorateNavigateToItems( if ( item.name.startsWith('__sveltets_') || - (item.name === 'render' && !item.containerName) + item.name === internalHelpers.renderName || + item.name.startsWith('$$') ) { return; } - let textSpan = snapshotManager - .get(item.fileName) - ?.getOriginalTextSpan(item.textSpan); + const snapshot = snapshotManager.get(item.fileName); + if (!snapshot || !isNoTextSpanInGeneratedCode(snapshot.getText(), item.textSpan)) { + return; + } + let textSpan = snapshot.getOriginalTextSpan(item.textSpan); if (!textSpan) { if (isGeneratedSvelteComponentName(item.name)) { textSpan = { start: 0, length: 1 }; @@ -35,11 +44,27 @@ export function decorateNavigateToItems( } } + const containerName = + item.containerName === internalHelpers.renderName || !item.containerName + ? isInScript(textSpan.start, snapshot.getOriginalText()) + ? 'script' + : '' + : item.containerName; + return { ...item, + containerName, textSpan }; }) .filter(isNotNullOrUndefined); }; } + +function isInScript(offset: number, originalText: string): boolean { + const text = originalText.slice(0, offset); + const lastScriptTag = text.lastIndexOf(''); + + return lastScriptTag > lastCloseTag; +}