Skip to content

Commit

Permalink
feat: add "tsserver.trace" init option for tracing tsserver (#586)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl committed Sep 14, 2022
1 parent 851a527 commit e3e8930
Show file tree
Hide file tree
Showing 16 changed files with 251 additions and 62 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ The language server accepts various settings through the `initializationOptions`
| locale | string | The locale to use to show error messages. |
| plugins | object[] | An array of `{ name: string, location: string }` objects for registering a Typescript plugins. **Default**: [] |
| preferences | object | Preferences passed to the Typescript (`tsserver`) process. See below for more info. |
| tsserver | object | Options related to the `tsserver` process. See below for more info. |

The `tsserver` setting specifies additional options related to the internal `tsserver` process, like tracing and logging.

```ts
interface TsserverOptions {
/**
* The verbosity of logging of the tsserver communication.
* Delivered through the LSP messages and not related to file logging.
* @default 'off'
*/
trace?: 'off' | 'messages' | 'verbose';
}
```

The `preferences` object is an object specifying preferences for the internal `tsserver` process. Those options depend on the version of Typescript used but at the time of writing Typescript v4.4.3 contains these options:

Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import { readFileSync } from 'node:fs';
import { Command } from 'commander';
import lsp from 'vscode-languageserver';
import { createLspConnection } from './lsp-connection.js';
import * as lsp from 'vscode-languageserver';

const DEFAULT_LOG_LEVEL = lsp.MessageType.Info;
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), { encoding: 'utf8' }));
Expand Down
2 changes: 1 addition & 1 deletion src/diagnostic-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import type tsp from 'typescript/lib/protocol.d.js';
import * as lsp from 'vscode-languageserver';
import debounce from 'p-debounce';
import { Logger } from './logger.js';
import { Logger } from './utils/logger.js';
import { pathToUri, toDiagnostic } from './protocol-translation.js';
import { EventTypes } from './tsp-command-types.js';
import { LspDocuments } from './document.js';
Expand Down
8 changes: 4 additions & 4 deletions src/lsp-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import * as lsp from 'vscode-languageserver/node.js';
import lsp from 'vscode-languageserver/node.js';
import * as lspcalls from './lsp-protocol.calls.proposed.js';
import * as lspinlayHints from './lsp-protocol.inlayHints.proposed.js';
import { LspClientLogger } from './logger.js';
import { LspClientLogger } from './utils/logger.js';
import { LspServer } from './lsp-server.js';
import { LspClientImpl } from './lsp-client.js';

