diff --git a/docs/packages/TypeScript_ESTree.mdx b/docs/packages/TypeScript_ESTree.mdx index f589cb8f6605..df50232716f0 100644 --- a/docs/packages/TypeScript_ESTree.mdx +++ b/docs/packages/TypeScript_ESTree.mdx @@ -277,9 +277,14 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ interface ProjectServiceOptions { /** - * Globs of files to allow running with the default inferred project settings. + * Globs of files to allow running with the default project compiler options. */ allowDefaultProjectForFiles?: string[]; + + /** + * Path to a TSConfig to use instead of TypeScript's default project configuration. + */ + defaultProject?: string; } interface ParserServices { diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 1cf7f9d82992..c355033191c4 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/no-empty-function -- for TypeScript APIs*/ +import os from 'node:os'; + import type * as ts from 'typescript/lib/tsserverlibrary'; import type { ProjectServiceOptions } from '../parser-options'; @@ -58,6 +60,42 @@ export function createProjectService( jsDocParsingMode, }); + if (typeof options === 'object' && options.defaultProject) { + let configRead; + + try { + configRead = tsserver.readConfigFile( + options.defaultProject, + system.readFile, + ); + } catch (error) { + throw new Error( + `Could not parse default project '${options.defaultProject}': ${(error as Error).message}`, + ); + } + + if (configRead.error) { + throw new Error( + `Could not read default project '${options.defaultProject}': ${tsserver.formatDiagnostic( + configRead.error, + { + getCurrentDirectory: system.getCurrentDirectory, + getCanonicalFileName: fileName => fileName, + getNewLine: () => os.EOL, + }, + )}`, + ); + } + + service.setCompilerOptionsForInferredProjects( + ( + configRead.config as { + compilerOptions: ts.server.protocol.InferredProjectCompilerOptions; + } + ).compilerOptions, + ); + } + return { allowDefaultProjectForFiles: typeof options === 'object' diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 5df5b5b0e271..02c77dfe1a32 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -106,9 +106,14 @@ interface ParseOptions { */ export interface ProjectServiceOptions { /** - * Globs of files to allow running with the default inferred project settings. + * Globs of files to allow running with the default project compiler options. */ allowDefaultProjectForFiles?: string[]; + + /** + * Path to a TSConfig to use instead of TypeScript's default project configuration. + */ + defaultProject?: string; } interface ParseAndGenerateServicesOptions extends ParseOptions { diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts index 9541dcd43942..a81106d03620 100644 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -1,5 +1,21 @@ +import * as ts from 'typescript'; + import { createProjectService } from '../../src/create-program/createProjectService'; +const mockReadConfigFile = jest.fn(); +const mockSetCompilerOptionsForInferredProjects = jest.fn(); + +jest.mock('typescript/lib/tsserverlibrary', () => ({ + ...jest.requireActual('typescript/lib/tsserverlibrary'), + readConfigFile: mockReadConfigFile, + server: { + ProjectService: class { + setCompilerOptionsForInferredProjects = + mockSetCompilerOptionsForInferredProjects; + }, + }, +})); + describe('createProjectService', () => { it('sets allowDefaultProjectForFiles when options.allowDefaultProjectForFiles is defined', () => { const allowDefaultProjectForFiles = ['./*.js']; @@ -18,4 +34,64 @@ describe('createProjectService', () => { expect(settings.allowDefaultProjectForFiles).toBeUndefined(); }); + + it('throws an error when options.defaultProject is set and readConfigFile returns an error', () => { + mockReadConfigFile.mockReturnValue({ + error: { + category: ts.DiagnosticCategory.Error, + code: 1234, + file: ts.createSourceFile('./tsconfig.json', '', { + languageVersion: ts.ScriptTarget.Latest, + }), + start: 0, + length: 0, + messageText: 'Oh no!', + } satisfies ts.Diagnostic, + }); + + expect(() => + createProjectService( + { + allowDefaultProjectForFiles: ['file.js'], + defaultProject: './tsconfig.json', + }, + undefined, + ), + ).toThrow( + /Could not read default project '\.\/tsconfig.json': .+ error TS1234: Oh no!/, + ); + }); + + it('throws an error when options.defaultProject is set and readConfigFile throws an error', () => { + mockReadConfigFile.mockImplementation(() => { + throw new Error('Oh no!'); + }); + + expect(() => + createProjectService( + { + allowDefaultProjectForFiles: ['file.js'], + defaultProject: './tsconfig.json', + }, + undefined, + ), + ).toThrow("Could not parse default project './tsconfig.json': Oh no!"); + }); + + it('uses the default projects compiler options when options.defaultProject is set and readConfigFile succeeds', () => { + const compilerOptions = { strict: true }; + mockReadConfigFile.mockReturnValue({ config: { compilerOptions } }); + + const { service } = createProjectService( + { + allowDefaultProjectForFiles: ['file.js'], + defaultProject: './tsconfig.json', + }, + undefined, + ); + + expect(service.setCompilerOptionsForInferredProjects).toHaveBeenCalledWith( + compilerOptions, + ); + }); });
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: