Skip to content

Commit 3493f9b

Browse files
authored
Merge branch 'main' into haiyzhan/fix_in_methodProvider
2 parents 5f1a6ce + f3169c4 commit 3493f9b

File tree

25 files changed

+819
-570
lines changed

25 files changed

+819
-570
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
3+
changeKind: feature
4+
packages:
5+
- "@typespec/compiler"
6+
- typespec-vscode
7+
---
8+
9+
Allow LSP to configure which emitters to include for live checks
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Connection } from "vscode-languageserver/node.js";
2+
import { ServerHost } from "./types.js";
3+
4+
interface LSPConfig {
5+
emit?: string[];
6+
}
7+
8+
interface Config {
9+
lsp?: LSPConfig;
10+
}
11+
12+
/**
13+
* TypeSpec client-side configuration provider
14+
* Inspired by VS Code's WorkspaceConfiguration API for extensibility
15+
*/
16+
export interface ClientConfigProvider {
17+
/**
18+
* Initialize client configuration with connection and host
19+
* @param connection Language server connection
20+
* @param host Server host instance
21+
*/
22+
initialize(connection: Connection, host: ServerHost): Promise<void>;
23+
24+
config?: Config;
25+
}
26+
27+
export function createClientConfigProvider(): ClientConfigProvider {
28+
let config: Config | undefined;
29+
30+
async function initialize(connection: Connection, host: ServerHost): Promise<void> {
31+
try {
32+
const configs = await connection.workspace.getConfiguration("typespec");
33+
host.log({ level: "debug", message: "VSCode settings loaded", detail: configs });
34+
35+
// Transform the raw configuration to match our Config interface
36+
config = {
37+
lsp: {
38+
emit: configs?.lsp?.emit,
39+
},
40+
};
41+
42+
connection.onDidChangeConfiguration(async (params) => {
43+
if (params.settings) {
44+
const newConfigs = params.settings?.typespec;
45+
config = {
46+
lsp: {
47+
emit: newConfigs?.lsp?.emit,
48+
},
49+
};
50+
}
51+
52+
host.log({ level: "debug", message: "Configuration changed", detail: params.settings });
53+
});
54+
} catch (error) {
55+
host.log({
56+
level: "error",
57+
message: "An error occurred while loading the VSCode settings",
58+
detail: error,
59+
});
60+
}
61+
}
62+
63+
return {
64+
initialize,
65+
get config() {
66+
return config;
67+
},
68+
};
69+
}

packages/compiler/src/server/compile-service.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { doIO, loadFile } from "../utils/io.js";
2525
import { resolveTspMain } from "../utils/misc.js";
2626
import { getLocationInYamlScript } from "../yaml/diagnostics.js";
2727
import { parseYaml } from "../yaml/parser.js";
28+
import { ClientConfigProvider } from "./client-config-provider.js";
2829
import { serverOptions } from "./constants.js";
2930
import { FileService } from "./file-service.js";
3031
import { FileSystemCache } from "./file-system-cache.js";
@@ -78,6 +79,7 @@ export interface CompileServiceOptions {
7879
readonly serverHost: ServerHost;
7980
readonly compilerHost: CompilerHost;
8081
readonly log: (log: ServerLog) => void;
82+
readonly clientConfigsProvider?: ClientConfigProvider;
8183
}
8284

