Skip to content
Merged
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b0ac596
Merge pull request #1 from microsoft/main
mzhongl524 Nov 22, 2024
3fcbe52
Merge branch 'microsoft:main' into main
mzhongl524 Nov 25, 2024
c109816
Merge branch 'microsoft:main' into main
mzhongl524 Dec 6, 2024
23f0108
Merge branch 'microsoft:main' into main
mzhongl524 Dec 12, 2024
6f04f65
Merge branch 'microsoft:main' into main
mzhongl524 Dec 23, 2024
688ca03
Merge branch 'microsoft:main' into main
mzhongl524 Dec 24, 2024
de3725e
Merge branch 'microsoft:main' into main
mzhongl524 Dec 30, 2024
8130ffb
Merge branch 'microsoft:main' into main
mzhongl524 Jan 3, 2025
beb6433
Merge branch 'microsoft:main' into main
mzhongl524 Jan 15, 2025
8a501f2
Merge branch 'microsoft:main' into main
mzhongl524 Jan 15, 2025
6e1ab2d
Merge branch 'microsoft:main' into main
mzhongl524 Jan 23, 2025
a35052f
Merge branch 'microsoft:main' into main
mzhongl524 Jan 24, 2025
be57c13
Merge branch 'microsoft:main' into main
mzhongl524 Feb 5, 2025
3a9dc7a
Merge branch 'microsoft:main' into main
mzhongl524 Feb 10, 2025
ad7dff7
Merge branch 'microsoft:main' into main
mzhongl524 Feb 10, 2025
f8e8f2f
Merge branch 'microsoft:main' into main
mzhongl524 Feb 12, 2025
7a700c8
Merge branch 'microsoft:main' into main
mzhongl524 Feb 14, 2025
5099583
Merge branch 'microsoft:main' into main
mzhongl524 Feb 18, 2025
a45dbd5
Merge branch 'microsoft:main' into main
mzhongl524 Feb 25, 2025
68cc91d
Merge branch 'microsoft:main' into main
mzhongl524 Feb 25, 2025
48a44ad
Merge branch 'microsoft:main' into main
mzhongl524 Feb 27, 2025
b9283c4
Merge branch 'microsoft:main' into main
mzhongl524 Feb 28, 2025
4286411
Merge branch 'microsoft:main' into main
mzhongl524 Feb 28, 2025
e549035
Merge branch 'microsoft:main' into main
mzhongl524 Mar 5, 2025
9717310
Merge branch 'microsoft:main' into main
mzhongl524 Mar 6, 2025
75260f8
Merge branch 'microsoft:main' into main
mzhongl524 Mar 10, 2025
18b1595
Merge branch 'microsoft:main' into main
mzhongl524 Mar 11, 2025
51078a7
Merge branch 'microsoft:main' into main
mzhongl524 Mar 13, 2025
75ae17a
Merge branch 'microsoft:main' into main
mzhongl524 Mar 17, 2025
da28d95
Merge branch 'microsoft:main' into main
mzhongl524 Mar 18, 2025
4afbb98
Merge branch 'microsoft:main' into main
mzhongl524 Mar 25, 2025
463bdd5
Merge branch 'microsoft:main' into main
mzhongl524 Mar 27, 2025
c105d86
Merge branch 'microsoft:main' into main
mzhongl524 Mar 27, 2025
39dc63e
Merge branch 'microsoft:main' into main
mzhongl524 Mar 31, 2025
ddb0c11
Merge branch 'microsoft:main' into main
mzhongl524 Apr 1, 2025
dccb3dc
Merge branch 'microsoft:main' into main
mzhongl524 Apr 8, 2025
af9903b
Merge branch 'microsoft:main' into main
mzhongl524 Apr 10, 2025
2dddaf5
Merge branch 'microsoft:main' into main
mzhongl524 Apr 15, 2025
857b72b
Merge branch 'microsoft:main' into main
mzhongl524 Apr 16, 2025
41f34d9
Merge branch 'microsoft:main' into main
mzhongl524 Apr 17, 2025
f4dd38f
Merge branch 'microsoft:main' into main
mzhongl524 Apr 18, 2025
4fd48e5
Merge branch 'microsoft:main' into main
mzhongl524 Apr 21, 2025
9870818
Merge branch 'microsoft:main' into main
mzhongl524 Apr 22, 2025
9bf8cdc
Merge branch 'microsoft:main' into main
mzhongl524 Apr 25, 2025
d596efd
Merge branch 'microsoft:main' into main
mzhongl524 May 8, 2025
aed33c0
Merge branch 'microsoft:main' into main
mzhongl524 May 14, 2025
6b49c4b
Merge branch 'microsoft:main' into main
mzhongl524 May 15, 2025
6687f5c
Merge branch 'microsoft:main' into main
mzhongl524 May 15, 2025
10aa654
Merge branch 'microsoft:main' into main
mzhongl524 May 15, 2025
fbf956e
Merge branch 'microsoft:main' into main
mzhongl524 May 15, 2025
0e3d44d
Merge branch 'microsoft:main' into main
mzhongl524 May 15, 2025
6586a10
Merge branch 'microsoft:main' into main
mzhongl524 May 16, 2025
5920ad3
Merge branch 'microsoft:main' into main
mzhongl524 May 16, 2025
2ece297
Merge branch 'microsoft:main' into main
mzhongl524 May 16, 2025
4ad5c5b
Merge branch 'microsoft:main' into main
mzhongl524 May 19, 2025
11fd24f
Merge branch 'microsoft:main' into main
mzhongl524 May 21, 2025
f15e07c
Merge branch 'microsoft:main' into main
mzhongl524 May 22, 2025
7661273
Allow LSP to disable running emitters
mzhongl524 May 28, 2025
b43717a
updated
mzhongl524 Jun 3, 2025
4e395a0
updated
mzhongl524 Jun 6, 2025
2a9bb56
updated
mzhongl524 Jun 11, 2025
64975d6
updated
mzhongl524 Jun 11, 2025
23ab58b
Create allow-lsp-disable-running-emitters-2025-5-11-2-18-42.md
mzhongl524 Jun 11, 2025
a5419df
updated
mzhongl524 Jun 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@typespec/compiler"
- typespec-vscode
---

