Skip to content

Leverage LSP to do compile instead of CLI #7239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
749bc60
implement compile request in server
chunyu3 Apr 20, 2025
11a6604
implement custom compile in compile server
chunyu3 Apr 25, 2025
432cc86
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 Apr 25, 2025
d3714ca
remove unused comment
chunyu3 Apr 25, 2025
c4b0d66
define trackAction log
chunyu3 Apr 25, 2025
32fc655
only trackAction when run custom compile
chunyu3 Apr 25, 2025
ecb8339
separate warning diagnostic and error diagnostic
chunyu3 Apr 27, 2025
5bfcf06
check the compile result
chunyu3 Apr 27, 2025
b7f5a38
reuse statusIcon from core
chunyu3 Apr 29, 2025
b890aac
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 6, 2025
c5c3ec0
add change log
chunyu3 May 6, 2025
50f0f56
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 6, 2025
64f5290
remove unused code
chunyu3 May 6, 2025
2a6cd2d
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 7, 2025
5aa5580
internal export CustomeCompileResult type
chunyu3 May 7, 2025
946dce2
Merge branch 'main' into lsp
chunyu3 May 7, 2025
9c10f23
Update packages/compiler/src/server/types.ts
chunyu3 May 8, 2025
2842198
resolve comments
chunyu3 May 8, 2025
5794426
resolve playground break issue
chunyu3 May 9, 2025
2e92ad2
split change log for each package
chunyu3 May 9, 2025
2a49fd5
resolve comment
chunyu3 May 9, 2025
1ed8d13
return diagnostic object instead of log string
chunyu3 May 9, 2025
a931e6b
return target position
chunyu3 May 9, 2025
4f61626
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 12, 2025
6334848
resolve comment
chunyu3 May 13, 2025
22cdef4
Merge branch 'main' into lsp
chunyu3 May 13, 2025
c85aa52
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 14, 2025
1c52f40
resolve comment
chunyu3 May 14, 2025
32808fe
Merge branch 'lsp' of https://github.com/chunyu3/typespec into lsp
chunyu3 May 14, 2025
a469699
add internalCompile capability
chunyu3 May 14, 2025
6393be9
define option bag for optional parameters
chunyu3 May 14, 2025
2188687
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 14, 2025
2728043
resolve circle dependency
chunyu3 May 14, 2025
2831176
rename client to tspLanguageClient
chunyu3 May 15, 2025
bd199b5
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 15, 2025
aad28af
Merge branch 'main' of https://github.com/microsoft/typespec into lsp
chunyu3 May 15, 2025
1b86d56
Merge branch 'main' into lsp
chunyu3 May 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .chronus/changes/lsp-2025-4-9-9-38-32.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- typespec-vscode
---

Use language server to compile project instead of CLI
7 changes: 7 additions & 0 deletions .chronus/changes/lsp-2025-4-9-9-38-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/compiler"
---