8385
export function createCompileService({
@@ -86,6 +88,7 @@ export function createCompileService({
8688
fileService,
8789
fileSystemCache,
8890
log,
91+
clientConfigsProvider,
8992
}: CompileServiceOptions): CompileService {
9093
const oldPrograms = new Map<string, Program>();
9194
const eventListeners = new Map<string, (...args: unknown[]) => void>();
@@ -140,6 +143,15 @@ export function createCompileService({
140143
...(additionalOptions ?? {}),
141144
};
142145

146+
// If emit is set in additionalOptions, use this setting first
147+
// otherwise, obtain the `typespec.lsp.emit` configuration from clientConfigsProvider
148+
if (additionalOptions?.emit === undefined) {
149+
const configEmits = clientConfigsProvider?.config?.lsp?.emit;
150+
if (configEmits) {
151+
options.emit = configEmits;
152+
}
153+
}
154+
143155
// add linter rule for unused using if user didn't configure it explicitly
144156
const unusedUsingRule = `${builtInLinterLibraryName}/${builtInLinterRule_UnusedUsing}`;
145157
if (

packages/compiler/src/server/server.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "vscode-languageserver/node.js";
1616
import { NodeHost } from "../core/node-host.js";
1717
import { typespecVersion } from "../manifest.js";
18+
import { createClientConfigProvider } from "./client-config-provider.js";
1819
import { createServer } from "./serverlib.js";
1920
import { CustomRequestName, Server, ServerHost, ServerLog } from "./types.js";
2021

@@ -86,7 +87,8 @@ function main() {
8687
},
8788
};
8889

89-
const s = createServer(host);
90+
const clientConfigProvider = createClientConfigProvider();
91+
const s = createServer(host, clientConfigProvider);
9092
server = s;
9193
s.log({ level: `info`, message: `TypeSpec language server v${typespecVersion}` });
9294
s.log({ level: `info`, message: `Module: ${fileURLToPath(import.meta.url)}` });
@@ -106,10 +108,13 @@ function main() {
106108
return await s.initialize(params);
107109
});
108110

109-
connection.onInitialized((params) => {
111+
connection.onInitialized(async (params) => {
110112
if (clientHasWorkspaceFolderCapability) {
111113
connection.workspace.onDidChangeWorkspaceFolders(s.workspaceFoldersChanged);
112114
}
115+
116+
// Initialize client configurations
117+
await clientConfigProvider.initialize(connection, host);
113118
s.initialized(params);
114119
});
115120

packages/compiler/src/server/serverlib.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ import { resolveModule, ResolveModuleHost } from "../module-resolver/module-reso
9999
import { listAllFilesInDir } from "../utils/fs-utils.js";
100100
import { getNormalizedRealPath, resolveTspMain } from "../utils/misc.js";
101101
import { getSemanticTokens } from "./classify.js";
102+
import { ClientConfigProvider } from "./client-config-provider.js";
102103
import { createCompileService } from "./compile-service.js";
103104
import { resolveCompletion } from "./completion.js";
104105
import { Commands } from "./constants.js";
@@ -131,7 +132,10 @@ import {
131132
ServerWorkspaceFolder,
132133
} from "./types.js";
133134

134-
export function createServer(host: ServerHost): Server {
135+
export function createServer(
136+
host: ServerHost,
137+
clientConfigsProvider?: ClientConfigProvider,
138+
): Server {
135139
const fileService = createFileService({ serverHost: host });
136140

137141
// Cache all file I/O. Only open documents are sent over the LSP pipe. When
@@ -159,6 +163,7 @@ export function createServer(host: ServerHost): Server {
159163
compilerHost,
160164
serverHost: host,
161165
log,
166+
clientConfigsProvider,
162167
});
163168
const currentDiagnosticIndex = new Map<number, Diagnostic>();
164169
let diagnosticIdCounter = 0;

packages/compiler/src/testing/test-server-host.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Diagnostic, FileChangeType } from "vscode-languageserver/node.js";
44
import { parse, visitChildren } from "../core/parser.js";
55
import { resolvePath } from "../core/path-utils.js";
66
import { IdentifierNode, SyntaxKind } from "../core/types.js";
7+
import { createClientConfigProvider } from "../server/client-config-provider.js";
78
import { Server, ServerHost, createServer } from "../server/index.js";
89
import { createStringMap } from "../utils/misc.js";
910
import { StandardTestLibrary, TestHostOptions, createTestFileSystem } from "./test-host.js";
@@ -108,7 +109,8 @@ export async function createTestServerHost(options?: TestHostOptions & { workspa
108109

109110
const workspaceDir = options?.workspaceDir ?? "./";
110111
const rootUri = serverHost.getURL(workspaceDir);
111-
const server = createServer(serverHost);
112+
const clientConfigProvider = createClientConfigProvider();
113+
const server = createServer(serverHost, clientConfigProvider);
112114
await server.initialize({
113115
rootUri: options?.caseInsensitiveFileSystem ? rootUri.toUpperCase() : rootUri,
114116
capabilities: {},
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { strictEqual } from "assert";
2+
import { beforeEach, describe, it, vi } from "vitest";
3+
import {
4+
ClientConfigProvider,
5+
createClientConfigProvider,
6+
} from "../../src/server/client-config-provider.js";
7+
8+
let configProvider: ClientConfigProvider;
9+
10+
beforeEach(() => {
11+
// Create a new client config provider before each test
12+
configProvider = createClientConfigProvider();
13+
});
14+
15+
describe("configuration API", () => {
16+
it("should return undefined when configuration is not initialized", () => {
17+
const config = configProvider.config;
18+
strictEqual(config, undefined);
19+
});
20+
it("should handle accessing config properties safely", () => {
21+
const config = configProvider.config;
22+
strictEqual(config?.lsp?.emit, undefined);
23+
});
24+
});
25+
26+
describe("initialization and VS Code integration", () => {
27+
let mockHost: any;
28+
29+
beforeEach(() => {
30+
mockHost = {
31+
log: vi.fn(),
32+
} as any;
33+
});
34+
35+
it("should handle initialization with mock connection and host", async () => {
36+
// Create mock connection and host
37+
const mockConnection = {
38+
workspace: {
39+
getConfiguration: vi.fn().mockResolvedValue({
40+
lsp: { emit: ["openapi3"] },
41+
formatting: { enabled: true },
42+
}),
43+
},
44+
onDidChangeConfiguration: vi.fn(),
45+
} as any;
46+
47+
await configProvider.initialize(mockConnection, mockHost);
48+
49+
const config = configProvider.config;
50+
strictEqual(config?.lsp?.emit?.[0], "openapi3");
51+
});
52+
53+
it("should completely replace configuration on change notifications", async () => {
54+
let onDidChangeHandler: any;
55+
56+
const mockConnection = {
57+
workspace: {
58+
getConfiguration: vi.fn().mockResolvedValue({
59+
lsp: { emit: ["openapi3"] },
60+
formatting: { enabled: true },
61+
}),
62+
},
63+
onDidChangeConfiguration: vi.fn().mockImplementation((handler) => {
64+
onDidChangeHandler = handler;
65+
}),
66+
} as any;
67+
68+
await configProvider.initialize(mockConnection, mockHost);
69+
70+
// Verify initial configuration
71+
const initialConfig = configProvider.config;
72+
strictEqual(initialConfig?.lsp?.emit?.[0], "openapi3");
73+
74+
// Simulate a configuration change that only includes some settings
75+
const changeParams = {
76+
settings: {
77+
typespec: {
78+
lsp: { emit: ["swagger"] },
79+
},
80+
},
81+
};
82+
83+
await onDidChangeHandler(changeParams);
84+
85+
// Verify configuration was updated
86+
const updatedConfig = configProvider.config;
87+
strictEqual(updatedConfig?.lsp?.emit?.[0], "swagger");
88+
});
89+
});

packages/http-client-csharp/generator/TestProjects/Spector/http/versioning/added/v1/tspCodeModel.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,13 +377,23 @@
377377
"$ref": "6"
378378
},
379379
"location": "Uri",
380-
"isApiVersion": false,
380+
"isApiVersion": true,
381381
"isContentType": false,
382382
"isRequired": true,
383383
"isEndpoint": false,
384384
"skipUrlEncoding": false,
385385
"explode": false,
386386
"kind": "Client",
387+
"defaultValue": {
388+
"$id": "36",
389+
"type": {
390+
"$id": "37",
391+
"kind": "string",
392+
"name": "string",
393+
"crossLanguageDefinitionId": "TypeSpec.string"
394+
},
395+
"value": "v1"
396+
},
387397
"serverUrlTemplate": "{endpoint}/versioning/added/api-version:{version}"
388398
}
389399
],

0 commit comments

Comments
 (0)