export interface IServerOptions {
export interface LspConnectionOptions {
tsserverPath: string;
tsserverLogFile?: string;
tsserverLogVerbosity?: string;
showMessageLevel: lsp.MessageType;
}

export function createLspConnection(options: IServerOptions): lsp.Connection {
export function createLspConnection(options: LspConnectionOptions): lsp.Connection {
const connection = lsp.createConnection(lsp.ProposedFeatures.all);
const lspClient = new LspClientImpl(connection);
const logger = new LspClientLogger(lspClient, options.showMessageLevel);
Expand Down
10 changes: 6 additions & 4 deletions src/lsp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as lspsemanticTokens from './semantic-tokens.js';
import tsp from 'typescript/lib/protocol.d.js';
import API from './utils/api.js';
import { CommandTypes, EventTypes } from './tsp-command-types.js';
import { Logger, PrefixingLogger } from './logger.js';
import { Logger, PrefixingLogger } from './utils/logger.js';
import { TspClient } from './tsp-client.js';
import { DiagnosticEventQueue } from './diagnostic-queue.js';
import { toDocumentHighlight, asTagsDocumentation, uriToPath, toSymbolKind, toLocation, pathToUri, toTextEdit, asPlainText, normalizePath } from './protocol-translation.js';
Expand All @@ -30,10 +30,11 @@ import { provideOrganizeImports } from './organize-imports.js';
import { TypeScriptInitializeParams, TypeScriptInitializationOptions, TypeScriptInitializeResult, SupportedFeatures } from './ts-protocol.js';
import { collectDocumentSymbols, collectSymbolInformation } from './document-symbol.js';
import { computeCallers, computeCallees } from './calls.js';
import { IServerOptions } from './utils/configuration.js';
import { TypeScriptServiceConfiguration } from './utils/configuration.js';
import { TypeScriptAutoFixProvider } from './features/fix-all.js';
import { TypeScriptInlayHintsProvider } from './features/inlay-hints.js';
import { SourceDefinitionCommand } from './features/source-definition.js';
import { Trace } from './tsServer/tracer.js';
import { TypeScriptVersion, TypeScriptVersionProvider } from './tsServer/versionProvider.js';
import { Position, Range } from './utils/typeConverters.js';
import { CodeActionKind } from './utils/types.js';
Expand All @@ -51,7 +52,7 @@ export class LspServer {

private readonly documents = new LspDocuments();

constructor(private options: IServerOptions) {
constructor(private options: TypeScriptServiceConfiguration) {
this.configurationManager = new ConfigurationManager(this.documents);
this.logger = new PrefixingLogger(options.logger, '[lspserver]');
}
Expand Down Expand Up @@ -111,7 +112,7 @@ export class LspServer {
this.workspaceRoot = this.initializeParams.rootUri ? uriToPath(this.initializeParams.rootUri) : this.initializeParams.rootPath || undefined;

const userInitializationOptions: TypeScriptInitializationOptions = this.initializeParams.initializationOptions || {};
const { disableAutomaticTypingAcquisition, hostInfo, maxTsServerMemory, npmLocation, locale } = userInitializationOptions;
const { disableAutomaticTypingAcquisition, hostInfo, maxTsServerMemory, npmLocation, locale, tsserver } = userInitializationOptions;
const { logVerbosity, plugins }: TypeScriptInitializationOptions = {
logVerbosity: userInitializationOptions.logVerbosity || this.options.tsserverLogVerbosity,
plugins: userInitializationOptions.plugins || [],
Expand Down Expand Up @@ -168,6 +169,7 @@ export class LspServer {
);
this._tspClient = new TspClient({
lspClient: this.options.lspClient,
trace: Trace.fromString(tsserver?.trace || 'off'),
typescriptVersion,
logFile,
logVerbosity,
Expand Down
14 changes: 9 additions & 5 deletions src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import { normalizePath, pathToUri } from './protocol-translation.js';
import { TypeScriptInitializationOptions } from './ts-protocol.js';
import { LspClient, WithProgressOptions } from './lsp-client.js';
import { LspServer } from './lsp-server.js';
import { ConsoleLogger } from './logger.js';
import { ConsoleLogger } from './utils/logger.js';
import { TypeScriptVersionProvider } from './tsServer/versionProvider.js';
import { TypeScriptServiceConfiguration } from './utils/configuration.js';

const CONSOLE_LOG_LEVEL = ConsoleLogger.toMessageTypeLevel(process.env.CONSOLE_LOG_LEVEL);
export const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
Expand Down Expand Up @@ -165,16 +166,19 @@ interface TestLspServerOptions {
}

export async function createServer(options: TestLspServerOptions): Promise<TestLspServer> {
const typescriptVersionProvider = new TypeScriptVersionProvider();
const bundled = typescriptVersionProvider.bundledVersion();
const logger = new ConsoleLogger(CONSOLE_LOG_LEVEL);
const lspClient = new TestLspClient(options, logger);
const server = new TestLspServer({
const serverOptions: TypeScriptServiceConfiguration = {
logger,
lspClient,
};
const typescriptVersionProvider = new TypeScriptVersionProvider(serverOptions);
const bundled = typescriptVersionProvider.bundledVersion();
const server = new TestLspServer({
...serverOptions,
tsserverPath: bundled!.tsServerPath,
tsserverLogVerbosity: options.tsserverLogVerbosity,
tsserverLogFile: path.resolve(PACKAGE_ROOT, 'tsserver.log'),
lspClient,
});

lspClient.addApplyWorkspaceEditListener(args => {
Expand Down
15 changes: 13 additions & 2 deletions src/ts-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/
import * as lsp from 'vscode-languageserver-protocol';
import type tsp from 'typescript/lib/protocol.d.js';
import type { TraceValue } from './tsServer/tracer.js';

export namespace TypeScriptRenameRequest {
export const type = new lsp.RequestType<lsp.TextDocumentPositionParams, void, void>('_typescript.rename');
Expand Down Expand Up @@ -40,13 +41,23 @@ export interface TypeScriptPlugin {

export interface TypeScriptInitializationOptions {
disableAutomaticTypingAcquisition?: boolean;
hostInfo?: string;
locale?: string;
logVerbosity?: string;
maxTsServerMemory?: number;
npmLocation?: string;
locale?: string;
plugins: TypeScriptPlugin[];
preferences?: tsp.UserPreferences;
hostInfo?: string;
tsserver?: TsserverOptions;
}

interface TsserverOptions {
/**
* The verbosity of logging the tsserver communication through the LSP messages.
* This doesn't affect the file logging.
* @default 'off'
*/
trace?: TraceValue;
}

export type TypeScriptInitializeParams = lsp.InitializeParams & {
Expand Down
16 changes: 8 additions & 8 deletions src/tsServer/cancellation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@

import fs from 'node:fs';
import { temporaryFile } from 'tempy';
// import Tracer from '../utils/tracer';
import Tracer from './tracer.js';

export interface OngoingRequestCanceller {
readonly cancellationPipeName: string | undefined;
tryCancelOngoingRequest(seq: number): boolean;
}

export interface OngoingRequestCancellerFactory {
create(/*serverId: string, tracer: Tracer*/): OngoingRequestCanceller;
create(serverId: string, tracer: Tracer): OngoingRequestCanceller;
}

const noopRequestCanceller = new class implements OngoingRequestCanceller {
Expand All @@ -31,7 +31,7 @@ const noopRequestCanceller = new class implements OngoingRequestCanceller {
};

export const noopRequestCancellerFactory = new class implements OngoingRequestCancellerFactory {
create(/*_serverId: string, _tracer: Tracer*/): OngoingRequestCanceller {
create(_serverId: string, _tracer: Tracer): OngoingRequestCanceller {
return noopRequestCanceller;
}
};
Expand All @@ -40,8 +40,8 @@ export class NodeRequestCanceller implements OngoingRequestCanceller {
public readonly cancellationPipeName: string;

public constructor(
// private readonly _serverId: string,
// private readonly _tracer: Tracer,
private readonly _serverId: string,
private readonly _tracer: Tracer,
) {
this.cancellationPipeName = temporaryFile({ name: 'tscancellation' });
}
Expand All @@ -50,7 +50,7 @@ export class NodeRequestCanceller implements OngoingRequestCanceller {
if (!this.cancellationPipeName) {
return false;
}
// this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`);
this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`);
try {
fs.writeFileSync(this.cancellationPipeName + String(seq), '');
} catch {
Expand All @@ -61,7 +61,7 @@ export class NodeRequestCanceller implements OngoingRequestCanceller {
}

export const nodeRequestCancellerFactory = new class implements OngoingRequestCancellerFactory {
create(/*serverId: string, tracer: Tracer*/): OngoingRequestCanceller {
return new NodeRequestCanceller(/*serverId, tracer*/);
create(serverId: string, tracer: Tracer): OngoingRequestCanceller {
return new NodeRequestCanceller(serverId, tracer);
}
};
15 changes: 8 additions & 7 deletions src/tsServer/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { TspClientOptions } from '../tsp-client.js';
import { OngoingRequestCanceller } from './cancellation.js';
import { CallbackMap } from './callbackMap.js';
import { TypeScriptServerError } from './serverError.js';
import type Tracer from './tracer.js';
import type { TypeScriptVersion } from './versionProvider.js';

export enum ExecutionTarget {
Expand Down Expand Up @@ -92,7 +93,7 @@ export class ProcessBasedTsServer implements ITypeScriptServer {
private readonly _tsServerLogFile: string | undefined,
private readonly _requestCanceller: OngoingRequestCanceller,
private readonly _version: TypeScriptVersion,
// private readonly _tracer: Tracer,
private readonly _tracer: Tracer,
) {
this._process.onData(msg => {
this.dispatchMessage(msg);
Expand Down Expand Up @@ -161,11 +162,11 @@ export class ProcessBasedTsServer implements ITypeScriptServer {
const seq = (event as tsp.RequestCompletedEvent).body.request_seq;
const callback = this._callbacks.fetch(seq);
if (callback) {
// this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback);
this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback);
callback.onSuccess(undefined);
}
} else {
// this._tracer.traceEvent(this._serverId, event);
this._tracer.traceEvent(this._serverId, event);
this._eventHandlers.forEach(handler => handler(event));
}
break;
Expand Down Expand Up @@ -203,7 +204,7 @@ export class ProcessBasedTsServer implements ITypeScriptServer {
return;
}

// this._tracer.traceResponse(this._serverId, response, callback);
this._tracer.traceResponse(this._serverId, response, callback);
if (response.success) {
callback.onSuccess(response);
} else if (response.message === 'No content available.') {
Expand Down Expand Up @@ -253,7 +254,7 @@ export class ProcessBasedTsServer implements ITypeScriptServer {

private sendRequest(requestItem: RequestItem): void {
const serverRequest = requestItem.request;
// this._tracer.traceRequest(this._serverId, serverRequest, requestItem.expectsResponse, this._requestQueue.length);
this._tracer.traceRequest(this._serverId, serverRequest, requestItem.expectsResponse, this._requestQueue.length);

if (requestItem.expectsResponse && !requestItem.isAsync) {
this._pendingResponses.add(requestItem.request.seq);
Expand All @@ -277,8 +278,8 @@ export class ProcessBasedTsServer implements ITypeScriptServer {
return callback;
}

private logTrace(_message: string) {
// this._tracer.logTrace(this._serverId, message);
private logTrace(message: string) {
this._tracer.logTrace(this._serverId, message);
}

private static readonly fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']);
Expand Down
9 changes: 5 additions & 4 deletions src/tsServer/spawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@

import API from '../utils/api.js';
import { ServerType } from './requests.js';
import { Logger } from '../logger.js';
import { Logger } from '../utils/logger.js';
import type { TspClientOptions } from '../tsp-client.js';
import { nodeRequestCancellerFactory } from './cancellation.js';
import { ITypeScriptServer, ProcessBasedTsServer, TsServerProcessKind } from './server.js';
import { NodeTsServerProcessFactory } from './serverProcess.js';
import type Tracer from './tracer.js';
import type { TypeScriptVersion } from './versionProvider.js';

export class TypeScriptServerSpawner {
public constructor(
private readonly _apiVersion: API,
// private readonly _logDirectoryProvider: ILogDirectoryProvider,
private readonly _logger: Logger,
private readonly _tracer: Tracer,
) { }

public spawn(
Expand All @@ -31,7 +33,7 @@ export class TypeScriptServerSpawner {
): ITypeScriptServer {
const kind = TsServerProcessKind.Main;
const processFactory = new NodeTsServerProcessFactory();
const canceller = nodeRequestCancellerFactory.create(/*kind, this._tracer*/);
const canceller = nodeRequestCancellerFactory.create(kind, this._tracer);
const { args, tsServerLogFile } = this.getTsServerArgs(TsServerProcessKind.Main, configuration, this._apiVersion, canceller.cancellationPipeName);
const process = processFactory.fork(version, args, TsServerProcessKind.Main, configuration);
this._logger.log('Starting tsserver');
Expand All @@ -42,8 +44,7 @@ export class TypeScriptServerSpawner {
tsServerLogFile,
canceller,
version,
/*this._telemetryReporter,
this._tracer*/);
this._tracer);
}

private kindToServerType(kind: TsServerProcessKind): ServerType {
Expand Down

0 comments on commit e3e8930

Please sign in to comment.