[LSP] Expose new compile project command
1 change: 1 addition & 0 deletions packages/compiler/src/internals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ if (!(globalThis as any).enableCompilerInternalsExport) {
export { NodeSystemHost } from "../core/node-system-host.js";
export { InitTemplateSchema } from "../init/init-template.js";
export { makeScaffoldingConfig, scaffoldNewProject } from "../init/scaffold.js";
export { InternalCompileResult, ServerDiagnostic } from "../server/index.js";
39 changes: 35 additions & 4 deletions packages/compiler/src/server/compile-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { parseYaml } from "../yaml/parser.js";
import { serverOptions } from "./constants.js";
import { FileService } from "./file-service.js";
import { FileSystemCache } from "./file-system-cache.js";
import { trackActionFunc } from "./server-track-action-task.js";
import { CompileResult, ServerHost, ServerLog } from "./types.js";
import { UpdateManger } from "./update-manager.js";

Expand All @@ -44,7 +45,14 @@ export interface CompileService {
* @param document The document to compile. This is not necessarily the entrypoint, compile will try to guess which entrypoint to compile to include this one.
* @returns the compiled result or undefined if compilation was aborted.
*/
compile(document: TextDocument | TextDocumentIdentifier): Promise<CompileResult | undefined>;
compile(
document: TextDocument | TextDocumentIdentifier,
additionalOptions?: CompilerOptions,
compileOptions?: {
bypassCache?: boolean;
trackAction?: boolean;
},
): Promise<CompileResult | undefined>;

/**
* Load the AST for the given document.
Expand Down Expand Up @@ -112,18 +120,26 @@ export function createCompileService({
*/
async function compile(
document: TextDocument | TextDocumentIdentifier,
additionalOptions?: CompilerOptions,
runOptions?: {
bypassCache?: boolean;
trackAction?: boolean;
},
): Promise<CompileResult | undefined> {
const path = await fileService.getPath(document);
const mainFile = await getMainFileForDocument(path);
const config = await getConfig(mainFile);
configFilePath = config.filename;
log({ level: "debug", message: `config resolved`, detail: config });

const [optionsFromConfig, _] = resolveOptionsFromConfig(config, { cwd: path });
const [optionsFromConfig, _] = resolveOptionsFromConfig(config, {
cwd: getDirectoryPath(path),
});
const options: CompilerOptions = {
...optionsFromConfig,
...serverOptions,
...(additionalOptions ?? {}),
};

// add linter rule for unused using if user didn't configure it explicitly
const unusedUsingRule = `${builtInLinterLibraryName}/${builtInLinterRule_UnusedUsing}`;
if (
Expand Down Expand Up @@ -154,7 +170,22 @@ export function createCompileService({

let program: Program;
try {
program = await compileProgram(compilerHost, mainFile, options, oldPrograms.get(mainFile));
program = await compileProgram(
runOptions?.trackAction
? {
...compilerHost,
logSink: {
log: compilerHost.logSink.log,
getPath: compilerHost.logSink.getPath,
trackAction: (message, finalMessage, action) =>
trackActionFunc(serverHost.log, message, finalMessage, action),
},
}
: compilerHost,
mainFile,
options,
runOptions?.bypassCache ? undefined : oldPrograms.get(mainFile),
);
oldPrograms.set(mainFile, program);
if (!fileService.upToDate(document)) {
return undefined;
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ export type {
InitProjectTemplate,
InitProjectTemplateEmitterTemplate,
InitProjectTemplateLibrarySpec,
InternalCompileResult,
SemanticToken,
SemanticTokenKind,
Server,
ServerCustomCapacities,
ServerDiagnostic,
ServerHost,
ServerInitializeResult,
ServerLog,
Expand Down
87 changes: 87 additions & 0 deletions packages/compiler/src/server/server-track-action-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { TaskStatus, TrackActionTask } from "../core/types.js";
import { ServerLog } from "./types.js";

export class ServerTrackActionTask implements TrackActionTask {
#log: (log: ServerLog) => void;
#message: string;
#running: boolean;
#finalMessage: string;

constructor(message: string, finalMessage: string, log: (log: ServerLog) => void) {
this.#message = message;
this.#finalMessage = finalMessage;
this.#log = log;
this.#running = true;
}

get message() {
return this.#message;
}

get isStopped() {
return !this.#running;
}

set message(newMessage: string) {
this.#message = newMessage;
}

start() {
this.#log({
level: "info",
message: this.#message,
});
}

succeed(message?: string) {
this.stop("success", message);
}
fail(message?: string) {
this.stop("failure", message);
}
warn(message?: string) {
this.stop("warn", message);
}
skip(message?: string) {
this.stop("skipped", message);
}

stop(status: TaskStatus, message?: string) {
this.#running = false;
this.#message = message ?? this.#finalMessage;
this.#log({
level: status !== "failure" ? "info" : "error",
message: `[${TaskStatusText[status]}] ${this.#message}\n`,
});
}
}

const TaskStatusText = {
success: "succeed",
failure: "failed",
warn: "succeeded with warnings",
skipped: "skipped",
};

/** internal */
export async function trackActionFunc<T>(
log: (log: ServerLog) => void,
message: string,
finalMessage: string,
asyncAction: (task: TrackActionTask) => Promise<T>,
): Promise<T> {
const task = new ServerTrackActionTask(message, finalMessage, log);
task.start();

try {
const result = await asyncAction(task);
if (!task.isStopped) {
task.succeed();
}

return result;
} catch (error) {
task.fail(message);
throw error;
}
}
2 changes: 2 additions & 0 deletions packages/compiler/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ function main() {
connection.onRequest(getInitProjectContextRequestName, profile(s.getInitProjectContext));
const initProjectRequestName: CustomRequestName = "typespec/initProject";
connection.onRequest(initProjectRequestName, profile(s.initProject));
const compileProjectRequestName: CustomRequestName = "typespec/internalCompile";
connection.onRequest(compileProjectRequestName, profile(s.internalCompile));

documents.onDidChangeContent(profile(s.checkChange));
documents.onDidClose(profile(s.documentClosed));
Expand Down
60 changes: 60 additions & 0 deletions packages/compiler/src/server/serverlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { builtInLinterRule_UnusedTemplateParameter } from "../core/linter-rules/
import { builtInLinterRule_UnusedUsing } from "../core/linter-rules/unused-using.rule.js";
import { builtInLinterLibraryName } from "../core/linter.js";
import { formatLog } from "../core/logger/index.js";
import { CompilerOptions } from "../core/options.js";
import { getPositionBeforeTrivia } from "../core/parser-utils.js";
import { getNodeAtPosition, getNodeAtPositionDetail, visitChildren } from "../core/parser.js";
import {
Expand Down Expand Up @@ -117,9 +118,11 @@ import {
CompileResult,
InitProjectConfig,
InitProjectContext,
InternalCompileResult,
SemanticTokenKind,
Server,
ServerCustomCapacities,
ServerDiagnostic,
ServerHost,
ServerInitializeResult,
ServerLog,
Expand Down Expand Up @@ -199,6 +202,7 @@ export function createServer(host: ServerHost): Server {
getInitProjectContext,
validateInitProjectTemplate,
initProject,
internalCompile,
};

async function initialize(params: InitializeParams): Promise<InitializeResult> {
Expand Down Expand Up @@ -301,6 +305,7 @@ export function createServer(host: ServerHost): Server {
getInitProjectContext: true,
initProject: true,
validateInitProjectTemplate: true,
internalCompile: true,
};
// the file path is expected to be .../@typespec/compiler/dist/src/server/serverlib.js
const curFile = normalizePath(compilerHost.fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -367,6 +372,61 @@ export function createServer(host: ServerHost): Server {
}
}

async function internalCompile(param: {
doc: TextDocumentIdentifier;
options: CompilerOptions;
}): Promise<InternalCompileResult> {
const option: CompilerOptions = {
...param.options,
};

const result = await compileService.compile(param.doc, option, {
bypassCache: true,
trackAction: true,
});
if (result === undefined) {
return {
hasError: true,
diagnostics: [
{
code: "internal-error",
message:
"Failed to get compiler result, please check the compilation output for details",
severity: "error",
target: NoTarget,
url: undefined,
},
],
entrypoint: undefined,
options: undefined,
};
} else {
return {
hasError: result.program.hasError(),
diagnostics: result.program.diagnostics.map((diagnostic) => {
const target = getSourceLocation(diagnostic.target, { locateId: true });
let position = undefined;
if (target?.file) {
const lineAndCharacter = target.file.getLineAndCharacterOfPosition(target.pos);
position = {
line: lineAndCharacter.line + 1,
column: lineAndCharacter.character + 1,
};
}
return {
code: diagnostic.code,
message: diagnostic.message,
severity: diagnostic.severity,
target: { ...target, position: position },
url: diagnostic.url,
} as ServerDiagnostic;
}),
entrypoint: result.document?.uri,
options: result.program.compilerOptions,
};
}
}

async function renameFiles(params: RenameFilesParams): Promise<void> {
const firstFilePath = params.files[0];
if (!firstFilePath) {
Expand Down
29 changes: 26 additions & 3 deletions packages/compiler/src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ import {
import type { TextDocument, TextEdit } from "vscode-languageserver-textdocument";
import type { CompilerOptions } from "../core/options.js";
import type { Program } from "../core/program.js";
import type { CompilerHost, SourceFile, TypeSpecScriptNode } from "../core/types.js";
import type {
CompilerHost,
Diagnostic,
NoTarget,
SourceFile,
SourceLocation,
TypeSpecScriptNode,
} from "../core/types.js";
import { LoadedCoreTemplates } from "../init/core-templates.js";
import { EmitterTemplate, InitTemplate, InitTemplateLibrarySpec } from "../init/init-template.js";
import { ScaffoldingConfig } from "../init/scaffold.js";
Expand Down Expand Up @@ -70,6 +77,17 @@ export interface CompileResult {
readonly optionsFromConfig: CompilerOptions;
}

export interface ServerDiagnostic extends Diagnostic {
target: (SourceLocation & { position?: { line: number; column: number } }) | typeof NoTarget;
}

export interface InternalCompileResult {
readonly hasError: boolean;
readonly diagnostics: ServerDiagnostic[];
readonly entrypoint?: string;
readonly options?: CompilerOptions;
}

export interface Server {
readonly pendingMessages: readonly ServerLog[];
readonly workspaceFolders: readonly ServerWorkspaceFolder[];
Expand Down Expand Up @@ -106,6 +124,10 @@ export interface Server {
getInitProjectContext(): Promise<InitProjectContext>;
validateInitProjectTemplate(param: { template: InitTemplate }): Promise<boolean>;
initProject(param: { config: InitProjectConfig }): Promise<boolean>;
internalCompile(param: {
doc: TextDocumentIdentifier;
options: CompilerOptions;
}): Promise<InternalCompileResult>;
}

export interface ServerSourceFile extends SourceFile {
Expand Down Expand Up @@ -156,12 +178,13 @@ export interface SemanticToken {
export type CustomRequestName =
| "typespec/getInitProjectContext"
| "typespec/initProject"
| "typespec/validateInitProjectTemplate";

| "typespec/validateInitProjectTemplate"
| "typespec/internalCompile";
export interface ServerCustomCapacities {
getInitProjectContext?: boolean;
validateInitProjectTemplate?: boolean;
initProject?: boolean;
internalCompile?: boolean;
}

export interface ServerInitializeResult extends InitializeResult {
Expand Down
7 changes: 7 additions & 0 deletions packages/typespec-vscode/src/extension-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TspLanguageClient } from "./tsp-language-client";

export let tspLanguageClient: TspLanguageClient | undefined;

export function setTspLanguageClient(newTspLanguageClient: TspLanguageClient | undefined) {
tspLanguageClient = newTspLanguageClient;
}
Loading
Loading