From 749bc604801a74148a72aebf152c40a0fc35e80e Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 20 Apr 2025 19:55:00 +0800 Subject: [PATCH 01/23] implement compile request in server --- packages/compiler/src/server/types.ts | 3 ++- packages/typespec-vscode/src/tsp-language-client.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index 7050e79594d..5c2dcbe1f68 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -154,7 +154,8 @@ export interface SemanticToken { export type CustomRequestName = | "typespec/getInitProjectContext" | "typespec/initProject" - | "typespec/validateInitProjectTemplate"; + | "typespec/validateInitProjectTemplate" + | "typespec/compileProject"; export interface ServerCustomCapacities { getInitProjectContext?: boolean; validateInitProjectTemplate?: boolean; diff --git a/packages/typespec-vscode/src/tsp-language-client.ts b/packages/typespec-vscode/src/tsp-language-client.ts index 0c20095130d..94b2c525149 100644 --- a/packages/typespec-vscode/src/tsp-language-client.ts +++ b/packages/typespec-vscode/src/tsp-language-client.ts @@ -88,6 +88,17 @@ export class TspLanguageClient { } } + public async compileProject(): Promise { + const compileProjectRequestName: CustomRequestName = "typespec/compileProject"; + try { + const result = await this.client.sendRequest(compileProjectRequestName); + return result === true; + } catch (e) { + logger.error("Unexpected error when compiling project", [e]); + return false; + } + } + async runCliCommand(args: string[], cwd: string): Promise { if (isWhitespaceStringOrUndefined(this.initializeResult?.compilerCliJsPath)) { logger.warning( From 11a66046e6a78e5ff82db34332026c9590f56001 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 25 Apr 2025 10:21:51 +0800 Subject: [PATCH 02/23] implement custom compile in compile server --- packages/compiler/src/index.ts | 1 + .../compiler/src/server/compile-service.ts | 21 +++- packages/compiler/src/server/index.ts | 1 + packages/compiler/src/server/server.ts | 2 + packages/compiler/src/server/serverlib.ts | 115 ++++++++++++++++-- packages/compiler/src/server/types.ts | 11 ++ packages/typespec-vscode/src/extension.ts | 2 +- .../src/tsp-language-client.ts | 23 +++- .../typespec-vscode/src/typespec-utils.ts | 5 + .../src/vscode-cmd/emit-code/emit-code.ts | 49 ++++++-- 10 files changed, 200 insertions(+), 30 deletions(-) diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index b79466982e7..b51e45fe139 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -210,6 +210,7 @@ export { export { CompileResult, createServer, + CustomCompileResult, TypeSpecLanguageConfiguration, type CustomRequestName, type InitProjectConfig, diff --git a/packages/compiler/src/server/compile-service.ts b/packages/compiler/src/server/compile-service.ts index 324da2aefc7..0e20bbdd5fc 100644 --- a/packages/compiler/src/server/compile-service.ts +++ b/packages/compiler/src/server/compile-service.ts @@ -44,7 +44,11 @@ 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; + compile( + document: TextDocument | TextDocumentIdentifier, + additionalOptions?: CompilerOptions, + bypassCache?: boolean, + ): Promise; /** * Load the AST for the given document. @@ -101,17 +105,21 @@ export function createCompileService({ async function compile( document: TextDocument | TextDocumentIdentifier, + additionalOptions?: CompilerOptions, + bypassCache = false, ): Promise { 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}`; @@ -143,7 +151,12 @@ export function createCompileService({ let program: Program; try { - program = await compileProgram(compilerHost, mainFile, options, oldPrograms.get(mainFile)); + program = await compileProgram( + compilerHost, + mainFile, + options, + bypassCache ? undefined : oldPrograms.get(mainFile), + ); oldPrograms.set(mainFile, program); if (!fileService.upToDate(document)) { return undefined; diff --git a/packages/compiler/src/server/index.ts b/packages/compiler/src/server/index.ts index cce1a78d68c..a3c67fc5c68 100644 --- a/packages/compiler/src/server/index.ts +++ b/packages/compiler/src/server/index.ts @@ -2,6 +2,7 @@ export { TypeSpecLanguageConfiguration } from "./language-config.js"; export { createServer } from "./serverlib.js"; export type { CompileResult, + CustomCompileResult, CustomRequestName, InitProjectConfig, InitProjectContext, diff --git a/packages/compiler/src/server/server.ts b/packages/compiler/src/server/server.ts index 355fbd214eb..abcf7ec2b4f 100644 --- a/packages/compiler/src/server/server.ts +++ b/packages/compiler/src/server/server.ts @@ -135,6 +135,8 @@ function main() { connection.onRequest(getInitProjectContextRequestName, profile(s.getInitProjectContext)); const initProjectRequestName: CustomRequestName = "typespec/initProject"; connection.onRequest(initProjectRequestName, profile(s.initProject)); + const compileProjectRequestName: CustomRequestName = "typespec/compileProject"; + connection.onRequest(compileProjectRequestName, profile(s.compileProject)); documents.onDidChangeContent(profile(s.checkChange)); documents.onDidClose(profile(s.documentClosed)); diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 07733dfd630..fc825a9f31c 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -54,7 +54,8 @@ import { getEntityName, getTypeName } from "../core/helpers/type-name-utils.js"; import { builtInLinterRule_UnusedTemplateParameter } from "../core/linter-rules/unused-template-parameter.rule.js"; 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 { createConsoleSink, formatDiagnostic, 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 { @@ -80,6 +81,7 @@ import { PositionDetail, ProcessedLog, SourceFile, + SourceLocation, SyntaxKind, TextRange, TypeReferenceNode, @@ -110,6 +112,7 @@ import { } from "./type-details.js"; import { CompileResult, + CustomCompileResult, InitProjectConfig, InitProjectContext, SemanticTokenKind, @@ -193,6 +196,7 @@ export function createServer(host: ServerHost): Server { getInitProjectContext, validateInitProjectTemplate, initProject, + compileProject, }; async function initialize(params: InitializeParams): Promise { @@ -312,6 +316,7 @@ export function createServer(host: ServerHost): Server { } async function getInitProjectContext(): Promise { + log({ level: "info", message: "crystal->Get init project context" }); return { coreInitTemplates: await getTypeSpecCoreTemplates(host.compilerHost), }; @@ -347,6 +352,79 @@ export function createServer(host: ServerHost): Server { } } + async function compileProject(param: { + doc: TextDocumentIdentifier; + options: CompilerOptions; + }): Promise { + const option: CompilerOptions = { + ...param.options, + }; + + const result = await compileService.compile(param.doc, option, true); + if (result === undefined) { + return { + hasError: true, + diagnostics: + "Failed to get compiler result, please check the compilation output for details", + entryPoint: undefined, + options: undefined, + }; + } else { + const isSourceLocation = (obj: any): obj is SourceLocation => { + return obj && "file" in obj; + }; + let diagnostics: string | undefined = undefined; + if (result.program.diagnostics.length > 0) { + // diagnostics = result.program.diagnostics + // .map((diagnostic) => + // formatLog( + // { + // level: diagnostic.severity, + // message: diagnostic.message, + // code: diagnostic.code, + // url: diagnostic.url, + // sourceLocation: getSourceLocation(diagnostic.target, { locateId: true }), + // related: getRelatedLocations(diagnostic), + // }, + // { pretty: false }, + // ), + // ) + // .join("\n"); + diagnostics = result.program.diagnostics + .map((diagnostic) => formatDiagnostic(diagnostic, { pretty: false })) + .join("\n"); + } + // if ((result?.program.diagnostics.length ?? 0) > 0) { + // diagnostics = JSON.stringify( + // result?.program.diagnostics.map((x) => { + // let target = undefined; + // if (x.target && x.target !== NoTarget && isSourceLocation(x.target)) { + // target = x.target; + // } + + // return { + // message: x.message, + // code: x.code, + // severity: x.severity, + // path: target?.file.path, + // text: target?.file.text, + // pos: target?.pos, + // end: target?.end, + // }; + // }), + // null, + // 2, + // ); + // } + return { + hasError: result.program.hasError(), + diagnostics: diagnostics ?? "", + entryPoint: result.document.uri, + options: result.program.compilerOptions, + }; + } + } + async function workspaceFoldersChanged(e: WorkspaceFoldersChangeEvent) { log({ level: "info", message: "Workspace Folders Changed", detail: e }); const map = new Map(workspaceFolders.map((f) => [f.uri, f])); @@ -1085,6 +1163,19 @@ export function createServer(host: ServerHost): Server { } function createCompilerHost(): CompilerHost { + const logSink = createConsoleSink({ + pretty: true, + pathRelativeTo: process.cwd(), + trackAction: true, + }); + logSink.log = (log: ProcessedLog) => { + const msg = formatLog(log, { excludeLogLevel: true }); + const sLog: ServerLog = { + level: log.level, + message: msg, + }; + host.log(sLog); + }; const base = host.compilerHost; return { ...base, @@ -1092,16 +1183,18 @@ export function createServer(host: ServerHost): Server { readFile, stat, getSourceFileKind, - logSink: { - log: (log: ProcessedLog) => { - const msg = formatLog(log, { excludeLogLevel: true }); - const sLog: ServerLog = { - level: log.level, - message: msg, - }; - host.log(sLog); - }, - }, + logSink, + // logSink: { + // log: (log: ProcessedLog) => { + // const msg = formatLog(log, { excludeLogLevel: true }); + // const sLog: ServerLog = { + // level: log.level, + // message: msg, + // }; + // host.log(sLog); + // }, + // trackAction: (message, finalMessage, action) => trackAction(message, finalMessage, action), + // }, }; async function readFile(path: string): Promise { diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index 5c2dcbe1f68..18a03dd5ab2 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -69,6 +69,13 @@ export interface CompileResult { readonly optionsFromConfig: CompilerOptions; } +export interface CustomCompileResult { + readonly hasError: boolean; + readonly diagnostics: string | undefined; + readonly entryPoint: string | undefined; + readonly options: CompilerOptions | undefined; +} + export interface Server { readonly pendingMessages: readonly ServerLog[]; readonly workspaceFolders: readonly ServerWorkspaceFolder[]; @@ -104,6 +111,10 @@ export interface Server { getInitProjectContext(): Promise; validateInitProjectTemplate(param: { template: InitTemplate }): Promise; initProject(param: { config: InitProjectConfig }): Promise; + compileProject(param: { + doc: TextDocumentIdentifier; + options: CompilerOptions; + }): Promise; } export interface ServerSourceFile extends SourceFile { diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 073c25bb10d..3846bfc9126 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -29,7 +29,7 @@ import { importFromOpenApi3 } from "./vscode-cmd/import-from-openapi3.js"; import { installCompilerGlobally } from "./vscode-cmd/install-tsp-compiler.js"; import { clearOpenApi3PreviewTempFolders, showOpenApi3 } from "./vscode-cmd/openapi3-preview.js"; -let client: TspLanguageClient | undefined; +export let client: TspLanguageClient | undefined; /** * Workaround: LogOutputChannel doesn't work well with LSP RemoteConsole, so having a customized LogOutputChannel to make them work together properly * More detail can be found at https://github.com/microsoft/vscode-discussions/discussions/1149 diff --git a/packages/typespec-vscode/src/tsp-language-client.ts b/packages/typespec-vscode/src/tsp-language-client.ts index 94b2c525149..071edd7a7c9 100644 --- a/packages/typespec-vscode/src/tsp-language-client.ts +++ b/packages/typespec-vscode/src/tsp-language-client.ts @@ -1,4 +1,6 @@ import type { + CompilerOptions, + CustomCompileResult, CustomRequestName, InitProjectConfig, InitProjectContext, @@ -7,7 +9,12 @@ import type { } from "@typespec/compiler"; import { inspect } from "util"; import { ExtensionContext, LogOutputChannel, RelativePattern, workspace } from "vscode"; -import { Executable, LanguageClient, LanguageClientOptions } from "vscode-languageclient/node.js"; +import { + Executable, + LanguageClient, + LanguageClientOptions, + TextDocumentIdentifier, +} from "vscode-languageclient/node.js"; import { TspConfigFileName } from "./const.js"; import logger from "./log/logger.js"; import telemetryClient from "./telemetry/telemetry-client.js"; @@ -88,14 +95,20 @@ export class TspLanguageClient { } } - public async compileProject(): Promise { + public async compileProject( + doc: TextDocumentIdentifier, + options?: CompilerOptions, + ): Promise { const compileProjectRequestName: CustomRequestName = "typespec/compileProject"; try { - const result = await this.client.sendRequest(compileProjectRequestName); - return result === true; + const result = await this.client.sendRequest(compileProjectRequestName, { + doc: doc, + options: { ...options, dryRun: false }, + }); + return result; } catch (e) { logger.error("Unexpected error when compiling project", [e]); - return false; + return undefined; } } diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index fc112a6cc5e..98d98149147 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -53,3 +53,8 @@ export async function TraverseMainTspFileInWorkspace() { .map((uri) => normalizeSlashes(uri.fsPath)), ); } + +export function GetVscodeUriFromPath(path: string) { + const uri = vscode.Uri.file(path); + return uri.toString(); +} diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index 1d29622ae7f..3fa223d6e28 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -6,6 +6,7 @@ import vscode, { QuickInputButton, Uri } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import { Document, isScalar, isSeq } from "yaml"; import { StartFileName, TspConfigFileName } from "../../const.js"; +import { client } from "../../extension.js"; import logger from "../../log/logger.js"; import { InstallAction, npmDependencyType, NpmUtil } from "../../npm-utils.js"; import { getDirectoryPath } from "../../path-utils.js"; @@ -13,7 +14,11 @@ import telemetryClient from "../../telemetry/telemetry-client.js"; import { OperationTelemetryEvent } from "../../telemetry/telemetry-event.js"; import { resolveTypeSpecCli } from "../../tsp-executable-resolver.js"; import { ResultCode } from "../../types.js"; -import { getEntrypointTspFile, TraverseMainTspFileInWorkspace } from "../../typespec-utils.js"; +import { + getEntrypointTspFile, + GetVscodeUriFromPath, + TraverseMainTspFileInWorkspace, +} from "../../typespec-utils.js"; import { ExecOutput, isFile, @@ -444,15 +449,16 @@ async function doEmit( emitterVersion: e.version ?? (await npmUtil.loadNpmPackage(e.package))?.version, }); }); - const compileResult = await compile( - cli, - mainTspFile, - emitters.map((e) => { - return { name: e.package, options: {} }; - }), - false, + + const compileResult = await client?.compileProject( + { + // uri: pathToFileURL(resolve(mainTspFile)).href, + uri: GetVscodeUriFromPath(mainTspFile).toString(), + }, + { emit: emitters.map((e) => e.package) }, ); - if (compileResult.exitCode !== 0) { + if (compileResult?.diagnostics) logger.info(compileResult?.diagnostics); + if (compileResult?.hasError) { logger.error(`Emitting ${codeInfoStr}...Failed`, [], { showOutput: true, showPopup: true, @@ -468,6 +474,30 @@ async function doEmit( }); return ResultCode.Success; } + // const compileResult = await compile( + // cli, + // mainTspFile, + // emitters.map((e) => { + // return { name: e.package, options: {} }; + // }), + // false, + // ); + // if (compileResult.exitCode !== 0) { + // logger.error(`Emitting ${codeInfoStr}...Failed`, [], { + // showOutput: true, + // showPopup: true, + // }); + // telemetryClient.logOperationDetailTelemetry(tel.activityId, { + // emitResult: `Emitting code failed: ${inspect(compileResult)}`, + // }); + // return ResultCode.Fail; + // } else { + // logger.info(`Emitting ${codeInfoStr}...Succeeded`, [], { + // showOutput: true, + // showPopup: true, + // }); + // return ResultCode.Success; + // } } catch (err: any) { if (typeof err === "object" && "stdout" in err && "stderr" in err && `error` in err) { const execOutput = err as ExecOutput; @@ -541,6 +571,7 @@ export async function emitCode( tspProjectFile = selectedProjectFile.path; } } else { + const doc = uri.toString(); const tspStartFile = await getEntrypointTspFile(uri.fsPath); if (!tspStartFile) { logger.info(`No entrypoint file (${StartFileName}). Invalid TypeSpec project.`, [], { From d3714ca1042f96527eb8c295192a9d35c99d7788 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 25 Apr 2025 12:58:47 +0800 Subject: [PATCH 03/23] remove unused comment --- packages/compiler/src/server/serverlib.ts | 102 ++++++++-------------- 1 file changed, 35 insertions(+), 67 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index fc825a9f31c..0b9fd5ca03e 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -54,7 +54,8 @@ import { getEntityName, getTypeName } from "../core/helpers/type-name-utils.js"; import { builtInLinterRule_UnusedTemplateParameter } from "../core/linter-rules/unused-template-parameter.rule.js"; import { builtInLinterRule_UnusedUsing } from "../core/linter-rules/unused-using.rule.js"; import { builtInLinterLibraryName } from "../core/linter.js"; -import { createConsoleSink, formatDiagnostic, formatLog } from "../core/logger/index.js"; +import { DynamicTask } from "../core/logger/dynamic-task.js"; +import { formatDiagnostic, 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"; @@ -81,9 +82,9 @@ import { PositionDetail, ProcessedLog, SourceFile, - SourceLocation, SyntaxKind, TextRange, + TrackActionTask, TypeReferenceNode, TypeSpecScriptNode, } from "../core/types.js"; @@ -370,52 +371,13 @@ export function createServer(host: ServerHost): Server { options: undefined, }; } else { - const isSourceLocation = (obj: any): obj is SourceLocation => { - return obj && "file" in obj; - }; let diagnostics: string | undefined = undefined; if (result.program.diagnostics.length > 0) { - // diagnostics = result.program.diagnostics - // .map((diagnostic) => - // formatLog( - // { - // level: diagnostic.severity, - // message: diagnostic.message, - // code: diagnostic.code, - // url: diagnostic.url, - // sourceLocation: getSourceLocation(diagnostic.target, { locateId: true }), - // related: getRelatedLocations(diagnostic), - // }, - // { pretty: false }, - // ), - // ) - // .join("\n"); diagnostics = result.program.diagnostics .map((diagnostic) => formatDiagnostic(diagnostic, { pretty: false })) .join("\n"); } - // if ((result?.program.diagnostics.length ?? 0) > 0) { - // diagnostics = JSON.stringify( - // result?.program.diagnostics.map((x) => { - // let target = undefined; - // if (x.target && x.target !== NoTarget && isSourceLocation(x.target)) { - // target = x.target; - // } - - // return { - // message: x.message, - // code: x.code, - // severity: x.severity, - // path: target?.file.path, - // text: target?.file.text, - // pos: target?.pos, - // end: target?.end, - // }; - // }), - // null, - // 2, - // ); - // } + return { hasError: result.program.hasError(), diagnostics: diagnostics ?? "", @@ -1163,38 +1125,44 @@ export function createServer(host: ServerHost): Server { } function createCompilerHost(): CompilerHost { - const logSink = createConsoleSink({ - pretty: true, - pathRelativeTo: process.cwd(), - trackAction: true, - }); - logSink.log = (log: ProcessedLog) => { - const msg = formatLog(log, { excludeLogLevel: true }); - const sLog: ServerLog = { - level: log.level, - message: msg, - }; - host.log(sLog); - }; const base = host.compilerHost; + async function trackAction( + message: string, + finalMessage: string, + asyncAction: (task: TrackActionTask) => Promise, + ): Promise { + const task = new DynamicTask(message, finalMessage, process.stderr); + task.start(); + + try { + const result = await asyncAction(task); + if (!task.isStopped) { + task.succeed(); + } + + return result; + } catch (error) { + task.fail(message); + throw error; + } + } return { ...base, parseCache: new WeakMap(), readFile, stat, getSourceFileKind, - logSink, - // logSink: { - // log: (log: ProcessedLog) => { - // const msg = formatLog(log, { excludeLogLevel: true }); - // const sLog: ServerLog = { - // level: log.level, - // message: msg, - // }; - // host.log(sLog); - // }, - // trackAction: (message, finalMessage, action) => trackAction(message, finalMessage, action), - // }, + logSink: { + log: (log: ProcessedLog) => { + const msg = formatLog(log, { excludeLogLevel: true }); + const sLog: ServerLog = { + level: log.level, + message: msg, + }; + host.log(sLog); + }, + trackAction: (message, finalMessage, action) => trackAction(message, finalMessage, action), + }, }; async function readFile(path: string): Promise { From c4b0d66c40e0c9050bb7564fcfbbb9f82e5bad1b Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 25 Apr 2025 14:31:57 +0800 Subject: [PATCH 04/23] define trackAction log --- packages/compiler/src/server/serverlib.ts | 69 ++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 0b9fd5ca03e..74879b80dbf 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -54,7 +54,6 @@ import { getEntityName, getTypeName } from "../core/helpers/type-name-utils.js"; import { builtInLinterRule_UnusedTemplateParameter } from "../core/linter-rules/unused-template-parameter.rule.js"; import { builtInLinterRule_UnusedUsing } from "../core/linter-rules/unused-using.rule.js"; import { builtInLinterLibraryName } from "../core/linter.js"; -import { DynamicTask } from "../core/logger/dynamic-task.js"; import { formatDiagnostic, formatLog } from "../core/logger/index.js"; import { CompilerOptions } from "../core/options.js"; import { getPositionBeforeTrivia } from "../core/parser-utils.js"; @@ -83,6 +82,7 @@ import { ProcessedLog, SourceFile, SyntaxKind, + TaskStatus, TextRange, TrackActionTask, TypeReferenceNode, @@ -1126,12 +1126,77 @@ export function createServer(host: ServerHost): Server { function createCompilerHost(): CompilerHost { const base = host.compilerHost; + const StatusIcons = { + success: "✔", + failure: "×", + warn: "⚠", + skipped: "•", + }; + class DynamicServerTask implements TrackActionTask { + #log: (log: ServerLog) => void; + #message: string; + #interval: NodeJS.Timeout | undefined; + #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; + if (this.#interval) { + clearInterval(this.#interval); + this.#interval = undefined; + } + this.#log({ + level: status !== "failure" ? "info" : "error", + message: `${StatusIcons[status]} ${this.#message}\n`, + }); + } + } async function trackAction( message: string, finalMessage: string, asyncAction: (task: TrackActionTask) => Promise, ): Promise { - const task = new DynamicTask(message, finalMessage, process.stderr); + const task = new DynamicServerTask(message, finalMessage, host.log); task.start(); try { From 32fc6555dda7a87600489055f6c446e336da9a9f Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 25 Apr 2025 16:43:45 +0800 Subject: [PATCH 05/23] only trackAction when run custom compile --- .../compiler/src/server/compile-service.ts | 37 +++++++- .../src/server/dynamic-server-task.ts | 68 ++++++++++++++ packages/compiler/src/server/serverlib.ts | 90 +------------------ packages/typespec-vscode/src/extension.ts | 5 ++ 4 files changed, 109 insertions(+), 91 deletions(-) create mode 100644 packages/compiler/src/server/dynamic-server-task.ts diff --git a/packages/compiler/src/server/compile-service.ts b/packages/compiler/src/server/compile-service.ts index 0e20bbdd5fc..05f5a6e434d 100644 --- a/packages/compiler/src/server/compile-service.ts +++ b/packages/compiler/src/server/compile-service.ts @@ -18,6 +18,7 @@ import { getDirectoryPath, joinPaths } from "../core/path-utils.js"; import { compile as compileProgram, Program } from "../core/program.js"; import type { CompilerHost, + TrackActionTask, Diagnostic as TypeSpecDiagnostic, TypeSpecScriptNode, } from "../core/types.js"; @@ -26,6 +27,7 @@ import { resolveTspMain } from "../utils/misc.js"; import { getLocationInYamlScript } from "../yaml/diagnostics.js"; import { parseYaml } from "../yaml/parser.js"; import { serverOptions } from "./constants.js"; +import { DynamicServerTask } from "./dynamic-server-task.js"; import { FileService } from "./file-service.js"; import { FileSystemCache } from "./file-system-cache.js"; import { CompileResult, ServerHost, ServerLog } from "./types.js"; @@ -48,6 +50,7 @@ export interface CompileService { document: TextDocument | TextDocumentIdentifier, additionalOptions?: CompilerOptions, bypassCache?: boolean, + trackAction?: boolean, ): Promise; /** @@ -106,7 +109,8 @@ export function createCompileService({ async function compile( document: TextDocument | TextDocumentIdentifier, additionalOptions?: CompilerOptions, - bypassCache = false, + bypassCache: boolean = false, + trackAction: boolean = false, ): Promise { const path = await fileService.getPath(document); const mainFile = await getMainFileForDocument(path); @@ -149,10 +153,39 @@ export function createCompileService({ return undefined; } + async function trackActionFunc( + message: string, + finalMessage: string, + asyncAction: (task: TrackActionTask) => Promise, + ): Promise { + const task = new DynamicServerTask(message, finalMessage, serverHost.log); + task.start(); + + try { + const result = await asyncAction(task); + if (!task.isStopped) { + task.succeed(); + } + + return result; + } catch (error) { + task.fail(message); + throw error; + } + } let program: Program; try { program = await compileProgram( - compilerHost, + trackAction + ? { + ...compilerHost, + logSink: { + log: compilerHost.logSink.log, + trackAction: (message, finalMessage, action) => + trackActionFunc(message, finalMessage, action), + }, + } + : compilerHost, mainFile, options, bypassCache ? undefined : oldPrograms.get(mainFile), diff --git a/packages/compiler/src/server/dynamic-server-task.ts b/packages/compiler/src/server/dynamic-server-task.ts new file mode 100644 index 00000000000..19304eb8b68 --- /dev/null +++ b/packages/compiler/src/server/dynamic-server-task.ts @@ -0,0 +1,68 @@ +import { TaskStatus, TrackActionTask } from "../core/types.js"; +import { ServerLog } from "./types.js"; + +const StatusIcons = { + success: "✔", + failure: "×", + warn: "⚠", + skipped: "•", +}; +export class DynamicServerTask implements TrackActionTask { + #log: (log: ServerLog) => void; + #message: string; + #interval: NodeJS.Timeout | undefined; + #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; + if (this.#interval) { + clearInterval(this.#interval); + this.#interval = undefined; + } + this.#log({ + level: status !== "failure" ? "info" : "error", + message: `${StatusIcons[status]} ${this.#message}\n`, + }); + } +} diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 74879b80dbf..148a98c612e 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -82,9 +82,7 @@ import { ProcessedLog, SourceFile, SyntaxKind, - TaskStatus, TextRange, - TrackActionTask, TypeReferenceNode, TypeSpecScriptNode, } from "../core/types.js"; @@ -361,7 +359,7 @@ export function createServer(host: ServerHost): Server { ...param.options, }; - const result = await compileService.compile(param.doc, option, true); + const result = await compileService.compile(param.doc, option, true, true); if (result === undefined) { return { hasError: true, @@ -1126,91 +1124,6 @@ export function createServer(host: ServerHost): Server { function createCompilerHost(): CompilerHost { const base = host.compilerHost; - const StatusIcons = { - success: "✔", - failure: "×", - warn: "⚠", - skipped: "•", - }; - class DynamicServerTask implements TrackActionTask { - #log: (log: ServerLog) => void; - #message: string; - #interval: NodeJS.Timeout | undefined; - #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; - if (this.#interval) { - clearInterval(this.#interval); - this.#interval = undefined; - } - this.#log({ - level: status !== "failure" ? "info" : "error", - message: `${StatusIcons[status]} ${this.#message}\n`, - }); - } - } - async function trackAction( - message: string, - finalMessage: string, - asyncAction: (task: TrackActionTask) => Promise, - ): Promise { - const task = new DynamicServerTask(message, finalMessage, host.log); - task.start(); - - try { - const result = await asyncAction(task); - if (!task.isStopped) { - task.succeed(); - } - - return result; - } catch (error) { - task.fail(message); - throw error; - } - } return { ...base, parseCache: new WeakMap(), @@ -1226,7 +1139,6 @@ export function createServer(host: ServerHost): Server { }; host.log(sLog); }, - trackAction: (message, finalMessage, action) => trackAction(message, finalMessage, action), }, }; diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 3846bfc9126..ba8aa8cfbaa 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -40,6 +40,11 @@ logger.registerLogListener("extension-log", new ExtensionLogListener(outputChann export async function activate(context: ExtensionContext) { const stateManager = new ExtensionStateManager(context); telemetryClient.Initialize(stateManager); + /** + * workaround: vscode output cannot display ANSI color. + * Set the NO_COLOR environment variable to suppress the addition of ANSI color escape codes. + */ + process.env["NO_COLOR"] = "true"; context.subscriptions.push(telemetryClient); context.subscriptions.push(createTaskProvider()); From ecb833972cf1f4606aded0182ab237e68c3fd6a5 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 27 Apr 2025 11:13:21 +0800 Subject: [PATCH 06/23] separate warning diagnostic and error diagnostic --- packages/compiler/src/server/serverlib.ts | 19 +++++++++++----- packages/compiler/src/server/types.ts | 7 +++--- .../src/vscode-cmd/emit-code/emit-code.ts | 22 ++++++++++++++++++- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 148a98c612e..498569d2a28 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -363,22 +363,29 @@ export function createServer(host: ServerHost): Server { if (result === undefined) { return { hasError: true, - diagnostics: + errorDiagnostics: [ "Failed to get compiler result, please check the compilation output for details", + ], entryPoint: undefined, options: undefined, }; } else { - let diagnostics: string | undefined = undefined; + let errorDiagnostics: string[] | undefined = undefined; + let warningDiagnostics: string[] | undefined = undefined; if (result.program.diagnostics.length > 0) { - diagnostics = result.program.diagnostics - .map((diagnostic) => formatDiagnostic(diagnostic, { pretty: false })) - .join("\n"); + errorDiagnostics = result.program.diagnostics + .filter((diag) => diag.severity === "error") + .map((diagnostic) => formatDiagnostic(diagnostic, { pretty: false })); + + warningDiagnostics = result.program.diagnostics + .filter((diag) => diag.severity === "warning") + .map((diagnostic) => formatDiagnostic(diagnostic, { pretty: false })); } return { hasError: result.program.hasError(), - diagnostics: diagnostics ?? "", + errorDiagnostics: errorDiagnostics, + warningDiagnostics: warningDiagnostics, entryPoint: result.document.uri, options: result.program.compilerOptions, }; diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index 18a03dd5ab2..172b1eecfa5 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -71,9 +71,10 @@ export interface CompileResult { export interface CustomCompileResult { readonly hasError: boolean; - readonly diagnostics: string | undefined; - readonly entryPoint: string | undefined; - readonly options: CompilerOptions | undefined; + readonly warningDiagnostics?: string[]; + readonly errorDiagnostics?: string[]; + readonly entryPoint?: string; + readonly options?: CompilerOptions; } export interface Server { diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index f5b66954db9..58d647a4206 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -460,7 +460,27 @@ async function doEmit( }, { emit: emitters.map((e) => e.package) }, ); - if (compileResult?.diagnostics) logger.info(compileResult?.diagnostics); + const addSuffix = (count: number, suffix: string) => + count > 1 ? `${count} ${suffix}s` : count === 1 ? `${count} ${suffix}` : undefined; + let count = 0; + if ( + compileResult?.warningDiagnostics && + (count = compileResult?.warningDiagnostics.length) > 0 + ) { + logger.warning(`Found ${addSuffix(count, "warning")}`); + for (const diag of compileResult?.warningDiagnostics) { + logger.warning(diag); + } + } + if ( + compileResult?.errorDiagnostics && + (count = compileResult?.errorDiagnostics.length) > 0 + ) { + logger.warning(`Found ${addSuffix(count, "error")}`); + for (const diag of compileResult?.errorDiagnostics) { + logger.error(diag); + } + } if (compileResult?.hasError) { logger.error(`Emitting ${codeInfoStr}...Failed`, [], { showOutput: true, From 5bfcf06d6341c8a17182e3668491f5d69ec60bfa Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Sun, 27 Apr 2025 16:40:01 +0800 Subject: [PATCH 07/23] check the compile result --- .../src/vscode-cmd/emit-code/emit-code.ts | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index 58d647a4206..1b559d882f2 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -453,35 +453,49 @@ async function doEmit( telemetryClient.logOperationDetailTelemetry(tel.activityId, { CompileStartTime: new Date().toISOString(), // ISO format: YYYY-MM-DDTHH:mm:ss.sssZ }); - const compileResult = await client?.compileProject( + if (!client) { + logger.error("LSP client is not started."); + logger.error(`Emitting ${codeInfoStr}...Failed.`, [], { + showOutput: true, + showPopup: true, + }); + return ResultCode.Fail; + } + const compileResult = await client.compileProject( { - // uri: pathToFileURL(resolve(mainTspFile)).href, uri: GetVscodeUriFromPath(mainTspFile).toString(), }, { emit: emitters.map((e) => e.package) }, ); + if (!compileResult) { + logger.error(`Emitting ${codeInfoStr}...Failed.`, [], { + showOutput: true, + showPopup: true, + }); + return ResultCode.Fail; + } const addSuffix = (count: number, suffix: string) => count > 1 ? `${count} ${suffix}s` : count === 1 ? `${count} ${suffix}` : undefined; let count = 0; if ( - compileResult?.warningDiagnostics && + compileResult.warningDiagnostics && (count = compileResult?.warningDiagnostics.length) > 0 ) { logger.warning(`Found ${addSuffix(count, "warning")}`); - for (const diag of compileResult?.warningDiagnostics) { + for (const diag of compileResult.warningDiagnostics) { logger.warning(diag); } } if ( - compileResult?.errorDiagnostics && + compileResult.errorDiagnostics && (count = compileResult?.errorDiagnostics.length) > 0 ) { logger.warning(`Found ${addSuffix(count, "error")}`); - for (const diag of compileResult?.errorDiagnostics) { + for (const diag of compileResult.errorDiagnostics) { logger.error(diag); } } - if (compileResult?.hasError) { + if (compileResult.hasError) { logger.error(`Emitting ${codeInfoStr}...Failed`, [], { showOutput: true, showPopup: true, @@ -497,30 +511,6 @@ async function doEmit( }); return ResultCode.Success; } - // const compileResult = await compile( - // cli, - // mainTspFile, - // emitters.map((e) => { - // return { name: e.package, options: {} }; - // }), - // false, - // ); - // if (compileResult.exitCode !== 0) { - // logger.error(`Emitting ${codeInfoStr}...Failed`, [], { - // showOutput: true, - // showPopup: true, - // }); - // telemetryClient.logOperationDetailTelemetry(tel.activityId, { - // emitResult: `Emitting code failed: ${inspect(compileResult)}`, - // }); - // return ResultCode.Fail; - // } else { - // logger.info(`Emitting ${codeInfoStr}...Succeeded`, [], { - // showOutput: true, - // showPopup: true, - // }); - // return ResultCode.Success; - // } } catch (err: any) { if (typeof err === "object" && "stdout" in err && "stderr" in err && `error` in err) { const execOutput = err as ExecOutput; From b7f5a387025c576eb4b3064006ef111af02f4617 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 29 Apr 2025 16:39:24 +0800 Subject: [PATCH 08/23] reuse statusIcon from core --- packages/compiler/src/core/logger/dynamic-task.ts | 2 +- packages/compiler/src/server/dynamic-server-task.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/core/logger/dynamic-task.ts b/packages/compiler/src/core/logger/dynamic-task.ts index e154f5d8343..9ee17b5fdbc 100644 --- a/packages/compiler/src/core/logger/dynamic-task.ts +++ b/packages/compiler/src/core/logger/dynamic-task.ts @@ -2,7 +2,7 @@ import isUnicodeSupported from "is-unicode-supported"; import pc from "picocolors"; import { TaskStatus, TrackActionTask } from "../types.js"; -const StatusIcons = { +export const StatusIcons = { success: pc.green("✔"), failure: pc.red("×"), warn: pc.yellow("⚠"), diff --git a/packages/compiler/src/server/dynamic-server-task.ts b/packages/compiler/src/server/dynamic-server-task.ts index 19304eb8b68..a52dc0d1a42 100644 --- a/packages/compiler/src/server/dynamic-server-task.ts +++ b/packages/compiler/src/server/dynamic-server-task.ts @@ -1,12 +1,7 @@ +import { StatusIcons } from "../core/logger/dynamic-task.js"; import { TaskStatus, TrackActionTask } from "../core/types.js"; import { ServerLog } from "./types.js"; -const StatusIcons = { - success: "✔", - failure: "×", - warn: "⚠", - skipped: "•", -}; export class DynamicServerTask implements TrackActionTask { #log: (log: ServerLog) => void; #message: string; From c5c3ec0587be860f8b2b2f93fd6003b0841b27dc Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 6 May 2025 10:05:44 +0800 Subject: [PATCH 09/23] add change log --- .chronus/changes/lsp-2025-4-6-10-5-13.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/lsp-2025-4-6-10-5-13.md diff --git a/.chronus/changes/lsp-2025-4-6-10-5-13.md b/.chronus/changes/lsp-2025-4-6-10-5-13.md new file mode 100644 index 00000000000..82e2e1ce99a --- /dev/null +++ b/.chronus/changes/lsp-2025-4-6-10-5-13.md @@ -0,0 +1,8 @@ +--- +changeKind: feature +packages: + - "@typespec/compiler" + - typespec-vscode +--- + +leverage lsp to emit code instead of cli \ No newline at end of file From 64f5290549a59ec38ef8d2fb39a82178067c70d4 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 6 May 2025 13:14:00 +0800 Subject: [PATCH 10/23] remove unused code --- packages/compiler/src/server/serverlib.ts | 1 - .../src/vscode-cmd/emit-code/emit-code.ts | 39 +------------------ 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 1d9896cec90..22c15a28d62 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -335,7 +335,6 @@ export function createServer(host: ServerHost): Server { } async function getInitProjectContext(): Promise { - log({ level: "info", message: "crystal->Get init project context" }); return { coreInitTemplates: await getTypeSpecCoreTemplates(host.compilerHost), }; diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index 1b559d882f2..c1eca3d9b23 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -3,7 +3,6 @@ import { readFile, writeFile } from "fs/promises"; import path from "path"; import { inspect } from "util"; import vscode, { QuickInputButton, Uri } from "vscode"; -import { Executable } from "vscode-languageclient/node.js"; import { Document, isScalar, isSeq } from "yaml"; import { StartFileName, TspConfigFileName } from "../../const.js"; import { client } from "../../extension.js"; @@ -19,13 +18,7 @@ import { GetVscodeUriFromPath, TraverseMainTspFileInWorkspace, } from "../../typespec-utils.js"; -import { - ExecOutput, - isFile, - spawnExecutionAndLogToOutput, - tryParseYaml, - tryReadFile, -} from "../../utils.js"; +import { ExecOutput, isFile, tryParseYaml, tryReadFile } from "../../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { Emitter, @@ -588,7 +581,6 @@ export async function emitCode( tspProjectFile = selectedProjectFile.path; } } else { - const doc = uri.toString(); const tspStartFile = await getEntrypointTspFile(uri.fsPath); if (!tspStartFile) { logger.info(`No entrypoint file (${StartFileName}). Invalid TypeSpec project.`, [], { @@ -802,32 +794,3 @@ export async function emitCode( } } } - -async function compile( - cli: Executable, - startFile: string, - emitters: { name: string; options: Record }[], - logPretty?: boolean, -): Promise { - const args: string[] = cli.args ?? []; - args.push("compile"); - args.push(startFile); - if (emitters) { - for (const emitter of emitters) { - args.push("--emit", emitter.name); - if (emitter.options) { - for (const [key, value] of Object.entries(emitter.options)) { - args.push("--option", `${emitter.name}.${key}=${value}`); - } - } - } - } - if (logPretty !== undefined) { - args.push("--pretty"); - args.push(logPretty ? "true" : "false"); - } - - return await spawnExecutionAndLogToOutput(cli.command, args, getDirectoryPath(startFile), { - NO_COLOR: "true", - }); -} From 5aa5580b6a63be62d9548de507daf962016e6d4d Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 7 May 2025 09:22:53 +0800 Subject: [PATCH 11/23] internal export CustomeCompileResult type --- packages/compiler/src/index.ts | 1 - packages/compiler/src/internals/index.ts | 1 + packages/typespec-vscode/src/tsp-language-client.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index b51e45fe139..b79466982e7 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -210,7 +210,6 @@ export { export { CompileResult, createServer, - CustomCompileResult, TypeSpecLanguageConfiguration, type CustomRequestName, type InitProjectConfig, diff --git a/packages/compiler/src/internals/index.ts b/packages/compiler/src/internals/index.ts index 5b27ff61229..e214a67d980 100644 --- a/packages/compiler/src/internals/index.ts +++ b/packages/compiler/src/internals/index.ts @@ -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 { CustomCompileResult } from "../server/index.js"; diff --git a/packages/typespec-vscode/src/tsp-language-client.ts b/packages/typespec-vscode/src/tsp-language-client.ts index 071edd7a7c9..68a337d5e5b 100644 --- a/packages/typespec-vscode/src/tsp-language-client.ts +++ b/packages/typespec-vscode/src/tsp-language-client.ts @@ -1,12 +1,12 @@ import type { CompilerOptions, - CustomCompileResult, CustomRequestName, InitProjectConfig, InitProjectContext, InitProjectTemplate, ServerInitializeResult, } from "@typespec/compiler"; +import { CustomCompileResult } from "@typespec/compiler/internals"; import { inspect } from "util"; import { ExtensionContext, LogOutputChannel, RelativePattern, workspace } from "vscode"; import { From 9c10f238e0da66282979e69843c819d6efd9ebfd Mon Sep 17 00:00:00 2001 From: Crystal YU Date: Thu, 8 May 2025 09:10:42 +0800 Subject: [PATCH 12/23] Update packages/compiler/src/server/types.ts Co-authored-by: Timothee Guerin --- packages/compiler/src/server/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index c525fa1016f..3ba96521614 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -74,7 +74,7 @@ export interface CustomCompileResult { readonly hasError: boolean; readonly warningDiagnostics?: string[]; readonly errorDiagnostics?: string[]; - readonly entryPoint?: string; + readonly entrypoint?: string; readonly options?: CompilerOptions; } From 2842198247000bc09aae503b975ff8f14155826a Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Thu, 8 May 2025 09:32:09 +0800 Subject: [PATCH 13/23] resolve comments --- packages/compiler/src/server/serverlib.ts | 4 ++-- packages/typespec-vscode/src/typespec-utils.ts | 2 +- .../typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 22c15a28d62..addaef232b2 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -385,7 +385,7 @@ export function createServer(host: ServerHost): Server { errorDiagnostics: [ "Failed to get compiler result, please check the compilation output for details", ], - entryPoint: undefined, + entrypoint: undefined, options: undefined, }; } else { @@ -405,7 +405,7 @@ export function createServer(host: ServerHost): Server { hasError: result.program.hasError(), errorDiagnostics: errorDiagnostics, warningDiagnostics: warningDiagnostics, - entryPoint: result.document?.uri, + entrypoint: result.document?.uri, options: result.program.compilerOptions, }; } diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index 98d98149147..e905be29f93 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -54,7 +54,7 @@ export async function TraverseMainTspFileInWorkspace() { ); } -export function GetVscodeUriFromPath(path: string) { +export function getVscodeUriFromPath(path: string) { const uri = vscode.Uri.file(path); return uri.toString(); } diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index c1eca3d9b23..ab0931fa652 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -15,7 +15,7 @@ import { resolveTypeSpecCli } from "../../tsp-executable-resolver.js"; import { ResultCode } from "../../types.js"; import { getEntrypointTspFile, - GetVscodeUriFromPath, + getVscodeUriFromPath, TraverseMainTspFileInWorkspace, } from "../../typespec-utils.js"; import { ExecOutput, isFile, tryParseYaml, tryReadFile } from "../../utils.js"; @@ -456,7 +456,7 @@ async function doEmit( } const compileResult = await client.compileProject( { - uri: GetVscodeUriFromPath(mainTspFile).toString(), + uri: getVscodeUriFromPath(mainTspFile).toString(), }, { emit: emitters.map((e) => e.package) }, ); From 5794426db4c253a216ea9be471424b251a50b29b Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 9 May 2025 08:58:41 +0800 Subject: [PATCH 14/23] resolve playground break issue --- .../compiler/src/core/logger/dynamic-task.ts | 2 +- .../compiler/src/server/compile-service.ts | 30 +++----------- ...er-task.ts => server-track-action-task.ts} | 40 +++++++++++++++---- 3 files changed, 39 insertions(+), 33 deletions(-) rename packages/compiler/src/server/{dynamic-server-task.ts => server-track-action-task.ts} (61%) diff --git a/packages/compiler/src/core/logger/dynamic-task.ts b/packages/compiler/src/core/logger/dynamic-task.ts index 9ee17b5fdbc..e154f5d8343 100644 --- a/packages/compiler/src/core/logger/dynamic-task.ts +++ b/packages/compiler/src/core/logger/dynamic-task.ts @@ -2,7 +2,7 @@ import isUnicodeSupported from "is-unicode-supported"; import pc from "picocolors"; import { TaskStatus, TrackActionTask } from "../types.js"; -export const StatusIcons = { +const StatusIcons = { success: pc.green("✔"), failure: pc.red("×"), warn: pc.yellow("⚠"), diff --git a/packages/compiler/src/server/compile-service.ts b/packages/compiler/src/server/compile-service.ts index a4b261388ff..8c7e89cdbed 100644 --- a/packages/compiler/src/server/compile-service.ts +++ b/packages/compiler/src/server/compile-service.ts @@ -18,7 +18,6 @@ import { getDirectoryPath, joinPaths } from "../core/path-utils.js"; import { compile as compileProgram, Program } from "../core/program.js"; import type { CompilerHost, - TrackActionTask, Diagnostic as TypeSpecDiagnostic, TypeSpecScriptNode, } from "../core/types.js"; @@ -27,9 +26,9 @@ import { resolveTspMain } from "../utils/misc.js"; import { getLocationInYamlScript } from "../yaml/diagnostics.js"; import { parseYaml } from "../yaml/parser.js"; import { serverOptions } from "./constants.js"; -import { DynamicServerTask } from "./dynamic-server-task.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"; @@ -134,8 +133,10 @@ export function createCompileService({ const options: CompilerOptions = { ...optionsFromConfig, ...serverOptions, - ...additionalOptions, }; + if (additionalOptions) { + Object.assign(options, additionalOptions); + } // add linter rule for unused using if user didn't configure it explicitly const unusedUsingRule = `${builtInLinterLibraryName}/${builtInLinterRule_UnusedUsing}`; if ( @@ -164,26 +165,6 @@ export function createCompileService({ return undefined; } - async function trackActionFunc( - message: string, - finalMessage: string, - asyncAction: (task: TrackActionTask) => Promise, - ): Promise { - const task = new DynamicServerTask(message, finalMessage, serverHost.log); - task.start(); - - try { - const result = await asyncAction(task); - if (!task.isStopped) { - task.succeed(); - } - - return result; - } catch (error) { - task.fail(message); - throw error; - } - } let program: Program; try { program = await compileProgram( @@ -192,8 +173,9 @@ export function createCompileService({ ...compilerHost, logSink: { log: compilerHost.logSink.log, + getPath: compilerHost.logSink.getPath, trackAction: (message, finalMessage, action) => - trackActionFunc(message, finalMessage, action), + trackActionFunc(serverHost.log, message, finalMessage, action), }, } : compilerHost, diff --git a/packages/compiler/src/server/dynamic-server-task.ts b/packages/compiler/src/server/server-track-action-task.ts similarity index 61% rename from packages/compiler/src/server/dynamic-server-task.ts rename to packages/compiler/src/server/server-track-action-task.ts index a52dc0d1a42..0eedab5b4b3 100644 --- a/packages/compiler/src/server/dynamic-server-task.ts +++ b/packages/compiler/src/server/server-track-action-task.ts @@ -1,11 +1,9 @@ -import { StatusIcons } from "../core/logger/dynamic-task.js"; import { TaskStatus, TrackActionTask } from "../core/types.js"; import { ServerLog } from "./types.js"; -export class DynamicServerTask implements TrackActionTask { +export class ServerTrackActionTask implements TrackActionTask { #log: (log: ServerLog) => void; #message: string; - #interval: NodeJS.Timeout | undefined; #running: boolean; #finalMessage: string; @@ -51,13 +49,39 @@ export class DynamicServerTask implements TrackActionTask { stop(status: TaskStatus, message?: string) { this.#running = false; this.#message = message ?? this.#finalMessage; - if (this.#interval) { - clearInterval(this.#interval); - this.#interval = undefined; - } this.#log({ level: status !== "failure" ? "info" : "error", - message: `${StatusIcons[status]} ${this.#message}\n`, + message: `[${TaskStatusText[status]}] ${this.#message}\n`, }); } } + +const TaskStatusText = { + success: "succeed", + failure: "failed", + warn: "succeeded with warnings", + skipped: "skipped", +}; + +/** internal */ +export async function trackActionFunc( + log: (log: ServerLog) => void, + message: string, + finalMessage: string, + asyncAction: (task: TrackActionTask) => Promise, +): Promise { + 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; + } +} From 2e92ad2852aa1bf5e83d794f1a2a9a2ddfa9b8a2 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 9 May 2025 09:41:13 +0800 Subject: [PATCH 15/23] split change log for each package --- .chronus/changes/lsp-2025-4-9-9-38-32.md | 7 +++++++ .../{lsp-2025-4-6-10-5-13.md => lsp-2025-4-9-9-38-5.md} | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .chronus/changes/lsp-2025-4-9-9-38-32.md rename .chronus/changes/{lsp-2025-4-6-10-5-13.md => lsp-2025-4-9-9-38-5.md} (51%) diff --git a/.chronus/changes/lsp-2025-4-9-9-38-32.md b/.chronus/changes/lsp-2025-4-9-9-38-32.md new file mode 100644 index 00000000000..9f497512784 --- /dev/null +++ b/.chronus/changes/lsp-2025-4-9-9-38-32.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - typespec-vscode +--- + +Use language server to compile project instead of CLI \ No newline at end of file diff --git a/.chronus/changes/lsp-2025-4-6-10-5-13.md b/.chronus/changes/lsp-2025-4-9-9-38-5.md similarity index 51% rename from .chronus/changes/lsp-2025-4-6-10-5-13.md rename to .chronus/changes/lsp-2025-4-9-9-38-5.md index 82e2e1ce99a..a10ec3ed269 100644 --- a/.chronus/changes/lsp-2025-4-6-10-5-13.md +++ b/.chronus/changes/lsp-2025-4-9-9-38-5.md @@ -2,7 +2,6 @@ changeKind: feature packages: - "@typespec/compiler" - - typespec-vscode --- -leverage lsp to emit code instead of cli \ No newline at end of file +[LSP] Expose new compile project command \ No newline at end of file From 2a49fd5c4eb773dec9b056a1391224a73155d331 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 9 May 2025 10:02:42 +0800 Subject: [PATCH 16/23] resolve comment --- packages/compiler/src/server/server.ts | 4 ++-- packages/compiler/src/server/serverlib.ts | 4 ++-- packages/compiler/src/server/types.ts | 4 ++-- packages/typespec-vscode/src/tsp-language-client.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/server/server.ts b/packages/compiler/src/server/server.ts index e9755860cb6..edd082164bb 100644 --- a/packages/compiler/src/server/server.ts +++ b/packages/compiler/src/server/server.ts @@ -136,8 +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/compileProject"; - connection.onRequest(compileProjectRequestName, profile(s.compileProject)); + const compileProjectRequestName: CustomRequestName = "typespec/internalCompile"; + connection.onRequest(compileProjectRequestName, profile(s.internalCompile)); documents.onDidChangeContent(profile(s.checkChange)); documents.onDidClose(profile(s.documentClosed)); diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index addaef232b2..276bded0d73 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -201,7 +201,7 @@ export function createServer(host: ServerHost): Server { getInitProjectContext, validateInitProjectTemplate, initProject, - compileProject, + internalCompile, }; async function initialize(params: InitializeParams): Promise { @@ -370,7 +370,7 @@ export function createServer(host: ServerHost): Server { } } - async function compileProject(param: { + async function internalCompile(param: { doc: TextDocumentIdentifier; options: CompilerOptions; }): Promise { diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index 3ba96521614..71b7790a92d 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -114,7 +114,7 @@ export interface Server { getInitProjectContext(): Promise; validateInitProjectTemplate(param: { template: InitTemplate }): Promise; initProject(param: { config: InitProjectConfig }): Promise; - compileProject(param: { + internalCompile(param: { doc: TextDocumentIdentifier; options: CompilerOptions; }): Promise; @@ -169,7 +169,7 @@ export type CustomRequestName = | "typespec/getInitProjectContext" | "typespec/initProject" | "typespec/validateInitProjectTemplate" - | "typespec/compileProject"; + | "typespec/internalCompile"; export interface ServerCustomCapacities { getInitProjectContext?: boolean; validateInitProjectTemplate?: boolean; diff --git a/packages/typespec-vscode/src/tsp-language-client.ts b/packages/typespec-vscode/src/tsp-language-client.ts index 68a337d5e5b..0b21ccb685b 100644 --- a/packages/typespec-vscode/src/tsp-language-client.ts +++ b/packages/typespec-vscode/src/tsp-language-client.ts @@ -99,7 +99,7 @@ export class TspLanguageClient { doc: TextDocumentIdentifier, options?: CompilerOptions, ): Promise { - const compileProjectRequestName: CustomRequestName = "typespec/compileProject"; + const compileProjectRequestName: CustomRequestName = "typespec/internalCompile"; try { const result = await this.client.sendRequest(compileProjectRequestName, { doc: doc, From 1ed8d135637789f7c26ca74b78b214168bd6c5d3 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 9 May 2025 16:28:16 +0800 Subject: [PATCH 17/23] return diagnostic object instead of log string --- packages/compiler/src/server/serverlib.ts | 37 ++++++++++--------- packages/compiler/src/server/types.ts | 5 +-- .../typespec-vscode/src/typespec-utils.ts | 17 +++++++++ .../src/vscode-cmd/emit-code/emit-code.ts | 28 +++++++------- 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 276bded0d73..7621b8999d9 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -56,7 +56,7 @@ import { getEntityName, getTypeName } from "../core/helpers/type-name-utils.js"; import { builtInLinterRule_UnusedTemplateParameter } from "../core/linter-rules/unused-template-parameter.rule.js"; import { builtInLinterRule_UnusedUsing } from "../core/linter-rules/unused-using.rule.js"; import { builtInLinterLibraryName } from "../core/linter.js"; -import { formatDiagnostic, formatLog } from "../core/logger/index.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"; @@ -382,29 +382,32 @@ export function createServer(host: ServerHost): Server { if (result === undefined) { return { hasError: true, - errorDiagnostics: [ - "Failed to get compiler result, please check the compilation output for details", + 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 { - let errorDiagnostics: string[] | undefined = undefined; - let warningDiagnostics: string[] | undefined = undefined; - if (result.program.diagnostics.length > 0) { - errorDiagnostics = result.program.diagnostics - .filter((diag) => diag.severity === "error") - .map((diagnostic) => formatDiagnostic(diagnostic, { pretty: false })); - - warningDiagnostics = result.program.diagnostics - .filter((diag) => diag.severity === "warning") - .map((diagnostic) => formatDiagnostic(diagnostic, { pretty: false })); - } - return { hasError: result.program.hasError(), - errorDiagnostics: errorDiagnostics, - warningDiagnostics: warningDiagnostics, + diagnostics: result.program.diagnostics.map( + (diagnostic) => + ({ + code: diagnostic.code, + message: diagnostic.message, + severity: diagnostic.severity, + target: getSourceLocation(diagnostic.target, { locateId: true }), + url: diagnostic.url, + }) as Diagnostic, + ), entrypoint: result.document?.uri, options: result.program.compilerOptions, }; diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index 71b7790a92d..3f0c859e7f2 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -40,7 +40,7 @@ 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, SourceFile, 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"; @@ -72,8 +72,7 @@ export interface CompileResult { export interface CustomCompileResult { readonly hasError: boolean; - readonly warningDiagnostics?: string[]; - readonly errorDiagnostics?: string[]; + readonly diagnostics: Diagnostic[]; readonly entrypoint?: string; readonly options?: CompilerOptions; } diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index e905be29f93..14b623f969d 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,3 +1,4 @@ +import { Diagnostic, SourceLocation } from "@typespec/compiler"; import { readFile } from "fs/promises"; import path from "path"; import vscode from "vscode"; @@ -58,3 +59,19 @@ export function getVscodeUriFromPath(path: string) { const uri = vscode.Uri.file(path); return uri.toString(); } + +export function formatDiagnostic(diagnostic: Diagnostic): string { + const code = diagnostic.code ? ` ${diagnostic.code}` : ""; + const content = code ? `${code}: ${diagnostic.message}` : diagnostic.message; + const root = diagnostic.target as SourceLocation; + if (root && root.file) { + const path = root.file.path; + const pos = root.pos ?? 0; + const formattedLocation = `${path}:${pos}`; + const message = [`${formattedLocation} - ${content}`]; + + return message.join("\n"); + } else { + return content; + } +} diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index ab0931fa652..3363a4b57b1 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -14,6 +14,7 @@ import { OperationTelemetryEvent } from "../../telemetry/telemetry-event.js"; import { resolveTypeSpecCli } from "../../tsp-executable-resolver.js"; import { ResultCode } from "../../types.js"; import { + formatDiagnostic, getEntrypointTspFile, getVscodeUriFromPath, TraverseMainTspFileInWorkspace, @@ -469,23 +470,20 @@ async function doEmit( } const addSuffix = (count: number, suffix: string) => count > 1 ? `${count} ${suffix}s` : count === 1 ? `${count} ${suffix}` : undefined; - let count = 0; - if ( - compileResult.warningDiagnostics && - (count = compileResult?.warningDiagnostics.length) > 0 - ) { - logger.warning(`Found ${addSuffix(count, "warning")}`); - for (const diag of compileResult.warningDiagnostics) { - logger.warning(diag); + const warningDiagnostics = compileResult.diagnostics.filter( + (d) => d.severity === "warning", + ); + if (warningDiagnostics.length > 0) { + logger.warning(`Found ${addSuffix(warningDiagnostics.length, "warning")}`); + for (const diag of warningDiagnostics) { + logger.warning(formatDiagnostic(diag)); } } - if ( - compileResult.errorDiagnostics && - (count = compileResult?.errorDiagnostics.length) > 0 - ) { - logger.warning(`Found ${addSuffix(count, "error")}`); - for (const diag of compileResult.errorDiagnostics) { - logger.error(diag); + const errorDiagnostics = compileResult.diagnostics.filter((d) => d.severity === "error"); + if (errorDiagnostics.length > 0) { + logger.error(`Found ${addSuffix(errorDiagnostics.length, "error")}`); + for (const diag of errorDiagnostics) { + logger.error(formatDiagnostic(diag)); } } if (compileResult.hasError) { From a931e6b48a3e5e7be1a533a04f1b15e1525c6ecf Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Fri, 9 May 2025 17:16:30 +0800 Subject: [PATCH 18/23] return target position --- packages/compiler/src/internals/index.ts | 2 +- packages/compiler/src/server/index.ts | 1 + packages/compiler/src/server/serverlib.ts | 29 ++++++++++++------- packages/compiler/src/server/types.ts | 15 ++++++++-- .../typespec-vscode/src/typespec-utils.ts | 16 ++++++---- 5 files changed, 44 insertions(+), 19 deletions(-) diff --git a/packages/compiler/src/internals/index.ts b/packages/compiler/src/internals/index.ts index e214a67d980..598fdb8736e 100644 --- a/packages/compiler/src/internals/index.ts +++ b/packages/compiler/src/internals/index.ts @@ -10,4 +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 { CustomCompileResult } from "../server/index.js"; +export { CustomCompileResult, ServerDiagnostic } from "../server/index.js"; diff --git a/packages/compiler/src/server/index.ts b/packages/compiler/src/server/index.ts index a3c67fc5c68..e726799d3c0 100644 --- a/packages/compiler/src/server/index.ts +++ b/packages/compiler/src/server/index.ts @@ -13,6 +13,7 @@ export type { SemanticTokenKind, Server, ServerCustomCapacities, + ServerDiagnostic, ServerHost, ServerInitializeResult, ServerLog, diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 7621b8999d9..33944b5f662 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -122,6 +122,7 @@ import { SemanticTokenKind, Server, ServerCustomCapacities, + ServerDiagnostic, ServerHost, ServerInitializeResult, ServerLog, @@ -398,16 +399,24 @@ export function createServer(host: ServerHost): Server { } else { return { hasError: result.program.hasError(), - diagnostics: result.program.diagnostics.map( - (diagnostic) => - ({ - code: diagnostic.code, - message: diagnostic.message, - severity: diagnostic.severity, - target: getSourceLocation(diagnostic.target, { locateId: true }), - url: diagnostic.url, - }) as Diagnostic, - ), + 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, }; diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index 3f0c859e7f2..f35604f57d2 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -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, Diagnostic, 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"; @@ -70,9 +77,13 @@ export interface CompileResult { readonly optionsFromConfig: CompilerOptions; } +export interface ServerDiagnostic extends Diagnostic { + target: (SourceLocation & { position?: { line: number; column: number } }) | typeof NoTarget; +} + export interface CustomCompileResult { readonly hasError: boolean; - readonly diagnostics: Diagnostic[]; + readonly diagnostics: ServerDiagnostic[]; readonly entrypoint?: string; readonly options?: CompilerOptions; } diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index 14b623f969d..97224cdadb3 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -1,4 +1,5 @@ -import { Diagnostic, SourceLocation } from "@typespec/compiler"; +import { SourceLocation } from "@typespec/compiler"; +import { ServerDiagnostic } from "@typespec/compiler/internals"; import { readFile } from "fs/promises"; import path from "path"; import vscode from "vscode"; @@ -60,17 +61,20 @@ export function getVscodeUriFromPath(path: string) { return uri.toString(); } -export function formatDiagnostic(diagnostic: Diagnostic): string { +export function formatDiagnostic(diagnostic: ServerDiagnostic): string { const code = diagnostic.code ? ` ${diagnostic.code}` : ""; const content = code ? `${code}: ${diagnostic.message}` : diagnostic.message; - const root = diagnostic.target as SourceLocation; + const root = diagnostic.target as SourceLocation & { + position?: { line: number; column: number }; + }; if (root && root.file) { const path = root.file.path; const pos = root.pos ?? 0; - const formattedLocation = `${path}:${pos}`; - const message = [`${formattedLocation} - ${content}`]; + const formattedLocation = root.position + ? `${path}:${root.position.line}:${root.position.column}` + : `${path}:${pos}`; - return message.join("\n"); + return `${formattedLocation} - ${content}`; } else { return content; } From 6334848a9dd028637d198f8e7f257c1d2b7ed6b0 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Tue, 13 May 2025 10:43:55 +0800 Subject: [PATCH 19/23] resolve comment --- packages/compiler/src/server/compile-service.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/server/compile-service.ts b/packages/compiler/src/server/compile-service.ts index 8c7e89cdbed..076dc1a0eb5 100644 --- a/packages/compiler/src/server/compile-service.ts +++ b/packages/compiler/src/server/compile-service.ts @@ -133,10 +133,9 @@ export function createCompileService({ const options: CompilerOptions = { ...optionsFromConfig, ...serverOptions, + ...(additionalOptions ?? {}), }; - if (additionalOptions) { - Object.assign(options, additionalOptions); - } + // add linter rule for unused using if user didn't configure it explicitly const unusedUsingRule = `${builtInLinterLibraryName}/${builtInLinterRule_UnusedUsing}`; if ( From 1c52f40aacd9e606f7c7c76fc26e25e816c6cfab Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 14 May 2025 13:19:00 +0800 Subject: [PATCH 20/23] resolve comment --- packages/compiler/src/internals/index.ts | 2 +- packages/compiler/src/server/index.ts | 2 +- packages/compiler/src/server/serverlib.ts | 4 ++-- packages/compiler/src/server/types.ts | 4 ++-- .../typespec-vscode/src/tsp-language-client.ts | 15 +++++++++------ packages/typespec-vscode/src/typespec-utils.ts | 5 ----- packages/typespec-vscode/src/utils.ts | 7 ++++++- .../src/vscode-cmd/emit-code/emit-code.ts | 11 ++++++++--- 8 files changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/compiler/src/internals/index.ts b/packages/compiler/src/internals/index.ts index 598fdb8736e..a2926d5ac9e 100644 --- a/packages/compiler/src/internals/index.ts +++ b/packages/compiler/src/internals/index.ts @@ -10,4 +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 { CustomCompileResult, ServerDiagnostic } from "../server/index.js"; +export { InternalCompileResult, ServerDiagnostic } from "../server/index.js"; diff --git a/packages/compiler/src/server/index.ts b/packages/compiler/src/server/index.ts index e726799d3c0..3f21efa5333 100644 --- a/packages/compiler/src/server/index.ts +++ b/packages/compiler/src/server/index.ts @@ -2,13 +2,13 @@ export { TypeSpecLanguageConfiguration } from "./language-config.js"; export { createServer } from "./serverlib.js"; export type { CompileResult, - CustomCompileResult, CustomRequestName, InitProjectConfig, InitProjectContext, InitProjectTemplate, InitProjectTemplateEmitterTemplate, InitProjectTemplateLibrarySpec, + InternalCompileResult, SemanticToken, SemanticTokenKind, Server, diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 33944b5f662..eac763d22f1 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -116,9 +116,9 @@ import { } from "./type-details.js"; import { CompileResult, - CustomCompileResult, InitProjectConfig, InitProjectContext, + InternalCompileResult, SemanticTokenKind, Server, ServerCustomCapacities, @@ -374,7 +374,7 @@ export function createServer(host: ServerHost): Server { async function internalCompile(param: { doc: TextDocumentIdentifier; options: CompilerOptions; - }): Promise { + }): Promise { const option: CompilerOptions = { ...param.options, }; diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index f35604f57d2..275e4f0c32e 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -81,7 +81,7 @@ export interface ServerDiagnostic extends Diagnostic { target: (SourceLocation & { position?: { line: number; column: number } }) | typeof NoTarget; } -export interface CustomCompileResult { +export interface InternalCompileResult { readonly hasError: boolean; readonly diagnostics: ServerDiagnostic[]; readonly entrypoint?: string; @@ -127,7 +127,7 @@ export interface Server { internalCompile(param: { doc: TextDocumentIdentifier; options: CompilerOptions; - }): Promise; + }): Promise; } export interface ServerSourceFile extends SourceFile { diff --git a/packages/typespec-vscode/src/tsp-language-client.ts b/packages/typespec-vscode/src/tsp-language-client.ts index 0b21ccb685b..146f91bbbf7 100644 --- a/packages/typespec-vscode/src/tsp-language-client.ts +++ b/packages/typespec-vscode/src/tsp-language-client.ts @@ -6,7 +6,7 @@ import type { InitProjectTemplate, ServerInitializeResult, } from "@typespec/compiler"; -import { CustomCompileResult } from "@typespec/compiler/internals"; +import { InternalCompileResult } from "@typespec/compiler/internals"; import { inspect } from "util"; import { ExtensionContext, LogOutputChannel, RelativePattern, workspace } from "vscode"; import { @@ -98,13 +98,16 @@ export class TspLanguageClient { public async compileProject( doc: TextDocumentIdentifier, options?: CompilerOptions, - ): Promise { + ): Promise { const compileProjectRequestName: CustomRequestName = "typespec/internalCompile"; try { - const result = await this.client.sendRequest(compileProjectRequestName, { - doc: doc, - options: { ...options, dryRun: false }, - }); + const result = await this.client.sendRequest( + compileProjectRequestName, + { + doc: doc, + options: { ...options, dryRun: false }, + }, + ); return result; } catch (e) { logger.error("Unexpected error when compiling project", [e]); diff --git a/packages/typespec-vscode/src/typespec-utils.ts b/packages/typespec-vscode/src/typespec-utils.ts index 97224cdadb3..94ae71c1f1a 100644 --- a/packages/typespec-vscode/src/typespec-utils.ts +++ b/packages/typespec-vscode/src/typespec-utils.ts @@ -56,11 +56,6 @@ export async function TraverseMainTspFileInWorkspace() { ); } -export function getVscodeUriFromPath(path: string) { - const uri = vscode.Uri.file(path); - return uri.toString(); -} - export function formatDiagnostic(diagnostic: ServerDiagnostic): string { const code = diagnostic.code ? ` ${diagnostic.code}` : ""; const content = code ? `${code}: ${diagnostic.message}` : diagnostic.message; diff --git a/packages/typespec-vscode/src/utils.ts b/packages/typespec-vscode/src/utils.ts index 0312ab0be5c..a91898646ff 100644 --- a/packages/typespec-vscode/src/utils.ts +++ b/packages/typespec-vscode/src/utils.ts @@ -2,7 +2,7 @@ import type { ModuleResolutionResult, PackageJson, ResolveModuleHost } from "@ty import { spawn, SpawnOptions } from "child_process"; import { mkdtemp, readdir, readFile, realpath, stat } from "fs/promises"; import { dirname } from "path"; -import { CancellationToken } from "vscode"; +import vscode, { CancellationToken } from "vscode"; import { Executable } from "vscode-languageclient/node.js"; import which from "which"; import { parseDocument } from "yaml"; @@ -474,3 +474,8 @@ export function throttle any>(fn: T, blockInMs: nu } } as T; } + +export function getVscodeUriFromPath(path: string): string { + const uri = vscode.Uri.file(path); + return uri.toString(); +} diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index 3363a4b57b1..fd9e2b8020f 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -16,10 +16,15 @@ import { ResultCode } from "../../types.js"; import { formatDiagnostic, getEntrypointTspFile, - getVscodeUriFromPath, TraverseMainTspFileInWorkspace, } from "../../typespec-utils.js"; -import { ExecOutput, isFile, tryParseYaml, tryReadFile } from "../../utils.js"; +import { + ExecOutput, + getVscodeUriFromPath, + isFile, + tryParseYaml, + tryReadFile, +} from "../../utils.js"; import { EmitQuickPickItem } from "./emit-quick-pick-item.js"; import { Emitter, @@ -457,7 +462,7 @@ async function doEmit( } const compileResult = await client.compileProject( { - uri: getVscodeUriFromPath(mainTspFile).toString(), + uri: getVscodeUriFromPath(mainTspFile), }, { emit: emitters.map((e) => e.package) }, ); From a469699a47a04a389a3e00a703c1ad2d8c81745f Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 14 May 2025 13:26:48 +0800 Subject: [PATCH 21/23] add internalCompile capability --- packages/compiler/src/server/serverlib.ts | 1 + packages/compiler/src/server/types.ts | 1 + packages/typespec-vscode/src/tsp-language-client.ts | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index eac763d22f1..85553804b7e 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -305,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)); diff --git a/packages/compiler/src/server/types.ts b/packages/compiler/src/server/types.ts index 275e4f0c32e..15f32e2575e 100644 --- a/packages/compiler/src/server/types.ts +++ b/packages/compiler/src/server/types.ts @@ -184,6 +184,7 @@ export interface ServerCustomCapacities { getInitProjectContext?: boolean; validateInitProjectTemplate?: boolean; initProject?: boolean; + internalCompile?: boolean; } export interface ServerInitializeResult extends InitializeResult { diff --git a/packages/typespec-vscode/src/tsp-language-client.ts b/packages/typespec-vscode/src/tsp-language-client.ts index 146f91bbbf7..0342f67622b 100644 --- a/packages/typespec-vscode/src/tsp-language-client.ts +++ b/packages/typespec-vscode/src/tsp-language-client.ts @@ -101,6 +101,10 @@ export class TspLanguageClient { ): Promise { const compileProjectRequestName: CustomRequestName = "typespec/internalCompile"; try { + if (this.initializeResult?.customCapacities?.internalCompile !== true) { + logger.warning("Compile project is not supported by the current TypeSpec Compiler's LSP."); + return undefined; + } const result = await this.client.sendRequest( compileProjectRequestName, { From 6393be9e7412a55b42008fb0b8ec6f460c3c29e4 Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 14 May 2025 14:15:45 +0800 Subject: [PATCH 22/23] define option bag for optional parameters --- packages/compiler/src/server/compile-service.ts | 16 ++++++++++------ packages/compiler/src/server/serverlib.ts | 5 ++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/server/compile-service.ts b/packages/compiler/src/server/compile-service.ts index 076dc1a0eb5..380f40338d9 100644 --- a/packages/compiler/src/server/compile-service.ts +++ b/packages/compiler/src/server/compile-service.ts @@ -48,8 +48,10 @@ export interface CompileService { compile( document: TextDocument | TextDocumentIdentifier, additionalOptions?: CompilerOptions, - bypassCache?: boolean, - trackAction?: boolean, + compileOptions?: { + bypassCache?: boolean; + trackAction?: boolean; + }, ): Promise; /** @@ -119,8 +121,10 @@ export function createCompileService({ async function compile( document: TextDocument | TextDocumentIdentifier, additionalOptions?: CompilerOptions, - bypassCache: boolean = false, - trackAction: boolean = false, + runOptions?: { + bypassCache?: boolean; + trackAction?: boolean; + }, ): Promise { const path = await fileService.getPath(document); const mainFile = await getMainFileForDocument(path); @@ -167,7 +171,7 @@ export function createCompileService({ let program: Program; try { program = await compileProgram( - trackAction + runOptions?.trackAction ? { ...compilerHost, logSink: { @@ -180,7 +184,7 @@ export function createCompileService({ : compilerHost, mainFile, options, - bypassCache ? undefined : oldPrograms.get(mainFile), + runOptions?.bypassCache ? undefined : oldPrograms.get(mainFile), ); oldPrograms.set(mainFile, program); if (!fileService.upToDate(document)) { diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 85553804b7e..1305af6b8b3 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -380,7 +380,10 @@ export function createServer(host: ServerHost): Server { ...param.options, }; - const result = await compileService.compile(param.doc, option, true, true); + const result = await compileService.compile(param.doc, option, { + bypassCache: true, + trackAction: true, + }); if (result === undefined) { return { hasError: true, From 27280430435d6f9256840757056c7c5bcfc5d4aa Mon Sep 17 00:00:00 2001 From: "FAREAST\\chunyu" Date: Wed, 14 May 2025 16:10:18 +0800 Subject: [PATCH 23/23] resolve circle dependency --- packages/typespec-vscode/src/extension-component.ts | 7 +++++++ packages/typespec-vscode/src/extension.ts | 8 ++++---- .../typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 packages/typespec-vscode/src/extension-component.ts diff --git a/packages/typespec-vscode/src/extension-component.ts b/packages/typespec-vscode/src/extension-component.ts new file mode 100644 index 00000000000..ce49bd4c078 --- /dev/null +++ b/packages/typespec-vscode/src/extension-component.ts @@ -0,0 +1,7 @@ +import { TspLanguageClient } from "./tsp-language-client"; + +export let client: TspLanguageClient | undefined; + +export function setClient(newClient: TspLanguageClient) { + client = newClient; +} diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index ba8aa8cfbaa..fe16c844005 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -5,6 +5,7 @@ import "./pre-extension-activate.js"; import vscode, { commands, ExtensionContext, TabInputText } from "vscode"; import { State } from "vscode-languageclient"; import { createCodeActionProvider } from "./code-action-provider.js"; +import { client, setClient } from "./extension-component.js"; import { ExtensionStateManager } from "./extension-state-manager.js"; import { ExtensionLogListener, getPopupAction } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; @@ -29,7 +30,6 @@ import { importFromOpenApi3 } from "./vscode-cmd/import-from-openapi3.js"; import { installCompilerGlobally } from "./vscode-cmd/install-tsp-compiler.js"; import { clearOpenApi3PreviewTempFolders, showOpenApi3 } from "./vscode-cmd/openapi3-preview.js"; -export let client: TspLanguageClient | undefined; /** * Workaround: LogOutputChannel doesn't work well with LSP RemoteConsole, so having a customized LogOutputChannel to make them work together properly * More detail can be found at https://github.com/microsoft/vscode-discussions/discussions/1149 @@ -236,10 +236,10 @@ async function recreateLSPClient( ): Promise> { logger.info("Recreating TypeSpec LSP server..."); const oldClient = client; - client = await TspLanguageClient.create(activityId, context, outputChannel); + setClient(await TspLanguageClient.create(activityId, context, outputChannel)); await oldClient?.stop(); - await client.start(activityId); - if (client.state === State.Running) { + await client?.start(activityId); + if (client?.state === State.Running) { telemetryClient.logOperationDetailTelemetry(activityId, { compilerVersion: client.initializeResult?.serverInfo?.version ?? "< 0.64.0", }); diff --git a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts index fd9e2b8020f..bffabcfce17 100644 --- a/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts +++ b/packages/typespec-vscode/src/vscode-cmd/emit-code/emit-code.ts @@ -5,7 +5,7 @@ import { inspect } from "util"; import vscode, { QuickInputButton, Uri } from "vscode"; import { Document, isScalar, isSeq } from "yaml"; import { StartFileName, TspConfigFileName } from "../../const.js"; -import { client } from "../../extension.js"; +import { client } from "../../extension-component.js"; import logger from "../../log/logger.js"; import { InstallAction, npmDependencyType, NpmUtil } from "../../npm-utils.js"; import { getDirectoryPath } from "../../path-utils.js";