Skip to content

Commit

Permalink
Go to import (#2978)
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheeguerin committed Mar 4, 2024
1 parent 82fab0a commit de6bd7b
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 6 deletions.
8 changes: 8 additions & 0 deletions .chronus/changes/goto-import-2024-2-1-22-43-14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/compiler"
---

[IDE] Go to imports
49 changes: 43 additions & 6 deletions packages/compiler/src/server/serverlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ import { CharCode, codePointBefore, isIdentifierContinue } from "../core/charcod
import { compilerAssert, getSourceLocation } from "../core/diagnostics.js";
import { formatTypeSpec } from "../core/formatter.js";
import { getTypeName } from "../core/helpers/type-name-utils.js";
import { ResolveModuleHost, resolveModule } from "../core/index.js";
import { getNodeAtPosition, visitChildren } from "../core/parser.js";
import { ensureTrailingDirectorySeparator } from "../core/path-utils.js";
import { ensureTrailingDirectorySeparator, getDirectoryPath } from "../core/path-utils.js";
import { Program } from "../core/program.js";
import { skipTrivia, skipWhiteSpace } from "../core/scanner.js";
import { createSourceFile, getSourceFileKindFromExt } from "../core/source-file.js";
Expand All @@ -64,7 +65,7 @@ import {
TypeReferenceNode,
TypeSpecScriptNode,
} from "../core/types.js";
import { getNormalizedRealPath } from "../utils/misc.js";
import { getNormalizedRealPath, resolveTspMain } from "../utils/misc.js";
import { getSemanticTokens } from "./classify.js";
import { createCompileService } from "./compile-service.js";
import { resolveCompletion } from "./completion.js";
Expand Down Expand Up @@ -599,11 +600,47 @@ export function createServer(host: ServerHost): Server {
if (result === undefined) {
return [];
}
const id = getNodeAtPosition(result.script, result.document.offsetAt(params.position));
const sym =
id?.kind === SyntaxKind.Identifier ? result.program.checker.resolveIdentifier(id) : undefined;
return getLocations(sym?.declarations);
const node = getNodeAtPosition(result.script, result.document.offsetAt(params.position));
switch (node?.kind) {
case SyntaxKind.Identifier:
const sym = result.program.checker.resolveIdentifier(node);
return getLocations(sym?.declarations);
case SyntaxKind.StringLiteral:
if (node.parent?.kind === SyntaxKind.ImportStatement) {
return [await getImportLocation(node.value, result.script)];
} else {
return [];
}
}
return [];
}

async function getImportLocation(
importPath: string,
currentFile: TypeSpecScriptNode
): Promise<Location> {
const host: ResolveModuleHost = {
realpath: compilerHost.realpath,
readFile: async (path) => {
const file = await compilerHost.readFile(path);
return file.text;
},
stat: compilerHost.stat,
};
const resolved = await resolveModule(host, importPath, {
baseDir: getDirectoryPath(currentFile.file.path),
resolveMain(pkg) {
// this lets us follow node resolve semantics more-or-less exactly
// but using tspMain instead of main.
return resolveTspMain(pkg) ?? pkg.main;
},
});
return {
uri: fileService.getURL(resolved.type === "file" ? resolved.path : resolved.mainFile),
range: Range.create(0, 0, 0, 0),
};
}

async function complete(params: CompletionParams): Promise<CompletionList> {
const completions: CompletionList = {
isIncomplete: false,
Expand Down
73 changes: 73 additions & 0 deletions packages/compiler/test/server/goto-definition.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { pathToFileURL } from "url";
import { describe, expect, it } from "vitest";
import { Location } from "vscode-languageserver";
import {
createTestServerHost,
extractCursor,
resolveVirtualPath,
} from "../../src/testing/index.js";

function resolveVirtualPathUri(path: string): string {
return pathToFileURL(resolveVirtualPath(path)).href;
}

async function goToDefinitionAtCursor(
sourceWithCursor: string,
otherFiles: Record<string, string> = {}
): Promise<Location[]> {
const { source, pos } = extractCursor(sourceWithCursor);

const testHost = await createTestServerHost();
for (const [filename, content] of Object.entries(otherFiles)) {
testHost.addOrUpdateDocument(filename, content);
}
const textDocument = testHost.addOrUpdateDocument("main.tsp", source);
return await testHost.server.gotoDefinition({
textDocument,
position: textDocument.positionAt(pos),
});
}

describe("go to imports", () => {
it("go to local import", async () => {
const locations = await goToDefinitionAtCursor(
`
import "./othe┆r.tsp";
`,
{ "other.tsp": "model Other {}" }
);
expect(locations).toEqual([
{
range: {
end: { character: 0, line: 0 },
start: { character: 0, line: 0 },
},
uri: resolveVirtualPathUri("other.tsp"),
},
]);
});

it("go to library import", async () => {
const locations = await goToDefinitionAtCursor(
`
import "┆test-lib";
`,
{
"node_modules/test-lib/package.json": JSON.stringify({
name: "test-lib",
tspMain: "./main.tsp",
}),
"node_modules/test-lib/main.tsp": "model Other {}",
}
);
expect(locations).toEqual([
{
range: {
end: { character: 0, line: 0 },
start: { character: 0, line: 0 },
},
uri: resolveVirtualPathUri("node_modules/test-lib/main.tsp"),
},
]);
});
});

0 comments on commit de6bd7b

Please sign in to comment.