Allow LSP to configure which emitters to include for live checks
69 changes: 69 additions & 0 deletions packages/compiler/src/server/client-config-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Connection } from "vscode-languageserver/node.js";
import { ServerHost } from "./types.js";

interface LSPConfig {
emit?: string[];
}

interface Config {
lsp?: LSPConfig;
}

/**
* TypeSpec client-side configuration provider
* Inspired by VS Code's WorkspaceConfiguration API for extensibility
*/
export interface ClientConfigProvider {
/**
* Initialize client configuration with connection and host
* @param connection Language server connection
* @param host Server host instance
*/
initialize(connection: Connection, host: ServerHost): Promise<void>;

config?: Config;
}

export function createClientConfigProvider(): ClientConfigProvider {
let config: Config | undefined;

async function initialize(connection: Connection, host: ServerHost): Promise<void> {
try {
const configs = await connection.workspace.getConfiguration("typespec");
host.log({ level: "debug", message: "VSCode settings loaded", detail: configs });

// Transform the raw configuration to match our Config interface
config = {
lsp: {
emit: configs?.lsp?.emit,
},
};

connection.onDidChangeConfiguration(async (params) => {
if (params.settings) {
const newConfigs = params.settings?.typespec;
config = {
lsp: {
emit: newConfigs?.lsp?.emit,
},
};
}

host.log({ level: "debug", message: "Configuration changed", detail: params.settings });
});
} catch (error) {
host.log({
level: "error",
message: "An error occurred while loading the VSCode settings",
detail: error,
});
}
}

return {
initialize,
get config() {
return config;
},
};
}
12 changes: 12 additions & 0 deletions packages/compiler/src/server/compile-service.ts
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import { doIO, loadFile } from "../utils/io.js";
import { resolveTspMain } from "../utils/misc.js";
import { getLocationInYamlScript } from "../yaml/diagnostics.js";
import { parseYaml } from "../yaml/parser.js";
import { ClientConfigProvider } from "./client-config-provider.js";
import { serverOptions } from "./constants.js";
import { FileService } from "./file-service.js";
import { FileSystemCache } from "./file-system-cache.js";
@@ -78,6 +79,7 @@ export interface CompileServiceOptions {
readonly serverHost: ServerHost;
readonly compilerHost: CompilerHost;
readonly log: (log: ServerLog) => void;
readonly clientConfigsProvider?: ClientConfigProvider;
}

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

// If emit is set in additionalOptions, use this setting first
// otherwise, obtain the `typespec.lsp.emit` configuration from clientConfigsProvider
if (additionalOptions?.emit === undefined) {
const configEmits = clientConfigsProvider?.config?.lsp?.emit;
if (configEmits) {
options.emit = configEmits;
}
}

