From b60001d092fdbee4a40620b2e2f42be879be280d Mon Sep 17 00:00:00 2001 From: Christopher Aubut Date: Tue, 11 Jun 2024 16:51:59 -0600 Subject: [PATCH 1/6] feat(typescript-estree): exposes tsserver logs with ProjectService --- docs/packages/TypeScript_ESTree.mdx | 2 + .../create-program/createProjectService.ts | 62 ++++++-- .../tests/lib/createProjectService.test.ts | 132 ++++++++++++++++++ 3 files changed, 186 insertions(+), 10 deletions(-) diff --git a/docs/packages/TypeScript_ESTree.mdx b/docs/packages/TypeScript_ESTree.mdx index e8a27eb0f012..b8317ae6f25f 100644 --- a/docs/packages/TypeScript_ESTree.mdx +++ b/docs/packages/TypeScript_ESTree.mdx @@ -367,3 +367,5 @@ const { ast, services } = parseAndGenerateServices(code, { If you encounter a bug with the parser that you want to investigate, you can turn on the debug logging via setting the environment variable: `DEBUG=typescript-eslint:*`. I.e. in this repo you can run: `DEBUG=typescript-eslint:* yarn lint`. + +To include TypeScript server logs when using Project Services, include the following in the environment variable: `DEBUG=TBD` # FIXME: before PR diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 9f0f44dcec68..3f1af534ffca 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-empty-function -- for TypeScript APIs*/ import os from 'node:os'; +import debug from 'debug'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type { ProjectServiceOptions } from '../parser-options'; @@ -8,6 +9,19 @@ import { validateDefaultProjectForFilesGlob } from './validateDefaultProjectForF const DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD = 8; +const logTsserverErr = debug( + 'typescript-eslint:typescript-estree:tsserver:err', +); +const logTsserverInfo = debug( + 'typescript-eslint:typescript-estree:tsserver:info', +); +const logTsserverPerf = debug( + 'typescript-eslint:typescript-estree:tsserver:perf', +); +const logTsserverEvent = debug( + 'typescript-eslint:typescript-estree:tsserver:event', +); + const doNothing = (): void => {}; const createStubFileWatcher = (): ts.FileWatcher => ({ @@ -48,21 +62,49 @@ export function createProjectService( watchFile: createStubFileWatcher, }; + const logger: ts.server.Logger = { + close: doNothing, + endGroup: doNothing, + getLogFileName: (): undefined => undefined, + // The debug library doesn't use levels without creating a namespace for each. + // Log levels are not passed to the writer so we wouldn't be able to forward + // to a respective namespace. Supporting would require an additional flag for + // grainular control. Defaulting to all levels for now. + hasLevel: (): boolean => true, + info(s) { + this.msg(s, tsserver.server.Msg.Info); + }, + loggingEnabled: (): boolean => + // if none of the debug namespaces are enabled, then don;t enable logging in tsserver + logTsserverInfo.enabled || + logTsserverErr.enabled || + logTsserverPerf.enabled, + msg: (s, type) => { + switch (type) { + case tsserver.server.Msg.Err: + logTsserverErr(s); + break; + case tsserver.server.Msg.Perf: + logTsserverPerf(s); + break; + default: + logTsserverInfo(s); + } + }, + perftrc(s) { + this.msg(s, tsserver.server.Msg.Perf); + }, + startGroup: doNothing, + }; + const service = new tsserver.server.ProjectService({ host: system, cancellationToken: { isCancellationRequested: (): boolean => false }, useSingleInferredProject: false, useInferredProjectPerProjectRoot: false, - logger: { - close: doNothing, - endGroup: doNothing, - getLogFileName: (): undefined => undefined, - hasLevel: (): boolean => false, - info: doNothing, - loggingEnabled: (): boolean => false, - msg: doNothing, - perftrc: doNothing, - startGroup: doNothing, + logger, + eventHandler: (e): void => { + logTsserverEvent(e); }, session: undefined, jsDocParsingMode, diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts index 1159e8625f41..da0cd2eb046d 100644 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import * as ts from 'typescript'; import { createProjectService } from '../../src/create-program/createProjectService'; @@ -9,7 +10,19 @@ jest.mock('typescript/lib/tsserverlibrary', () => ({ ...jest.requireActual('typescript/lib/tsserverlibrary'), readConfigFile: mockReadConfigFile, server: { + ...jest.requireActual('typescript/lib/tsserverlibrary').server, ProjectService: class { + logger: ts.server.Logger; + eventHandler: ts.server.ProjectServiceEventHandler | undefined; + constructor( + ...args: ConstructorParameters + ) { + this.logger = args[0].logger; + this.eventHandler = args[0].eventHandler; + this.eventHandler!({ + eventName: 'projectLoadingStart', + } as ts.server.ProjectLoadingStartEvent); + } setCompilerOptionsForInferredProjects = mockSetCompilerOptionsForInferredProjects; }, @@ -17,6 +30,10 @@ jest.mock('typescript/lib/tsserverlibrary', () => ({ })); describe('createProjectService', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('sets allowDefaultProject when options.allowDefaultProject is defined', () => { const allowDefaultProject = ['./*.js']; const settings = createProjectService({ allowDefaultProject }, undefined); @@ -89,4 +106,119 @@ describe('createProjectService', () => { compilerOptions, ); }); + + it('uses the default projects error debugger for error messages when enabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + const { service } = createProjectService(undefined, undefined); + debug.enable('typescript-eslint:typescript-estree:tsserver:err'); + const enabled = service.logger.loggingEnabled(); + service.logger.msg('foo', ts.server.Msg.Err); + debug.disable(); + + expect(enabled).toBe(true); + expect(process.stderr.write).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:typescript-estree:tsserver:err foo\n$/, + ), + ); + }); + + it('does not use the default projects error debugger for error messages when disabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + const { service } = createProjectService(undefined, undefined); + const enabled = service.logger.loggingEnabled(); + service.logger.msg('foo', ts.server.Msg.Err); + + expect(enabled).toBe(false); + expect(process.stderr.write).toHaveBeenCalledTimes(0); + }); + + it('uses the default projects info debugger for info messages when enabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + const { service } = createProjectService(undefined, undefined); + debug.enable('typescript-eslint:typescript-estree:tsserver:info'); + const enabled = service.logger.loggingEnabled(); + service.logger.info('foo'); + debug.disable(); + + expect(enabled).toBe(true); + expect(process.stderr.write).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:typescript-estree:tsserver:info foo\n$/, + ), + ); + }); + + it('does not use the default projects info debugger for info messages when disabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + const { service } = createProjectService(undefined, undefined); + const enabled = service.logger.loggingEnabled(); + service.logger.info('foo'); + + expect(enabled).toBe(false); + expect(process.stderr.write).toHaveBeenCalledTimes(0); + }); + + it('uses the default projects perf debugger for perf messages when enabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + const { service } = createProjectService(undefined, undefined); + debug.enable('typescript-eslint:typescript-estree:tsserver:perf'); + const enabled = service.logger.loggingEnabled(); + service.logger.perftrc('foo'); + debug.disable(); + + expect(enabled).toBe(true); + expect(process.stderr.write).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:typescript-estree:tsserver:perf foo\n$/, + ), + ); + }); + + it('does not use the default projects perf debugger for perf messages when disabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + const { service } = createProjectService(undefined, undefined); + const enabled = service.logger.loggingEnabled(); + service.logger.perftrc('foo'); + + expect(enabled).toBe(false); + expect(process.stderr.write).toHaveBeenCalledTimes(0); + }); + + it('enables all log levels for the default projects logger', () => { + const { service } = createProjectService(undefined, undefined); + + expect(service.logger.hasLevel(ts.server.LogLevel.terse)).toBe(true); + expect(service.logger.hasLevel(ts.server.LogLevel.normal)).toBe(true); + expect(service.logger.hasLevel(ts.server.LogLevel.requestTime)).toBe(true); + expect(service.logger.hasLevel(ts.server.LogLevel.verbose)).toBe(true); + }); + + it('does not return a log filename with the default projects logger', () => { + const { service } = createProjectService(undefined, undefined); + + expect(service.logger.getLogFileName()).toBeUndefined(); + }); + + it('uses the default projects event debugger for event handling when enabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + + debug.enable('typescript-eslint:typescript-estree:tsserver:event'); + createProjectService(undefined, undefined); + debug.disable(); + + expect(process.stderr.write).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:typescript-estree:tsserver:event { eventName: 'projectLoadingStart' }\n$/, + ), + ); + }); + + it('does not use the default projects event debugger for event handling when disabled', () => { + jest.spyOn(process.stderr, 'write').mockImplementation(); + + createProjectService(undefined, undefined); + + expect(process.stderr.write).toHaveBeenCalledTimes(0); + }); }); From 6c99fe53bdd7f06d219280fb0fc4a8a513a20791 Mon Sep 17 00:00:00 2001 From: Christopher Aubut Date: Tue, 11 Jun 2024 17:00:25 -0600 Subject: [PATCH 2/6] chore(typescript-estree): fixes comment typo --- .../src/create-program/createProjectService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 3f1af534ffca..0f6161a24d6f 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -75,7 +75,7 @@ export function createProjectService( this.msg(s, tsserver.server.Msg.Info); }, loggingEnabled: (): boolean => - // if none of the debug namespaces are enabled, then don;t enable logging in tsserver + // if none of the debug namespaces are enabled, then don't enable logging in tsserver logTsserverInfo.enabled || logTsserverErr.enabled || logTsserverPerf.enabled, From f124343aa553988511084aa1c9fa82c5f00ba51c Mon Sep 17 00:00:00 2001 From: Christopher Aubut Date: Tue, 11 Jun 2024 17:17:54 -0600 Subject: [PATCH 3/6] chore(typescript-estree): fixes another comment typo --- .../src/create-program/createProjectService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 0f6161a24d6f..697ece67a494 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -69,7 +69,7 @@ export function createProjectService( // The debug library doesn't use levels without creating a namespace for each. // Log levels are not passed to the writer so we wouldn't be able to forward // to a respective namespace. Supporting would require an additional flag for - // grainular control. Defaulting to all levels for now. + // granular control. Defaulting to all levels for now. hasLevel: (): boolean => true, info(s) { this.msg(s, tsserver.server.Msg.Info); From ace6dcfa4d004e9622dfa524e71b0561db9459a9 Mon Sep 17 00:00:00 2001 From: Christopher Aubut Date: Thu, 13 Jun 2024 15:24:01 -0600 Subject: [PATCH 4/6] docs: updates DEBUG usage --- docs/packages/TypeScript_ESTree.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/packages/TypeScript_ESTree.mdx b/docs/packages/TypeScript_ESTree.mdx index b8317ae6f25f..1ac35da35cd5 100644 --- a/docs/packages/TypeScript_ESTree.mdx +++ b/docs/packages/TypeScript_ESTree.mdx @@ -368,4 +368,6 @@ const { ast, services } = parseAndGenerateServices(code, { If you encounter a bug with the parser that you want to investigate, you can turn on the debug logging via setting the environment variable: `DEBUG=typescript-eslint:*`. I.e. in this repo you can run: `DEBUG=typescript-eslint:* yarn lint`. -To include TypeScript server logs when using Project Services, include the following in the environment variable: `DEBUG=TBD` # FIXME: before PR +This will include TypeScript server logs. +To turn off these logs, include `-typescript-eslint:typescript-estree:tsserver:*` when setting the environment variable. +I.e. for this repo change to: `DEBUG='typescript-eslint:*,-typescript-eslint:typescript-estree:tsserver:*' yarn lint`. From 4108d433018a936d99aa0051a9cc0817c02dffda Mon Sep 17 00:00:00 2001 From: Christopher Aubut Date: Thu, 13 Jun 2024 15:39:46 -0600 Subject: [PATCH 5/6] chore: adds basic log msg to createProjectService and disabled event handler if no debugger --- .../src/create-program/createProjectService.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 697ece67a494..508505b0ef2d 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -9,6 +9,7 @@ import { validateDefaultProjectForFilesGlob } from './validateDefaultProjectForF const DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD = 8; +const log = debug('typescript-eslint:typescript-estree:createProjectService'); const logTsserverErr = debug( 'typescript-eslint:typescript-estree:tsserver:err', ); @@ -97,20 +98,25 @@ export function createProjectService( startGroup: doNothing, }; + log('Creating project service with: %o', options); + const service = new tsserver.server.ProjectService({ host: system, cancellationToken: { isCancellationRequested: (): boolean => false }, useSingleInferredProject: false, useInferredProjectPerProjectRoot: false, logger, - eventHandler: (e): void => { - logTsserverEvent(e); - }, + eventHandler: logTsserverEvent.enabled + ? (e): void => { + logTsserverEvent(e); + } + : undefined, session: undefined, jsDocParsingMode, }); if (options.defaultProject) { + log('Enabling default project: %s', options.defaultProject); let configRead; try { From 26515874e8f465342e1cac22c7840e7e9e32e254 Mon Sep 17 00:00:00 2001 From: Christopher Aubut Date: Thu, 13 Jun 2024 15:50:46 -0600 Subject: [PATCH 6/6] chore: fix tests --- .../tests/lib/createProjectService.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts index da0cd2eb046d..f380c960ff1a 100644 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -19,9 +19,11 @@ jest.mock('typescript/lib/tsserverlibrary', () => ({ ) { this.logger = args[0].logger; this.eventHandler = args[0].eventHandler; - this.eventHandler!({ - eventName: 'projectLoadingStart', - } as ts.server.ProjectLoadingStartEvent); + if (this.eventHandler) { + this.eventHandler({ + eventName: 'projectLoadingStart', + } as ts.server.ProjectLoadingStartEvent); + } } setCompilerOptionsForInferredProjects = mockSetCompilerOptionsForInferredProjects;