summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@matthewphillips.info> 2021-05-20 15:14:27 -0400
committerGravatar GitHub <noreply@github.com> 2021-05-20 15:14:27 -0400
commit4834c090f85e21a63c5e64fecfb70e090b5708a6 (patch)
tree33644b68c3b4789ab5c390d0036442b7b83b555b
parent6ce068b838a165c65898b5581f7508b75e256efc (diff)
downloadastro-4834c090f85e21a63c5e64fecfb70e090b5708a6.tar.gz
astro-4834c090f85e21a63c5e64fecfb70e090b5708a6.tar.zst
astro-4834c090f85e21a63c5e64fecfb70e090b5708a6.zip
Support for Go to Definition in Astro components (#220)
* Start on css completion * Support for CSS completions * Adds support for Go to Definition in TypeScript in Astro * Run formatting * Add support for Astro component go to definition * Formatting * Jump directly to file where definition is found
-rw-r--r--tools/astro-languageserver/src/index.ts2
-rw-r--r--tools/astro-languageserver/src/plugins/astro/AstroPlugin.ts102
-rw-r--r--tools/astro-languageserver/src/plugins/typescript/TypeScriptPlugin.ts10
-rw-r--r--tools/astro-languageserver/src/plugins/typescript/languageService.ts6
-rw-r--r--tools/astro-languageserver/src/plugins/typescript/utils.ts6
5 files changed, 116 insertions, 10 deletions
diff --git a/tools/astro-languageserver/src/index.ts b/tools/astro-languageserver/src/index.ts
index e3532f252..5e4c736a2 100644
--- a/tools/astro-languageserver/src/index.ts
+++ b/tools/astro-languageserver/src/index.ts
@@ -23,7 +23,7 @@ export function startServer() {
filterIncompleteCompletions: !evt.initializationOptions?.dontFilterIncompleteCompletions,
definitionLinkSupport: !!evt.capabilities.textDocument?.definition?.linkSupport,
});
- pluginHost.register(new AstroPlugin(docManager, configManager));
+ pluginHost.register(new AstroPlugin(docManager, configManager, workspaceUris));
pluginHost.register(new HTMLPlugin(docManager, configManager));
pluginHost.register(new CSSPlugin(docManager, configManager));
pluginHost.register(new TypeScriptPlugin(docManager, configManager, workspaceUris));
diff --git a/tools/astro-languageserver/src/plugins/astro/AstroPlugin.ts b/tools/astro-languageserver/src/plugins/astro/AstroPlugin.ts
index 6baf407a5..3d5d10430 100644
--- a/tools/astro-languageserver/src/plugins/astro/AstroPlugin.ts
+++ b/tools/astro-languageserver/src/plugins/astro/AstroPlugin.ts
@@ -1,17 +1,36 @@
+import { DefinitionLink } from 'vscode-languageserver';
import type { Document, DocumentManager } from '../../core/documents';
import type { ConfigManager } from '../../core/config';
-import type { CompletionsProvider, AppCompletionItem, AppCompletionList, FoldingRangeProvider } from '../interfaces';
-import { CompletionContext, Position, CompletionList, CompletionItem, CompletionItemKind, InsertTextFormat, FoldingRange, TextEdit } from 'vscode-languageserver';
-import { isPossibleClientComponent } from '../../utils';
+import type { CompletionsProvider, AppCompletionList, FoldingRangeProvider } from '../interfaces';
+import {
+ CompletionContext,
+ Position,
+ CompletionList,
+ CompletionItem,
+ CompletionItemKind,
+ InsertTextFormat,
+ LocationLink,
+ FoldingRange,
+ Range,
+ TextEdit,
+} from 'vscode-languageserver';
+import { Node } from 'vscode-html-languageservice';
+import { isPossibleClientComponent, pathToUrl, urlToPath } from '../../utils';
+import { isInsideFrontmatter } from '../../core/documents/utils';
+import * as ts from 'typescript';
+import { LanguageServiceManager as TypeScriptLanguageServiceManager } from '../typescript/LanguageServiceManager';
+import { ensureRealFilePath } from '../typescript/utils';
import { FoldingRangeKind } from 'vscode-languageserver-types';
export class AstroPlugin implements CompletionsProvider, FoldingRangeProvider {
private readonly docManager: DocumentManager;
private readonly configManager: ConfigManager;
+ private readonly tsLanguageServiceManager: TypeScriptLanguageServiceManager;
- constructor(docManager: DocumentManager, configManager: ConfigManager) {
+ constructor(docManager: DocumentManager, configManager: ConfigManager, workspaceUris: string[]) {
this.docManager = docManager;
this.configManager = configManager;
+ this.tsLanguageServiceManager = new TypeScriptLanguageServiceManager(docManager, configManager, workspaceUris);
}
async getCompletions(document: Document, position: Position, completionContext?: CompletionContext): Promise<AppCompletionList | null> {
@@ -53,6 +72,53 @@ export class AstroPlugin implements CompletionsProvider, FoldingRangeProvider {
];
}
+ async getDefinitions(document: Document, position: Position): Promise<DefinitionLink[]> {
+ if (this.isInsideFrontmatter(document, position)) {
+ return [];
+ }
+
+ const offset = document.offsetAt(position);
+ const html = document.html;
+
+ const node = html.findNodeAt(offset);
+ if (!this.isComponentTag(node)) {
+ return [];
+ }
+
+ const [componentName] = node.tag!.split(':');
+
+ const filePath = urlToPath(document.uri);
+ const tsFilePath = filePath + '.ts';
+
+ const { lang, tsDoc } = await this.tsLanguageServiceManager.getTypeScriptDoc(document);
+
+ const sourceFile = lang.getProgram()?.getSourceFile(tsFilePath);
+ if (!sourceFile) {
+ return [];
+ }
+
+ const specifier = this.getImportSpecifierForIdentifier(sourceFile, componentName);
+ if(!specifier) {
+ return [];
+ }
+
+ const defs = lang.getDefinitionAtPosition(tsFilePath, specifier.getStart());
+ if(!defs) {
+ return [];
+ }
+
+ const tsFragment = await tsDoc.getFragment();
+ const startRange: Range = Range.create(Position.create(0, 0), Position.create(0, 0));
+ const links = defs.map(def => {
+ const defFilePath = ensureRealFilePath(def.fileName);
+ return LocationLink.create(
+ pathToUrl(defFilePath), startRange, startRange
+ );
+ });
+
+ return links;
+ }
+
private getClientHintCompletion(document: Document, position: Position, completionContext?: CompletionContext): CompletionItem[] | null {
const node = document.html.findNodeAt(document.offsetAt(position));
if (!isPossibleClientComponent(node)) return null;
@@ -104,4 +170,32 @@ export class AstroPlugin implements CompletionsProvider, FoldingRangeProvider {
}
return null;
}
+
+ private isInsideFrontmatter(document: Document, position: Position) {
+ return isInsideFrontmatter(document.getText(), document.offsetAt(position));
+ }
+
+ private isComponentTag(node: Node): boolean {
+ if (!node.tag) {
+ return false;
+ }
+ const firstChar = node.tag[0];
+ return /[A-Z]/.test(firstChar);
+ }
+
+ private getImportSpecifierForIdentifier(sourceFile: ts.SourceFile, identifier: string): ts.Expression | undefined {
+ let importSpecifier: ts.Expression | undefined = undefined;
+ ts.forEachChild(sourceFile, (tsNode) => {
+ if (ts.isImportDeclaration(tsNode)) {
+ if (tsNode.importClause) {
+ const { name } = tsNode.importClause;
+ if (name && name.getText() === identifier) {
+ importSpecifier = tsNode.moduleSpecifier;
+ return true;
+ }
+ }
+ }
+ });
+ return importSpecifier;
+ }
}
diff --git a/tools/astro-languageserver/src/plugins/typescript/TypeScriptPlugin.ts b/tools/astro-languageserver/src/plugins/typescript/TypeScriptPlugin.ts
index 30781a508..a3521a302 100644
--- a/tools/astro-languageserver/src/plugins/typescript/TypeScriptPlugin.ts
+++ b/tools/astro-languageserver/src/plugins/typescript/TypeScriptPlugin.ts
@@ -1,4 +1,4 @@
-import type { Document, DocumentManager } from '../../core/documents';
+import { Document, DocumentManager, isInsideFrontmatter } from '../../core/documents';
import type { ConfigManager } from '../../core/config';
import type { CompletionsProvider, AppCompletionItem, AppCompletionList } from '../interfaces';
import { CompletionContext, DefinitionLink, FileChangeType, Position, LocationLink } from 'vscode-languageserver';
@@ -36,6 +36,10 @@ export class TypeScriptPlugin implements CompletionsProvider {
}
async getDefinitions(document: Document, position: Position): Promise<DefinitionLink[]> {
+ if(!this.isInsideFrontmatter(document, position)) {
+ return [];
+ }
+
const { lang, tsDoc } = await this.languageServiceManager.getTypeScriptDoc(document);
const mainFragment = await tsDoc.getFragment();
@@ -102,4 +106,8 @@ export class TypeScriptPlugin implements CompletionsProvider {
public async getSnapshotManager(fileName: string) {
return this.languageServiceManager.getSnapshotManager(fileName);
}
+
+ private isInsideFrontmatter(document: Document, position: Position) {
+ return isInsideFrontmatter(document.getText(), document.offsetAt(position));
+ }
}
diff --git a/tools/astro-languageserver/src/plugins/typescript/languageService.ts b/tools/astro-languageserver/src/plugins/typescript/languageService.ts
index b7ff6df20..22e2b1cdd 100644
--- a/tools/astro-languageserver/src/plugins/typescript/languageService.ts
+++ b/tools/astro-languageserver/src/plugins/typescript/languageService.ts
@@ -31,9 +31,9 @@ export async function getLanguageService(path: string, workspaceUris: string[],
if (services.has(tsconfigPath)) {
service = (await services.get(tsconfigPath)) as LanguageServiceContainer;
} else {
- const newService = createLanguageService(tsconfigPath, workspaceRoot, docContext);
- services.set(tsconfigPath, newService);
- service = await newService;
+ const newServicePromise = createLanguageService(tsconfigPath, workspaceRoot, docContext);
+ services.set(tsconfigPath, newServicePromise);
+ service = await newServicePromise;
}
return service;
diff --git a/tools/astro-languageserver/src/plugins/typescript/utils.ts b/tools/astro-languageserver/src/plugins/typescript/utils.ts
index b212d9cd3..1b1a673da 100644
--- a/tools/astro-languageserver/src/plugins/typescript/utils.ts
+++ b/tools/astro-languageserver/src/plugins/typescript/utils.ts
@@ -189,6 +189,10 @@ export function ensureRealAstroFilePath(filePath: string) {
return isVirtualAstroFilePath(filePath) ? toRealAstroFilePath(filePath) : filePath;
}
+export function ensureRealFilePath(filePath: string) {
+ return isVirtualFilePath(filePath) ? filePath.slice(0, 3) : filePath;
+}
+
export function findTsConfigPath(fileName: string, rootUris: string[]) {
const searchDir = dirname(fileName);
const path = ts.findConfigFile(searchDir, ts.sys.fileExists, 'tsconfig.json') || ts.findConfigFile(searchDir, ts.sys.fileExists, 'jsconfig.json') || '';
@@ -229,4 +233,4 @@ function append(result: string, str: string, n: number): string {
str += str;
}
return result;
-}
+} \ No newline at end of file