// add linter rule for unused using if user didn't configure it explicitly
const unusedUsingRule = `${builtInLinterLibraryName}/${builtInLinterRule_UnusedUsing}`;
if (
9 changes: 7 additions & 2 deletions packages/compiler/src/server/server.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import {
} from "vscode-languageserver/node.js";
import { NodeHost } from "../core/node-host.js";
import { typespecVersion } from "../manifest.js";
import { createClientConfigProvider } from "./client-config-provider.js";
import { createServer } from "./serverlib.js";
import { CustomRequestName, Server, ServerHost, ServerLog } from "./types.js";

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

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

connection.onInitialized((params) => {
connection.onInitialized(async (params) => {
if (clientHasWorkspaceFolderCapability) {
connection.workspace.onDidChangeWorkspaceFolders(s.workspaceFoldersChanged);
}

// Initialize client configurations
await clientConfigProvider.initialize(connection, host);
s.initialized(params);
});

7 changes: 6 additions & 1 deletion packages/compiler/src/server/serverlib.ts
Original file line number Diff line number Diff line change
@@ -98,6 +98,7 @@ import { resolveModule, ResolveModuleHost } from "../module-resolver/module-reso
import { listAllFilesInDir } from "../utils/fs-utils.js";
import { getNormalizedRealPath, resolveTspMain } from "../utils/misc.js";
import { getSemanticTokens } from "./classify.js";
import { ClientConfigProvider } from "./client-config-provider.js";
import { createCompileService } from "./compile-service.js";
import { resolveCompletion } from "./completion.js";
import { Commands } from "./constants.js";
@@ -130,7 +131,10 @@ import {
ServerWorkspaceFolder,
} from "./types.js";

export function createServer(host: ServerHost): Server {
export function createServer(
host: ServerHost,
clientConfigsProvider?: ClientConfigProvider,
): Server {
const fileService = createFileService({ serverHost: host });

// Cache all file I/O. Only open documents are sent over the LSP pipe. When
@@ -158,6 +162,7 @@ export function createServer(host: ServerHost): Server {
compilerHost,
serverHost: host,
log,
clientConfigsProvider,
});
const currentDiagnosticIndex = new Map<number, Diagnostic>();
let diagnosticIdCounter = 0;
4 changes: 3 additions & 1 deletion packages/compiler/src/testing/test-server-host.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import { Diagnostic, FileChangeType } from "vscode-languageserver/node.js";
import { parse, visitChildren } from "../core/parser.js";
import { resolvePath } from "../core/path-utils.js";
import { IdentifierNode, SyntaxKind } from "../core/types.js";
import { createClientConfigProvider } from "../server/client-config-provider.js";
import { Server, ServerHost, createServer } from "../server/index.js";
import { createStringMap } from "../utils/misc.js";
import { StandardTestLibrary, TestHostOptions, createTestFileSystem } from "./test-host.js";
@@ -108,7 +109,8 @@ export async function createTestServerHost(options?: TestHostOptions & { workspa

const workspaceDir = options?.workspaceDir ?? "./";
const rootUri = serverHost.getURL(workspaceDir);
const server = createServer(serverHost);
const clientConfigProvider = createClientConfigProvider();
const server = createServer(serverHost, clientConfigProvider);
await server.initialize({
rootUri: options?.caseInsensitiveFileSystem ? rootUri.toUpperCase() : rootUri,
capabilities: {},
89 changes: 89 additions & 0 deletions packages/compiler/test/server/vscode-settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { strictEqual } from "assert";
import { beforeEach, describe, it, vi } from "vitest";
import {
ClientConfigProvider,
createClientConfigProvider,
} from "../../src/server/client-config-provider.js";

let configProvider: ClientConfigProvider;

beforeEach(() => {
// Create a new client config provider before each test
configProvider = createClientConfigProvider();
});

describe("configuration API", () => {
it("should return undefined when configuration is not initialized", () => {
const config = configProvider.config;
strictEqual(config, undefined);
});
it("should handle accessing config properties safely", () => {
const config = configProvider.config;
strictEqual(config?.lsp?.emit, undefined);
});
});

describe("initialization and VS Code integration", () => {
let mockHost: any;

beforeEach(() => {
mockHost = {
log: vi.fn(),
} as any;
});

it("should handle initialization with mock connection and host", async () => {
// Create mock connection and host
const mockConnection = {
workspace: {
getConfiguration: vi.fn().mockResolvedValue({
lsp: { emit: ["openapi3"] },
formatting: { enabled: true },
}),
},
onDidChangeConfiguration: vi.fn(),
} as any;

await configProvider.initialize(mockConnection, mockHost);

const config = configProvider.config;
strictEqual(config?.lsp?.emit?.[0], "openapi3");
});

it("should completely replace configuration on change notifications", async () => {
let onDidChangeHandler: any;

const mockConnection = {
workspace: {
getConfiguration: vi.fn().mockResolvedValue({
lsp: { emit: ["openapi3"] },
formatting: { enabled: true },
}),
},
onDidChangeConfiguration: vi.fn().mockImplementation((handler) => {
onDidChangeHandler = handler;
}),
} as any;

await configProvider.initialize(mockConnection, mockHost);

// Verify initial configuration
const initialConfig = configProvider.config;
strictEqual(initialConfig?.lsp?.emit?.[0], "openapi3");

// Simulate a configuration change that only includes some settings
const changeParams = {
settings: {
typespec: {
lsp: { emit: ["swagger"] },
},
},
};

await onDidChangeHandler(changeParams);

// Verify configuration was updated
const updatedConfig = configProvider.config;
strictEqual(updatedConfig?.lsp?.emit?.[0], "swagger");
});
});
9 changes: 9 additions & 0 deletions packages/typespec-vscode/package.json
Original file line number Diff line number Diff line change
@@ -98,6 +98,15 @@
"additionalProperties": false
}
},
"typespec.lsp.emit": {
"type": "array",
"default": null,
"description": "List of emitters that should be included in compilation in typespec LSP server when supporting LSP related functionalities. And only supports emitter running in dry mode.\n\nExample:\n\"typespec.lsp.emit\": [\n \"@typespec/openapi3\"\n]",
"scope": "machine-overridable",
"items": {
"type": "string"
}
},
"typespec.trace.server": {
"scope": "window",
"type": "string",
Loading
Oops, something went wrong.