Fix GoToDefinition for component imports (and component usage) (#1045)
* Fix GoToDefinition for component imports (and component usage) * Remove extra comment
This commit is contained in:
parent
812c9d8269
commit
5293519470
4 changed files with 88 additions and 12 deletions
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -7,8 +7,8 @@
|
|||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}/tools/astro-vscode"],
|
||||
"outFiles": ["${workspaceRoot}/tools/astro-vscode/dist/**/*.js"]
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}/tools/vscode"],
|
||||
"outFiles": ["${workspaceRoot}/tools/vscode/dist/**/*.js"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
|
@ -16,7 +16,7 @@
|
|||
"name": "Attach to Server",
|
||||
"port": 6040,
|
||||
"restart": true,
|
||||
"outFiles": ["${workspaceRoot}/tools/astro-languageserver/dist/**/*.js"],
|
||||
"outFiles": ["${workspaceRoot}/tools/languageserver/dist/**/*.js"],
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
}
|
||||
],
|
||||
|
|
|
@ -5,6 +5,8 @@ import { isInTag, positionAt, offsetAt } from '../../core/documents/utils';
|
|||
import { pathToUrl } from '../../utils';
|
||||
import { getScriptKindFromFileName, isAstroFilePath, toVirtualAstroFilePath } from './utils';
|
||||
|
||||
const FILLER_DEFAULT_EXPORT = `\nexport default function() { return ''; };`;
|
||||
|
||||
/**
|
||||
* The mapper to get from original snapshot positions to generated and vice versa.
|
||||
*/
|
||||
|
@ -66,7 +68,15 @@ class AstroDocumentSnapshot implements DocumentSnapshot {
|
|||
}
|
||||
|
||||
get text() {
|
||||
return this.doc.getText();
|
||||
let raw = this.doc.getText();
|
||||
return this.transformContent(raw);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private transformContent(content: string) {
|
||||
return content.replace(/---/g, '///') +
|
||||
// TypeScript needs this to know there's a default export.
|
||||
FILLER_DEFAULT_EXPORT;
|
||||
}
|
||||
|
||||
get filePath() {
|
||||
|
@ -128,7 +138,9 @@ export class DocumentFragmentSnapshot implements Omit<DocumentSnapshot, 'getFrag
|
|||
|
||||
/** @internal */
|
||||
private transformContent(content: string) {
|
||||
return content.replace(/---/g, '///');
|
||||
return content.replace(/---/g, '///') +
|
||||
// TypeScript needs this to know there's a default export.
|
||||
FILLER_DEFAULT_EXPORT;
|
||||
}
|
||||
|
||||
getText(start: number, end: number) {
|
||||
|
@ -214,6 +226,10 @@ export class TypeScriptDocumentSnapshot implements DocumentSnapshot {
|
|||
return this as unknown as any;
|
||||
}
|
||||
|
||||
getOriginalPosition(pos: Position): Position {
|
||||
return pos;
|
||||
}
|
||||
|
||||
destroyFragment() {
|
||||
// nothing to clean up
|
||||
}
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import { join as pathJoin, dirname as pathDirname } from 'path';
|
||||
import { Document, DocumentManager, isInsideFrontmatter } from '../../core/documents';
|
||||
import type { ConfigManager } from '../../core/config';
|
||||
import type { CompletionsProvider, AppCompletionItem, AppCompletionList } from '../interfaces';
|
||||
import { SourceFile, ImportDeclaration, Node, SyntaxKind } from 'typescript';
|
||||
import { CompletionContext, DefinitionLink, FileChangeType, Position, LocationLink } from 'vscode-languageserver';
|
||||
import * as ts from 'typescript';
|
||||
import { CompletionsProviderImpl, CompletionEntryWithIdentifer } from './features/CompletionsProvider';
|
||||
import { LanguageServiceManager } from './LanguageServiceManager';
|
||||
import { SnapshotManager } from './SnapshotManager';
|
||||
import { convertToLocationRange, isVirtualFilePath, getScriptKindFromFileName } from './utils';
|
||||
import { convertToLocationRange, isVirtualAstroFilePath, isVirtualFilePath, getScriptKindFromFileName } from './utils';
|
||||
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './features/utils';
|
||||
import { isNotNullOrUndefined, pathToUrl } from '../../utils';
|
||||
|
||||
type BetterTS = typeof ts & {
|
||||
getTouchingPropertyName(sourceFile: SourceFile, pos: number): Node;
|
||||
};
|
||||
|
||||
export class TypeScriptPlugin implements CompletionsProvider {
|
||||
private readonly docManager: DocumentManager;
|
||||
private readonly configManager: ConfigManager;
|
||||
|
@ -46,26 +52,38 @@ export class TypeScriptPlugin implements CompletionsProvider {
|
|||
const filePath = tsDoc.filePath;
|
||||
const tsFilePath = filePath.endsWith('.ts') ? filePath : filePath + '.ts';
|
||||
|
||||
const defs = lang.getDefinitionAndBoundSpan(tsFilePath, mainFragment.offsetAt(mainFragment.getGeneratedPosition(position)));
|
||||
const fragmentPosition = mainFragment.getGeneratedPosition(position);
|
||||
const fragmentOffset = mainFragment.offsetAt(fragmentPosition);
|
||||
|
||||
let defs = lang.getDefinitionAndBoundSpan(tsFilePath, fragmentOffset);
|
||||
|
||||
if (!defs || !defs.definitions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Resolve all imports if we can
|
||||
if(this.goToDefinitionFoundOnlyAlias(tsFilePath, defs.definitions!)) {
|
||||
let importDef = this.getGoToDefinitionRefsForImportSpecifier(tsFilePath, fragmentOffset, lang);
|
||||
if(importDef) {
|
||||
defs = importDef;
|
||||
}
|
||||
}
|
||||
|
||||
const docs = new SnapshotFragmentMap(this.languageServiceManager);
|
||||
docs.set(tsDoc.filePath, { fragment: mainFragment, snapshot: tsDoc });
|
||||
|
||||
const result = await Promise.all(
|
||||
defs.definitions.map(async (def) => {
|
||||
defs.definitions!.map(async (def) => {
|
||||
const { fragment, snapshot } = await docs.retrieve(def.fileName);
|
||||
|
||||
if (isNoTextSpanInGeneratedCode(snapshot.getFullText(), def.textSpan)) {
|
||||
const fileName = isVirtualFilePath(def.fileName) ? def.fileName.substr(0, def.fileName.length - 3) : def.fileName;
|
||||
const textSpan = isVirtualAstroFilePath(tsFilePath) ? { start: 0, length: 0 } : def.textSpan;
|
||||
return LocationLink.create(
|
||||
pathToUrl(fileName),
|
||||
convertToLocationRange(fragment, def.textSpan),
|
||||
convertToLocationRange(fragment, def.textSpan),
|
||||
convertToLocationRange(mainFragment, defs.textSpan)
|
||||
convertToLocationRange(fragment, textSpan),
|
||||
convertToLocationRange(fragment, textSpan),
|
||||
convertToLocationRange(mainFragment, defs!.textSpan)
|
||||
);
|
||||
}
|
||||
})
|
||||
|
@ -110,4 +128,46 @@ export class TypeScriptPlugin implements CompletionsProvider {
|
|||
private isInsideFrontmatter(document: Document, position: Position) {
|
||||
return isInsideFrontmatter(document.getText(), document.offsetAt(position));
|
||||
}
|
||||
|
||||
private goToDefinitionFoundOnlyAlias(tsFileName: string, defs: readonly ts.DefinitionInfo[]) {
|
||||
return !!(defs.length === 1 &&
|
||||
defs[0].kind === 'alias' &&
|
||||
defs[0].fileName === tsFileName);
|
||||
}
|
||||
|
||||
private getGoToDefinitionRefsForImportSpecifier(tsFilePath: string, offset: number, lang: ts.LanguageService): ts.DefinitionInfoAndBoundSpan | undefined {
|
||||
const program = lang.getProgram();
|
||||
const sourceFile = program?.getSourceFile(tsFilePath);
|
||||
if (sourceFile) {
|
||||
let node = (ts as BetterTS).getTouchingPropertyName(sourceFile, offset);
|
||||
if(node && node.kind === SyntaxKind.Identifier) {
|
||||
if(node.parent.kind === SyntaxKind.ImportClause) {
|
||||
let decl = node.parent.parent as ImportDeclaration;
|
||||
let spec = ts.isStringLiteral(decl.moduleSpecifier) && decl.moduleSpecifier.text;
|
||||
if(spec) {
|
||||
let fileName = pathJoin(pathDirname(tsFilePath), spec);
|
||||
let start = node.pos + 1;
|
||||
let def: ts.DefinitionInfoAndBoundSpan = {
|
||||
definitions: [{
|
||||
kind: 'alias',
|
||||
fileName,
|
||||
name: '',
|
||||
containerKind: '',
|
||||
containerName: '',
|
||||
textSpan: {
|
||||
start: 0,
|
||||
length: 0
|
||||
}
|
||||
} as ts.DefinitionInfo],
|
||||
textSpan: {
|
||||
start,
|
||||
length: node.end - start
|
||||
}
|
||||
};
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ export function ensureRealAstroFilePath(filePath: string) {
|
|||
}
|
||||
|
||||
export function ensureRealFilePath(filePath: string) {
|
||||
return isVirtualFilePath(filePath) ? filePath.slice(0, 3) : filePath;
|
||||
return isVirtualFilePath(filePath) ? filePath.slice(0, filePath.length - 3) : filePath;
|
||||
}
|
||||
|
||||
export function findTsConfigPath(fileName: string, rootUris: string[]) {
|
||||
|
|
Loading…
Reference in a new issue