From 17475ae1d42f1ab84d33845e87c26775cf1fa7a4 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 6 May 2025 14:59:52 -0400 Subject: [PATCH 01/20] feat: create standalone project-service, tsconfig-utils packages --- docs/packages/Project_Service.mdx | 17 + docs/packages/TSConfig_Utilsx.mdx | 17 + packages/project-service/LICENSE | 21 + packages/project-service/README.md | 12 + packages/project-service/package.json | 63 +++ packages/project-service/project.json | 13 + .../src}/createProjectService.ts | 79 ++-- packages/project-service/src/index.ts | 1 + .../tests/createProjectService.test.ts | 339 ++++++++++++++++ packages/project-service/tsconfig.build.json | 20 + packages/project-service/tsconfig.json | 22 + packages/project-service/tsconfig.spec.json | 18 + packages/project-service/tsconfig.tools.json | 10 + packages/project-service/vitest.config.mts | 22 + packages/tsconfig-utils/LICENSE | 21 + packages/tsconfig-utils/README.md | 12 + packages/tsconfig-utils/package.json | 61 +++ packages/tsconfig-utils/project.json | 16 + .../tsconfig-utils/src/compilerOptions.ts | 13 + .../src}/getParsedConfigFile.ts | 4 +- packages/tsconfig-utils/src/index.ts | 2 + .../tests/lib/getParsedConfigFile.test.ts | 2 +- packages/tsconfig-utils/tsconfig.build.json | 14 + packages/tsconfig-utils/tsconfig.json | 13 + packages/tsconfig-utils/tsconfig.spec.json | 26 ++ packages/tsconfig-utils/vitest.config.mts | 26 ++ packages/typescript-estree/package.json | 2 + .../src/create-program/shared.ts | 14 +- .../src/create-program/useProvidedPrograms.ts | 2 +- .../src/parseSettings/createParseSettings.ts | 33 +- .../src/parseSettings/index.ts | 4 +- .../typescript-estree/src/parser-options.ts | 2 - .../src/useProgramFromProjectService.ts | 53 +-- .../tests/lib/createParseSettings.test.ts | 4 +- .../tests/lib/createProjectService.test.ts | 379 ------------------ .../lib/useProgramFromProjectService.test.ts | 6 +- ...validateDefaultProjectForFilesGlob.test.ts | 2 +- .../typescript-estree/tsconfig.build.json | 6 + packages/typescript-estree/tsconfig.json | 6 + packages/website-eslint/build.mts | 1 + packages/website/docusaurus.config.mts | 48 ++- packages/website/sidebars/sidebar.base.js | 2 + tsconfig.json | 2 + yarn.lock | 33 +- 44 files changed, 982 insertions(+), 481 deletions(-) create mode 100644 docs/packages/Project_Service.mdx create mode 100644 docs/packages/TSConfig_Utilsx.mdx create mode 100644 packages/project-service/LICENSE create mode 100644 packages/project-service/README.md create mode 100644 packages/project-service/package.json create mode 100644 packages/project-service/project.json rename packages/{typescript-estree/src/create-program => project-service/src}/createProjectService.ts (71%) create mode 100644 packages/project-service/src/index.ts create mode 100644 packages/project-service/tests/createProjectService.test.ts create mode 100644 packages/project-service/tsconfig.build.json create mode 100644 packages/project-service/tsconfig.json create mode 100644 packages/project-service/tsconfig.spec.json create mode 100644 packages/project-service/tsconfig.tools.json create mode 100644 packages/project-service/vitest.config.mts create mode 100644 packages/tsconfig-utils/LICENSE create mode 100644 packages/tsconfig-utils/README.md create mode 100644 packages/tsconfig-utils/package.json create mode 100644 packages/tsconfig-utils/project.json create mode 100644 packages/tsconfig-utils/src/compilerOptions.ts rename packages/{typescript-estree/src/create-program => tsconfig-utils/src}/getParsedConfigFile.ts (94%) create mode 100644 packages/tsconfig-utils/src/index.ts rename packages/{typescript-estree => tsconfig-utils}/tests/lib/getParsedConfigFile.test.ts (97%) create mode 100644 packages/tsconfig-utils/tsconfig.build.json create mode 100644 packages/tsconfig-utils/tsconfig.json create mode 100644 packages/tsconfig-utils/tsconfig.spec.json create mode 100644 packages/tsconfig-utils/vitest.config.mts delete mode 100644 packages/typescript-estree/tests/lib/createProjectService.test.ts rename packages/typescript-estree/tests/{lib => }/validateDefaultProjectForFilesGlob.test.ts (88%) diff --git a/docs/packages/Project_Service.mdx b/docs/packages/Project_Service.mdx new file mode 100644 index 000000000000..5d4d4fef3406 --- /dev/null +++ b/docs/packages/Project_Service.mdx @@ -0,0 +1,17 @@ +--- +id: project-service +sidebar_label: project-service +toc_max_heading_level: 3 +--- + +import GeneratedDocs from './project-service/generated/index.md'; + +# `@typescript-eslint/project-service` + + + +> Standalone TypeScript project service wrapper for linting ✨ + +The following documentation is auto-generated from source code. + + diff --git a/docs/packages/TSConfig_Utilsx.mdx b/docs/packages/TSConfig_Utilsx.mdx new file mode 100644 index 000000000000..1ce3361c7fa4 --- /dev/null +++ b/docs/packages/TSConfig_Utilsx.mdx @@ -0,0 +1,17 @@ +--- +id: tsconfig-utils +sidebar_label: tsconfig-utils +toc_max_heading_level: 3 +--- + +import GeneratedDocs from './tsconfig-utils/generated/index.md'; + +# `@typescript-eslint/tsconfig-utils` + + + +> Utilities for collecting TSConfigs for linting scenarios ✨ + +The following documentation is auto-generated from source code. + + diff --git a/packages/project-service/LICENSE b/packages/project-service/LICENSE new file mode 100644 index 000000000000..a1164108d4d6 --- /dev/null +++ b/packages/project-service/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 typescript-eslint and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/project-service/README.md b/packages/project-service/README.md new file mode 100644 index 000000000000..916084c3df70 --- /dev/null +++ b/packages/project-service/README.md @@ -0,0 +1,12 @@ +# `@typescript-eslint/project-service` + +> Standalone TypeScript project service wrapper for linting. + +[![NPM Version](https://img.shields.io/npm/v/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) +[![NPM Downloads](https://img.shields.io/npm/dm/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) + +A standalone export of the "Project Service" that powers typed linting for typescript-eslint. + +> See https://typescript-eslint.io for general documentation on typescript-eslint, the tooling that allows you to run ESLint and Prettier on TypeScript code. + + diff --git a/packages/project-service/package.json b/packages/project-service/package.json new file mode 100644 index 000000000000..946e2e677899 --- /dev/null +++ b/packages/project-service/package.json @@ -0,0 +1,63 @@ +{ + "name": "@typescript-eslint/project-service", + "version": "8.32.0", + "description": "Standalone TypeScript project service wrapper for linting.", + "files": [ + "dist", + "!*.tsbuildinfo", + "package.json", + "README.md", + "LICENSE" + ], + "type": "commonjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "types": "./dist/index.d.ts", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/project-service" + }, + "bugs": { + "url": "https://github.com/typescript-eslint/typescript-eslint/issues" + }, + "homepage": "https://typescript-eslint.io", + "license": "MIT", + "keywords": [ + "eslint", + "typescript", + "estree" + ], + "scripts": { + "build": "tsc -b tsconfig.build.json", + "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist/ src/generated/ coverage/", + "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", + "lint": "npx nx lint", + "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", + "check-types": "npx nx typecheck" + }, + "dependencies": { + "@typescript-eslint/types": "^8.32.0" + }, + "devDependencies": { + "@vitest/coverage-v8": "^3.1.2", + "prettier": "^3.2.5", + "rimraf": "*", + "tsx": "*", + "typescript": "*", + "vitest": "^3.1.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } +} diff --git a/packages/project-service/project.json b/packages/project-service/project.json new file mode 100644 index 000000000000..7d029ba93a59 --- /dev/null +++ b/packages/project-service/project.json @@ -0,0 +1,13 @@ +{ + "name": "project-service", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "root": "packages/project-service", + "sourceRoot": "packages/project-service/src", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/project-service/src/createProjectService.ts similarity index 71% rename from packages/typescript-estree/src/create-program/createProjectService.ts rename to packages/project-service/src/createProjectService.ts index 26b2c5421612..9ac5a4cb5458 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/project-service/src/createProjectService.ts @@ -3,27 +3,24 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import debug from 'debug'; -import type { ProjectServiceOptions } from '../parser-options'; +import type { ProjectServiceOptions } from '@typescript-eslint/types'; -import { getParsedConfigFile } from './getParsedConfigFile'; -import { validateDefaultProjectForFilesGlob } from './validateDefaultProjectForFilesGlob'; +import { getParsedConfigFile } from '@typescript-eslint/tsconfig-utils'; const DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD = 8; const log = debug( - 'typescript-eslint:typescript-estree:create-program:createProjectService', -); -const logTsserverErr = debug( - 'typescript-eslint:typescript-estree:tsserver:err', + 'typescript-eslint:project-service:create-program:createProjectService', ); +const logTsserverErr = debug('typescript-eslint:project-service:tsserver:err'); const logTsserverInfo = debug( - 'typescript-eslint:typescript-estree:tsserver:info', + 'typescript-eslint:project-service:tsserver:info', ); const logTsserverPerf = debug( - 'typescript-eslint:typescript-estree:tsserver:perf', + 'typescript-eslint:project-service:tsserver:perf', ); const logTsserverEvent = debug( - 'typescript-eslint:typescript-estree:tsserver:event', + 'typescript-eslint:project-service:tsserver:event', ); const doNothing = (): void => {}; @@ -32,26 +29,62 @@ const createStubFileWatcher = (): ts.FileWatcher => ({ close: doNothing, }); +export type { ProjectServiceOptions }; + export type TypeScriptProjectService = ts.server.ProjectService; -export interface ProjectServiceSettings { +/** + * A created Project Service instances, as well as metadata on its creation. + */ +export interface ProjectServiceAndMetadata { + /** + * Files allowed to be loaded from the default project, if any were specified. + */ allowDefaultProject: string[] | undefined; + + /** + * The performance.now() timestamp of the last reload of the project service. + */ lastReloadTimestamp: number; + + /** + * The maximum number of files that can be matched by the default project. + */ maximumDefaultProjectFileMatchCount: number; + + /** + * The created TypeScript Project Service instance. + */ service: TypeScriptProjectService; } -export function createProjectService( - optionsRaw: boolean | ProjectServiceOptions | undefined, - jsDocParsingMode: ts.JSDocParsingMode | undefined, - tsconfigRootDir: string | undefined, -): ProjectServiceSettings { - const optionsRawObject = typeof optionsRaw === 'object' ? optionsRaw : {}; +export interface CreateProjectServiceSettings { + options?: ProjectServiceOptions; + jsDocParsingMode?: ts.JSDocParsingMode; + tsconfigRootDir?: string; +} + +/** + * @param settings Settings to create a new Project Service instance. + * @returns A new Project Service instance, as well as metadata on its creation. + * @example + * ```ts + * import { createProjectService } from '@typescript-eslint/project-service'; + * + * const { service } = createProjectService(); + * + * service.openClientFile('index.ts); + * ``` + */ +export function createProjectService({ + options: optionsRaw, + jsDocParsingMode, + tsconfigRootDir, +}: CreateProjectServiceSettings = {}): ProjectServiceAndMetadata { const options = { defaultProject: 'tsconfig.json', - ...optionsRawObject, + ...optionsRaw, }; - validateDefaultProjectForFilesGlob(options.allowDefaultProject); // We import this lazily to avoid its cost for users who don't use the service // TODO: Once we drop support for TS<5.3 we can import from "typescript" directly @@ -119,7 +152,7 @@ export function createProjectService( startGroup: doNothing, }; - log('Creating project service with: %o', options); + log('Creating Project Service with: %o', options); const service = new tsserver.server.ProjectService({ cancellationToken: { isCancellationRequested: (): boolean => false }, @@ -152,9 +185,9 @@ export function createProjectService( tsconfigRootDir, ); } catch (error) { - if (optionsRawObject.defaultProject) { + if (options.defaultProject) { throw new Error( - `Could not read project service default project '${options.defaultProject}': ${(error as Error).message}`, + `Could not read Project Service default project '${options.defaultProject}': ${(error as Error).message}`, ); } } @@ -162,7 +195,7 @@ export function createProjectService( if (configFile) { service.setCompilerOptionsForInferredProjects( // NOTE: The inferred projects API is not intended for source files when a tsconfig - // exists. There is no API that generates an InferredProjectCompilerOptions suggesting + // exists. There is no API that generates an InferredProjectCompilerOptions suggesting // it is meant for hard coded options passed in. Hard asserting as a work around. // See https://github.com/microsoft/TypeScript/blob/27bcd4cb5a98bce46c9cdd749752703ead021a4b/src/server/protocol.ts#L1904 configFile.options as ts.server.protocol.InferredProjectCompilerOptions, diff --git a/packages/project-service/src/index.ts b/packages/project-service/src/index.ts new file mode 100644 index 000000000000..943c04088b69 --- /dev/null +++ b/packages/project-service/src/index.ts @@ -0,0 +1 @@ +export * from './createProjectService'; diff --git a/packages/project-service/tests/createProjectService.test.ts b/packages/project-service/tests/createProjectService.test.ts new file mode 100644 index 000000000000..259e6bb852d3 --- /dev/null +++ b/packages/project-service/tests/createProjectService.test.ts @@ -0,0 +1,339 @@ +import debug from 'debug'; +import * as ts from 'typescript'; + +import { createProjectService } from '../src/createProjectService.js'; + +const mockGetParsedConfigFile = vi.fn(); + +vi.mock('@typescript-eslint/tsconfig-utils', () => ({ + get getParsedConfigFile() { + return mockGetParsedConfigFile; + }, +})); + +const mockSetCompilerOptionsForInferredProjects = vi.fn(); +const mockSetHostConfiguration = vi.fn(); + +vi.mock(import('../src/createProjectService.js'), async importOriginal => { + const actual = await importOriginal(); + + return { + ...actual, + createProjectService: vi + .fn(actual.createProjectService) + .mockImplementation((...args) => { + const projectServiceSettings = actual.createProjectService(...args); + const service = + projectServiceSettings.service as typeof projectServiceSettings.service & { + eventHandler: ts.server.ProjectServiceEventHandler | undefined; + }; + + if (service.eventHandler) { + service.eventHandler({ + eventName: ts.server.ProjectLoadingStartEvent, + } as ts.server.ProjectLoadingStartEvent); + } + + return projectServiceSettings; + }), + }; +}); + +describe(createProjectService, () => { + const processStderrWriteSpy = vi + .spyOn(process.stderr, 'write') + .mockImplementation(() => true); + + beforeEach(() => { + const { ProjectService } = require('typescript/lib/tsserverlibrary').server; + + ProjectService.prototype.setCompilerOptionsForInferredProjects = + mockSetCompilerOptionsForInferredProjects; + ProjectService.prototype.setHostConfiguration = mockSetHostConfiguration; + }); + + afterEach(() => { + debug.disable(); + vi.clearAllMocks(); + }); + + describe('defaultProject', () => { + it('sets allowDefaultProject when options.allowDefaultProject is defined', () => { + const allowDefaultProject = ['./*.js']; + + const settings = createProjectService({ + options: { allowDefaultProject }, + }); + + expect(settings.allowDefaultProject).toBe(allowDefaultProject); + }); + + it('does not set allowDefaultProject when options.allowDefaultProject is not defined', () => { + const settings = createProjectService(); + + assert.isUndefined(settings.allowDefaultProject); + }); + + it('does not throw an error when options.defaultProject is not provided and getParsedConfigFile throws a diagnostic error', () => { + mockGetParsedConfigFile.mockImplementation(() => { + throw new Error('tsconfig.json(1,1): error TS1234: Oh no!'); + }); + + expect(() => + createProjectService({ + options: { allowDefaultProject: ['file.js'] }, + }), + ).not.toThrow(); + }); + + it('throws an error with a relative path when options.defaultProject is set to a relative path and getParsedConfigFile throws a diagnostic error', () => { + mockGetParsedConfigFile.mockImplementation(() => { + throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!'); + }); + + expect(() => + createProjectService({ + options: { + allowDefaultProject: ['file.js'], + defaultProject: './tsconfig.eslint.json', + }, + }), + ).toThrow( + /Could not read project service default project '\.\/tsconfig.eslint.json': .+ error TS1234: Oh no!/, + ); + }); + + it('throws an error with a local path when options.defaultProject is set to a local path and getParsedConfigFile throws a diagnostic error', () => { + mockGetParsedConfigFile.mockImplementation(() => { + throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!'); + }); + + expect(() => + createProjectService({ + options: { + allowDefaultProject: ['file.js'], + defaultProject: 'tsconfig.eslint.json', + }, + }), + ).toThrow( + /Could not read project service default project 'tsconfig.eslint.json': .+ error TS1234: Oh no!/, + ); + }); + + it('throws an error when options.defaultProject is set and getParsedConfigFile throws an environment error', () => { + mockGetParsedConfigFile.mockImplementation(() => { + throw new Error( + '`getParsedConfigFile` is only supported in a Node-like environment.', + ); + }); + + expect(() => + createProjectService({ + options: { + allowDefaultProject: ['file.js'], + defaultProject: 'tsconfig.json', + }, + }), + ).toThrow( + "Could not read project service default project 'tsconfig.json': `getParsedConfigFile` is only supported in a Node-like environment.", + ); + }); + + it('uses the default project compiler options when options.defaultProject is set and getParsedConfigFile succeeds', async () => { + const compilerOptions: ts.CompilerOptions = { strict: true }; + mockGetParsedConfigFile.mockReturnValueOnce({ + errors: [], + fileNames: [], + options: compilerOptions, + }); + + const defaultProject = 'tsconfig.eslint.json'; + + createProjectService({ + options: { + allowDefaultProject: ['file.js'], + defaultProject, + }, + }); + + expect(mockSetCompilerOptionsForInferredProjects).toHaveBeenCalledWith( + compilerOptions, + ); + + expect(mockGetParsedConfigFile).toHaveBeenCalledWith( + expect.any(Object), + defaultProject, + undefined, + ); + }); + }); + + it('uses tsconfigRootDir as getParsedConfigFile projectDirectory when provided', async () => { + const compilerOptions: ts.CompilerOptions = { strict: true }; + const tsconfigRootDir = 'path/to/repo'; + mockGetParsedConfigFile.mockReturnValueOnce({ + errors: [], + fileNames: [], + options: compilerOptions, + }); + + const { service } = createProjectService({ + options: { allowDefaultProject: ['file.js'] }, + tsconfigRootDir, + }); + + expect(service.setCompilerOptionsForInferredProjects).toHaveBeenCalledWith( + compilerOptions, + ); + + expect(mockGetParsedConfigFile).toHaveBeenCalledWith( + (await import('typescript/lib/tsserverlibrary.js')).default, + 'tsconfig.json', + tsconfigRootDir, + ); + }); + + it('uses the default projects error debugger for error messages when enabled', () => { + debug.enable('typescript-eslint:project-service:tsserver:err'); + const { service } = createProjectService(); + + service.logger.msg('foo', ts.server.Msg.Err); + + const newLocal = service.logger.loggingEnabled(); + expect(newLocal).toBe(true); + expect(processStderrWriteSpy).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:project-service:tsserver:err foo\n$/, + ), + ); + }); + + it('does not use the default projects error debugger for error messages when disabled', () => { + const { service } = createProjectService(); + + service.logger.msg('foo', ts.server.Msg.Err); + + expect(service.logger.loggingEnabled()).toBe(false); + expect(processStderrWriteSpy).not.toHaveBeenCalled(); + }); + + it('uses the default projects info debugger for info messages when enabled', () => { + debug.enable('typescript-eslint:project-service:tsserver:info'); + + const { service } = createProjectService(); + + service.logger.info('foo'); + + expect(service.logger.loggingEnabled()).toBe(true); + expect(processStderrWriteSpy).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:project-service:tsserver:info foo\n$/, + ), + ); + }); + + it('does not use the default projects info debugger for info messages when disabled', () => { + const { service } = createProjectService(); + + service.logger.info('foo'); + + expect(service.logger.loggingEnabled()).toBe(false); + expect(processStderrWriteSpy).not.toHaveBeenCalled(); + }); + + it('uses the default projects perf debugger for perf messages when enabled', () => { + debug.enable('typescript-eslint:project-service:tsserver:perf'); + const { service } = createProjectService(); + + service.logger.perftrc('foo'); + + expect(service.logger.loggingEnabled()).toBe(true); + expect(processStderrWriteSpy).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:project-service:tsserver:perf foo\n$/, + ), + ); + }); + + it('does not use the default projects perf debugger for perf messages when disabled', () => { + const { service } = createProjectService(); + + service.logger.perftrc('foo'); + + expect(service.logger.loggingEnabled()).toBe(false); + expect(processStderrWriteSpy).not.toHaveBeenCalled(); + }); + + it('enables all log levels for the default projects logger', () => { + const { service } = createProjectService(); + + 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(); + + assert.isUndefined(service.logger.getLogFileName()); + }); + + it('uses the default projects event debugger for event handling when enabled', () => { + debug.enable('typescript-eslint:project-service:tsserver:event'); + + createProjectService(); + + expect(processStderrWriteSpy).toHaveBeenCalledWith( + expect.stringMatching( + /^.*typescript-eslint:project-service:tsserver:event { eventName: 'projectLoadingStart' }\n$/, + ), + ); + }); + + it('does not use the default projects event debugger for event handling when disabled', () => { + createProjectService(); + + expect(processStderrWriteSpy).not.toHaveBeenCalled(); + }); + + it('provides a stub require to the host system when loadTypeScriptPlugins is falsy', () => { + const { service } = createProjectService({}); + + const required = service.host.require?.('', ''); + + expect(required).toStrictEqual({ + error: { + message: + 'TypeScript plugins are not required when using parserOptions.projectService.', + }, + module: undefined, + }); + }); + + it('does not provide a require to the host system when loadTypeScriptPlugins is truthy', async () => { + const { service } = createProjectService({ + options: { loadTypeScriptPlugins: true }, + }); + + expect(service.host.require).toBe( + ( + await vi.importActual>>( + 'typescript/lib/tsserverlibrary.js', + ) + ).sys.require, + ); + }); + + it('sets a host configuration', () => { + createProjectService({ + options: { allowDefaultProject: ['file.js'] }, + }); + + expect(mockSetHostConfiguration).toHaveBeenCalledWith({ + preferences: { + includePackageJsonAutoImports: 'off', + }, + }); + }); +}); diff --git a/packages/project-service/tsconfig.build.json b/packages/project-service/tsconfig.build.json new file mode 100644 index 000000000000..d9cc02f3ba7c --- /dev/null +++ b/packages/project-service/tsconfig.build.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo", + "emitDeclarationOnly": false, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../tsconfig-utils/tsconfig.build.json" + }, + { + "path": "../types/tsconfig.build.json" + } + ] +} diff --git a/packages/project-service/tsconfig.json b/packages/project-service/tsconfig.json new file mode 100644 index 000000000000..7d8d748caa17 --- /dev/null +++ b/packages/project-service/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "../tsconfig-utils" + }, + { + "path": "../types" + }, + { + "path": "./tsconfig.build.json" + }, + { + "path": "./tsconfig.tools.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/project-service/tsconfig.spec.json b/packages/project-service/tsconfig.spec.json new file mode 100644 index 000000000000..872d3c655b27 --- /dev/null +++ b/packages/project-service/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/packages/types/vitest", + "resolveJsonModule": true, + "types": ["vitest/globals", "vitest/importMeta"] + }, + "include": ["vitest.config.mts", "package.json", "tests"], + "exclude": ["**/fixtures/**"], + "references": [ + { + "path": "./tsconfig.build.json" + }, + { + "path": "../../tsconfig.spec.json" + } + ] +} diff --git a/packages/project-service/tsconfig.tools.json b/packages/project-service/tsconfig.tools.json new file mode 100644 index 000000000000..cf7f584666a9 --- /dev/null +++ b/packages/project-service/tsconfig.tools.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/packages/types/tools", + "module": "NodeNext", + "types": ["node"] + }, + "include": ["tools"], + "references": [] +} diff --git a/packages/project-service/vitest.config.mts b/packages/project-service/vitest.config.mts new file mode 100644 index 000000000000..136e4f77b392 --- /dev/null +++ b/packages/project-service/vitest.config.mts @@ -0,0 +1,22 @@ +import * as path from 'node:path'; +import { defineConfig, mergeConfig } from 'vitest/config'; + +import { vitestBaseConfig } from '../../vitest.config.base.mjs'; +import packageJson from './package.json' with { type: 'json' }; + +const vitestConfig = mergeConfig( + vitestBaseConfig, + + defineConfig({ + root: import.meta.dirname, + + test: { + dir: path.join(import.meta.dirname, 'tests'), + name: packageJson.name.replace('@typescript-eslint/', ''), + passWithNoTests: true, + root: import.meta.dirname, + }, + }), +); + +export default vitestConfig; diff --git a/packages/tsconfig-utils/LICENSE b/packages/tsconfig-utils/LICENSE new file mode 100644 index 000000000000..310a18f8a6cb --- /dev/null +++ b/packages/tsconfig-utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 typescript-eslint and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tsconfig-utils/README.md b/packages/tsconfig-utils/README.md new file mode 100644 index 000000000000..c9eb2fdff88e --- /dev/null +++ b/packages/tsconfig-utils/README.md @@ -0,0 +1,12 @@ +# `@typescript-eslint/tsconfig-utils` + +> Utilities for collecting TSConfigs for linting scenarios. + +[![NPM Version](https://img.shields.io/npm/v/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) +[![NPM Downloads](https://img.shields.io/npm/dm/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) + +The utilities in this package are separated from `@typescript-eslint/utils` so that they do not have a dependency on `eslint` or `@typescript-eslint/typescript-estree`. + +> See https://typescript-eslint.io for general documentation on typescript-eslint, the tooling that allows you to run ESLint and Prettier on TypeScript code. + + diff --git a/packages/tsconfig-utils/package.json b/packages/tsconfig-utils/package.json new file mode 100644 index 000000000000..e5f6988a872c --- /dev/null +++ b/packages/tsconfig-utils/package.json @@ -0,0 +1,61 @@ +{ + "name": "@typescript-eslint/tsconfig-utils", + "version": "8.32.0", + "description": "Utilities for collecting TSConfigs for linting scenarios.", + "files": [ + "dist", + "!*.tsbuildinfo", + "package.json", + "README.md", + "LICENSE" + ], + "type": "commonjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/tsconfig-utils" + }, + "bugs": { + "url": "https://github.com/typescript-eslint/typescript-eslint/issues" + }, + "homepage": "https://typescript-eslint.io", + "license": "MIT", + "keywords": [ + "eslint", + "typescript", + "estree" + ], + "scripts": { + "build": "tsc -b tsconfig.build.json", + "clean": "rimraf dist/ coverage/", + "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", + "lint": "npx nx lint", + "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", + "check-types": "npx nx typecheck" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + }, + "devDependencies": { + "@vitest/coverage-v8": "^3.1.2", + "eslint": "*", + "prettier": "^3.2.5", + "rimraf": "*", + "typescript": "*", + "vitest": "^3.1.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } +} diff --git a/packages/tsconfig-utils/project.json b/packages/tsconfig-utils/project.json new file mode 100644 index 000000000000..8c91c0ace943 --- /dev/null +++ b/packages/tsconfig-utils/project.json @@ -0,0 +1,16 @@ +{ + "name": "type-utils", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "root": "packages/type-utils", + "sourceRoot": "packages/type-utils/src", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vite:test" + } + } +} diff --git a/packages/tsconfig-utils/src/compilerOptions.ts b/packages/tsconfig-utils/src/compilerOptions.ts new file mode 100644 index 000000000000..1183ce149911 --- /dev/null +++ b/packages/tsconfig-utils/src/compilerOptions.ts @@ -0,0 +1,13 @@ +import type * as ts from 'typescript'; + +/** + * Compiler options required to avoid critical functionality issues + */ +export const CORE_COMPILER_OPTIONS = { + // Required to avoid parse from causing emit to occur + noEmit: true, + + // Flags required to make no-unused-vars work + noUnusedLocals: true, + noUnusedParameters: true, +} satisfies ts.CompilerOptions; diff --git a/packages/typescript-estree/src/create-program/getParsedConfigFile.ts b/packages/tsconfig-utils/src/getParsedConfigFile.ts similarity index 94% rename from packages/typescript-estree/src/create-program/getParsedConfigFile.ts rename to packages/tsconfig-utils/src/getParsedConfigFile.ts index 6efebadf48d2..a6dafe5b1daa 100644 --- a/packages/typescript-estree/src/create-program/getParsedConfigFile.ts +++ b/packages/tsconfig-utils/src/getParsedConfigFile.ts @@ -3,10 +3,10 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; import * as fs from 'node:fs'; import * as path from 'node:path'; -import { CORE_COMPILER_OPTIONS } from './shared'; +import { CORE_COMPILER_OPTIONS } from './compilerOptions'; /** - * Utility offered by parser to help consumers parse a config file. + * Parses a TSConfig file using the same logic as tsserver. * * @param configFile the path to the tsconfig.json file, relative to `projectDirectory` * @param projectDirectory the project directory to use as the CWD, defaults to `process.cwd()` diff --git a/packages/tsconfig-utils/src/index.ts b/packages/tsconfig-utils/src/index.ts new file mode 100644 index 000000000000..3213fc4e40a1 --- /dev/null +++ b/packages/tsconfig-utils/src/index.ts @@ -0,0 +1,2 @@ +export * from './compilerOptions'; +export * from './getParsedConfigFile'; diff --git a/packages/typescript-estree/tests/lib/getParsedConfigFile.test.ts b/packages/tsconfig-utils/tests/lib/getParsedConfigFile.test.ts similarity index 97% rename from packages/typescript-estree/tests/lib/getParsedConfigFile.test.ts rename to packages/tsconfig-utils/tests/lib/getParsedConfigFile.test.ts index aa013abef444..d26919b52b5c 100644 --- a/packages/typescript-estree/tests/lib/getParsedConfigFile.test.ts +++ b/packages/tsconfig-utils/tests/lib/getParsedConfigFile.test.ts @@ -1,7 +1,7 @@ import path from 'node:path'; import * as ts from 'typescript'; -import { getParsedConfigFile } from '../../src/create-program/getParsedConfigFile'; +import { getParsedConfigFile } from '../../src/getParsedConfigFile'; const mockGetParsedCommandLineOfConfigFile = vi.fn(); diff --git a/packages/tsconfig-utils/tsconfig.build.json b/packages/tsconfig-utils/tsconfig.build.json new file mode 100644 index 000000000000..2250b25126e4 --- /dev/null +++ b/packages/tsconfig-utils/tsconfig.build.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo", + "emitDeclarationOnly": false, + "types": ["node"] + }, + "include": ["src/**/*.ts", "typings"], + "exclude": ["vitest.config.mts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "references": [] +} diff --git a/packages/tsconfig-utils/tsconfig.json b/packages/tsconfig-utils/tsconfig.json new file mode 100644 index 000000000000..d4d0929e1955 --- /dev/null +++ b/packages/tsconfig-utils/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.build.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/tsconfig-utils/tsconfig.spec.json b/packages/tsconfig-utils/tsconfig.spec.json new file mode 100644 index 000000000000..7ec995aaf7dc --- /dev/null +++ b/packages/tsconfig-utils/tsconfig.spec.json @@ -0,0 +1,26 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/packages/type-utils", + "module": "NodeNext", + "resolveJsonModule": true, + "types": ["node", "vitest/globals", "vitest/importMeta"] + }, + "include": [ + "vitest.config.mts", + "package.json", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts", + "tests" + ], + "exclude": ["**/fixtures/**"], + "references": [ + { + "path": "./tsconfig.build.json" + }, + { + "path": "../../tsconfig.spec.json" + } + ] +} diff --git a/packages/tsconfig-utils/vitest.config.mts b/packages/tsconfig-utils/vitest.config.mts new file mode 100644 index 000000000000..9cadb9dea2bb --- /dev/null +++ b/packages/tsconfig-utils/vitest.config.mts @@ -0,0 +1,26 @@ +import * as path from 'node:path'; +import { defineProject, mergeConfig } from 'vitest/config'; + +import { vitestBaseConfig } from '../../vitest.config.base.mjs'; +import packageJson from './package.json' with { type: 'json' }; + +const vitestConfig = mergeConfig( + vitestBaseConfig, + + defineProject({ + root: import.meta.dirname, + + test: { + diff: { + maxDepth: 1, + }, + + dir: path.join(import.meta.dirname, 'tests'), + name: packageJson.name.replace('@typescript-eslint/', ''), + root: import.meta.dirname, + testTimeout: 10_000, + }, + }), +); + +export default vitestConfig; diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index b992b01f0d7e..d681b00969e0 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -52,6 +52,8 @@ "check-types": "npx nx typecheck" }, "dependencies": { + "@typescript-eslint/project-service": "8.32.0", + "@typescript-eslint/tsconfig-utils": "8.32.0", "@typescript-eslint/types": "8.32.0", "@typescript-eslint/visitor-keys": "8.32.0", "debug": "^4.3.4", diff --git a/packages/typescript-estree/src/create-program/shared.ts b/packages/typescript-estree/src/create-program/shared.ts index 07a67cbb9b38..088e5ce1c7dd 100644 --- a/packages/typescript-estree/src/create-program/shared.ts +++ b/packages/typescript-estree/src/create-program/shared.ts @@ -1,5 +1,6 @@ import type { Program } from 'typescript'; +import { CORE_COMPILER_OPTIONS } from '@typescript-eslint/tsconfig-utils'; import path from 'node:path'; import * as ts from 'typescript'; @@ -15,19 +16,6 @@ export interface ASTAndDefiniteProgram { } export type ASTAndProgram = ASTAndDefiniteProgram | ASTAndNoProgram; -/** - * Compiler options required to avoid critical functionality issues - */ -export const CORE_COMPILER_OPTIONS: ts.CompilerOptions = { - noEmit: true, // required to avoid parse from causing emit to occur - - /** - * Flags required to make no-unused-vars work - */ - noUnusedLocals: true, - noUnusedParameters: true, -}; - /** * Default compiler options for program generation */ diff --git a/packages/typescript-estree/src/create-program/useProvidedPrograms.ts b/packages/typescript-estree/src/create-program/useProvidedPrograms.ts index f84767ea19c7..b8e958ed34f2 100644 --- a/packages/typescript-estree/src/create-program/useProvidedPrograms.ts +++ b/packages/typescript-estree/src/create-program/useProvidedPrograms.ts @@ -1,3 +1,4 @@ +import { getParsedConfigFile } from '@typescript-eslint/tsconfig-utils'; import debug from 'debug'; import * as path from 'node:path'; import * as ts from 'typescript'; @@ -5,7 +6,6 @@ import * as ts from 'typescript'; import type { ParseSettings } from '../parseSettings'; import type { ASTAndDefiniteProgram } from './shared'; -import { getParsedConfigFile } from './getParsedConfigFile'; import { getAstFromProgram } from './shared'; const log = debug( diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 6a1c42008c6b..d522f880e55a 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,12 +1,17 @@ +import type { + CreateProjectServiceSettings, + ProjectServiceAndMetadata, +} from '@typescript-eslint/project-service'; + +import { createProjectService } from '@typescript-eslint/project-service'; import debug from 'debug'; import path from 'node:path'; import * as ts from 'typescript'; -import type { ProjectServiceSettings } from '../create-program/createProjectService'; +import { validateDefaultProjectForFilesGlob } from '../create-program/validateDefaultProjectForFilesGlob'; import type { TSESTreeOptions } from '../parser-options'; import type { MutableParseSettings } from './index'; -import { createProjectService } from '../create-program/createProjectService'; import { ensureAbsolutePath } from '../create-program/shared'; import { isSourceFile } from '../source-files'; import { @@ -17,13 +22,14 @@ import { getProjectConfigFiles } from './getProjectConfigFiles'; import { inferSingleRun } from './inferSingleRun'; import { resolveProjectList } from './resolveProjectList'; import { warnAboutTSVersion } from './warnAboutTSVersion'; +import { ProjectServiceOptions } from '@typescript-eslint/types'; const log = debug( 'typescript-eslint:typescript-estree:parseSettings:createParseSettings', ); let TSCONFIG_MATCH_CACHE: ExpiringCache | null; -let TSSERVER_PROJECT_SERVICE: ProjectServiceSettings | null = null; +let TSSERVER_PROJECT_SERVICE: ProjectServiceAndMetadata | null = null; // NOTE - we intentionally use "unnecessary" `?.` here because in TS<5.3 this enum doesn't exist // This object exists so we can centralize these for tracking and so we don't proliferate these across the file @@ -112,11 +118,10 @@ export function createParseSettings( (tsestreeOptions.project && tsestreeOptions.projectService !== false && process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE === 'true') - ? (TSSERVER_PROJECT_SERVICE ??= createProjectService( - tsestreeOptions.projectService, + ? populateProjectService(tsestreeOptions.projectService, { jsDocParsingMode, tsconfigRootDir, - )) + }) : undefined, setExternalModuleIndicator: tsestreeOptions.sourceType === 'module' || @@ -223,3 +228,19 @@ function enforceCodeString(code: unknown): string { function getFileName(jsx?: boolean): string { return jsx ? 'estree.tsx' : 'estree.ts'; } + +function populateProjectService( + optionsRaw: ProjectServiceOptions | true | undefined, + settings: CreateProjectServiceSettings, +) { + const options = typeof optionsRaw === 'object' ? optionsRaw : {}; + + validateDefaultProjectForFilesGlob(options.allowDefaultProject); + + TSSERVER_PROJECT_SERVICE ??= createProjectService({ + options, + ...settings, + }); + + return TSSERVER_PROJECT_SERVICE; +} diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 1ea208210e0a..f4d771a8a4bb 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -1,6 +1,6 @@ +import type { ProjectServiceAndMetadata } from '@typescript-eslint/project-service'; import type * as ts from 'typescript'; -import type { ProjectServiceSettings } from '../create-program/createProjectService'; import type { CanonicalPath } from '../create-program/shared'; import type { TSESTree } from '../ts-estree'; import type { CacheLike } from './ExpiringCache'; @@ -120,7 +120,7 @@ export interface MutableParseSettings { /** * TypeScript server to power program creation. */ - projectService: ProjectServiceSettings | undefined; + projectService: ProjectServiceAndMetadata | undefined; /** * Whether to add the `range` property to AST nodes. diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index d3f8c8649fc5..a1841a828d48 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -9,8 +9,6 @@ import type * as ts from 'typescript'; import type { TSESTree, TSESTreeToTSNode, TSNode, TSToken } from './ts-estree'; -export type { ProjectServiceOptions } from '@typescript-eslint/types'; - ////////////////////////////////////////////////////////// // MAKE SURE THIS IS KEPT IN SYNC WITH THE WEBSITE DOCS // ////////////////////////////////////////////////////////// diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index cf98e5523921..9a046ee2c907 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -1,10 +1,11 @@ +import type { ProjectServiceAndMetadata as ProjectServiceAndMetadata } from '@typescript-eslint/project-service'; + import debug from 'debug'; import { minimatch } from 'minimatch'; import path from 'node:path'; import util from 'node:util'; import * as ts from 'typescript'; -import type { ProjectServiceSettings } from './create-program/createProjectService'; import type { ASTAndDefiniteProgram, ASTAndNoProgram, @@ -55,7 +56,7 @@ function openClientFileFromProjectService( isDefaultProjectAllowed: boolean, filePathAbsolute: string, parseSettings: Readonly, - serviceSettings: ProjectServiceSettings, + serviceAndSettings: ProjectServiceAndMetadata, ): ts.server.OpenConfiguredProjectResult { const opened = openClientFileAndMaybeReload(); @@ -107,7 +108,7 @@ function openClientFileFromProjectService( defaultProjectMatchedFiles.add(filePathAbsolute); if ( defaultProjectMatchedFiles.size > - serviceSettings.maximumDefaultProjectFileMatchCount + serviceAndSettings.maximumDefaultProjectFileMatchCount ) { const filePrintLimit = 20; const filesToPrint = [...defaultProjectMatchedFiles].slice( @@ -118,7 +119,7 @@ function openClientFileFromProjectService( defaultProjectMatchedFiles.size - filesToPrint.length; throw new Error( - `Too many files (>${serviceSettings.maximumDefaultProjectFileMatchCount}) have matched the default project.${DEFAULT_PROJECT_FILES_ERROR_EXPLANATION} + `Too many files (>${serviceAndSettings.maximumDefaultProjectFileMatchCount}) have matched the default project.${DEFAULT_PROJECT_FILES_ERROR_EXPLANATION} Matching files: ${filesToPrint.map(file => `- ${file}`).join('\n')} ${truncatedFileCount ? `...and ${truncatedFileCount} more files\n` : ''} @@ -131,7 +132,7 @@ If you absolutely need more files included, set parserOptions.projectService.max return opened; function openClientFile(): ts.server.OpenConfiguredProjectResult { - return serviceSettings.service.openClientFile( + return serviceAndSettings.service.openClientFile( filePathAbsolute, parseSettings.codeFullText, /* scriptKind */ undefined, @@ -152,13 +153,13 @@ If you absolutely need more files included, set parserOptions.projectService.max !opened.configFileName && !parseSettings.singleRun && !isDefaultProjectAllowed && - performance.now() - serviceSettings.lastReloadTimestamp > + performance.now() - serviceAndSettings.lastReloadTimestamp > RELOAD_THROTTLE_MS ) { log('No config file found; reloading project service and retrying.'); - serviceSettings.service.reloadProjects(); + serviceAndSettings.service.reloadProjects(); opened = openClientFile(); - serviceSettings.lastReloadTimestamp = performance.now(); + serviceAndSettings.lastReloadTimestamp = performance.now(); } return opened; @@ -192,13 +193,13 @@ function createNoProgramWithProjectService( function retrieveASTAndProgramFor( filePathAbsolute: string, parseSettings: Readonly, - serviceSettings: ProjectServiceSettings, + serviceAndSettings: ProjectServiceAndMetadata, ): ASTAndDefiniteProgram | undefined { log('Retrieving script info and then program for: %s', filePathAbsolute); - const scriptInfo = serviceSettings.service.getScriptInfo(filePathAbsolute); + const scriptInfo = serviceAndSettings.service.getScriptInfo(filePathAbsolute); /* eslint-disable @typescript-eslint/no-non-null-assertion */ - const program = serviceSettings.service + const program = serviceAndSettings.service .getDefaultProjectForFile(scriptInfo!.fileName, true)! .getLanguageService(/*ensureSynchronized*/ true) .getProgram(); @@ -215,38 +216,41 @@ function retrieveASTAndProgramFor( } export function useProgramFromProjectService( - settings: ProjectServiceSettings, + serviceAndSettings: ProjectServiceAndMetadata, parseSettings: Readonly, hasFullTypeInformation: boolean, defaultProjectMatchedFiles: Set, ): ASTAndProgram | undefined; export function useProgramFromProjectService( - settings: ProjectServiceSettings, + serviceAndSettings: ProjectServiceAndMetadata, parseSettings: Readonly, hasFullTypeInformation: true, defaultProjectMatchedFiles: Set, ): ASTAndDefiniteProgram | undefined; export function useProgramFromProjectService( - settings: ProjectServiceSettings, + serviceAndSettings: ProjectServiceAndMetadata, parseSettings: Readonly, hasFullTypeInformation: false, defaultProjectMatchedFiles: Set, ): ASTAndNoProgram | undefined; export function useProgramFromProjectService( - serviceSettings: ProjectServiceSettings, + serviceAndSettings: ProjectServiceAndMetadata, parseSettings: Readonly, hasFullTypeInformation: boolean, defaultProjectMatchedFiles: Set, ): ASTAndProgram | undefined { // NOTE: triggers a full project reload when changes are detected updateExtraFileExtensions( - serviceSettings.service, + serviceAndSettings.service, parseSettings.extraFileExtensions, ); // We don't canonicalize the filename because it caused a performance regression. // See https://github.com/typescript-eslint/typescript-eslint/issues/8519 - const filePathAbsolute = absolutify(parseSettings.filePath, serviceSettings); + const filePathAbsolute = absolutify( + parseSettings.filePath, + serviceAndSettings, + ); log( 'Opening project service file for: %s at absolute path %s', parseSettings.filePath, @@ -259,7 +263,7 @@ export function useProgramFromProjectService( ); const isDefaultProjectAllowed = filePathMatchedBy( filePathRelative, - serviceSettings.allowDefaultProject, + serviceAndSettings.allowDefaultProject, ); // Type-aware linting is disabled for this file. @@ -268,7 +272,7 @@ export function useProgramFromProjectService( return createNoProgramWithProjectService( filePathAbsolute, parseSettings, - serviceSettings.service, + serviceAndSettings.service, ); } @@ -284,7 +288,7 @@ export function useProgramFromProjectService( isDefaultProjectAllowed, filePathAbsolute, parseSettings, - serviceSettings, + serviceAndSettings, ); log('Opened project service file: %o', opened); @@ -292,17 +296,20 @@ export function useProgramFromProjectService( return retrieveASTAndProgramFor( filePathAbsolute, parseSettings, - serviceSettings, + serviceAndSettings, ); } function absolutify( filePath: string, - serviceSettings: ProjectServiceSettings, + serviceAndSettings: ProjectServiceAndMetadata, ): string { return path.isAbsolute(filePath) ? filePath - : path.join(serviceSettings.service.host.getCurrentDirectory(), filePath); + : path.join( + serviceAndSettings.service.host.getCurrentDirectory(), + filePath, + ); } function filePathMatchedBy( diff --git a/packages/typescript-estree/tests/lib/createParseSettings.test.ts b/packages/typescript-estree/tests/lib/createParseSettings.test.ts index 846210391076..ccb210fd8dbd 100644 --- a/packages/typescript-estree/tests/lib/createParseSettings.test.ts +++ b/packages/typescript-estree/tests/lib/createParseSettings.test.ts @@ -2,8 +2,8 @@ import { createParseSettings } from '../../src/parseSettings/createParseSettings const projectService = { service: true }; -vi.mock('../../src/create-program/createProjectService.js', () => ({ - createProjectService: (): typeof projectService => projectService, +vi.mock('@typescript-eslint/project-service', () => ({ + createProjectService: () => projectService, })); describe(createParseSettings, () => { diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts deleted file mode 100644 index af67dd38a024..000000000000 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ /dev/null @@ -1,379 +0,0 @@ -import debug from 'debug'; -import * as ts from 'typescript'; - -import { createProjectService } from '../../src/create-program/createProjectService.js'; -import { getParsedConfigFile } from '../../src/create-program/getParsedConfigFile.js'; - -const mockGetParsedConfigFile = vi.mocked(getParsedConfigFile); - -vi.mock( - import('../../src/create-program/getParsedConfigFile.js'), - async importOriginal => { - const actual = await importOriginal(); - - return { - ...actual, - getParsedConfigFile: vi.fn(actual.getParsedConfigFile), - }; - }, -); - -vi.mock( - import('../../src/create-program/createProjectService.js'), - async importOriginal => { - const actual = await importOriginal(); - - vi.spyOn( - ts.server.ProjectService.prototype, - 'setCompilerOptionsForInferredProjects', - ); - - vi.spyOn(ts.server.ProjectService.prototype, 'setHostConfiguration'); - - return { - ...actual, - createProjectService: vi - .fn(actual.createProjectService) - .mockImplementation((...args) => { - const projectServiceSettings = actual.createProjectService(...args); - const service = - projectServiceSettings.service as typeof projectServiceSettings.service & { - eventHandler: ts.server.ProjectServiceEventHandler | undefined; - }; - - if (service.eventHandler) { - service.eventHandler({ - eventName: ts.server.ProjectLoadingStartEvent, - } as ts.server.ProjectLoadingStartEvent); - } - - return projectServiceSettings; - }), - }; - }, -); - -describe(createProjectService, () => { - const processStderrWriteSpy = vi - .spyOn(process.stderr, 'write') - .mockImplementation(() => true); - - beforeEach(() => { - mockGetParsedConfigFile.mockReturnValue({ - errors: [], - fileNames: [], - options: {}, - }); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - afterAll(() => { - vi.restoreAllMocks(); - }); - - it('sets allowDefaultProject when options.allowDefaultProject is defined', () => { - const allowDefaultProject = ['./*.js']; - const settings = createProjectService( - { allowDefaultProject }, - undefined, - undefined, - ); - - expect(settings.allowDefaultProject).toBe(allowDefaultProject); - }); - - it('does not set allowDefaultProject when options.allowDefaultProject is not defined', () => { - const settings = createProjectService(undefined, undefined, undefined); - - assert.isUndefined(settings.allowDefaultProject); - }); - - it('does not throw an error when options.defaultProject is not provided and getParsedConfigFile throws a diagnostic error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error('tsconfig.json(1,1): error TS1234: Oh no!'); - }); - - expect(() => - createProjectService( - { - allowDefaultProject: ['file.js'], - }, - undefined, - undefined, - ), - ).not.toThrow(); - }); - - it('throws an error with a relative path when options.defaultProject is set to a relative path and getParsedConfigFile throws a diagnostic error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!'); - }); - - expect(() => - createProjectService( - { - allowDefaultProject: ['file.js'], - defaultProject: './tsconfig.eslint.json', - }, - undefined, - undefined, - ), - ).toThrow( - /Could not read project service default project '\.\/tsconfig.eslint.json': .+ error TS1234: Oh no!/, - ); - }); - - it('throws an error with a local path when options.defaultProject is set to a local path and getParsedConfigFile throws a diagnostic error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!'); - }); - - expect(() => - createProjectService( - { - allowDefaultProject: ['file.js'], - defaultProject: 'tsconfig.eslint.json', - }, - undefined, - undefined, - ), - ).toThrow( - /Could not read project service default project 'tsconfig.eslint.json': .+ error TS1234: Oh no!/, - ); - }); - - it('throws an error when options.defaultProject is set and getParsedConfigFile throws an environment error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error( - '`getParsedConfigFile` is only supported in a Node-like environment.', - ); - }); - - expect(() => - createProjectService( - { - allowDefaultProject: ['file.js'], - defaultProject: 'tsconfig.json', - }, - undefined, - undefined, - ), - ).toThrow( - "Could not read project service default project 'tsconfig.json': `getParsedConfigFile` is only supported in a Node-like environment.", - ); - }); - - it('uses the default project compiler options when options.defaultProject is set and getParsedConfigFile succeeds', async () => { - const compilerOptions: ts.CompilerOptions = { strict: true }; - mockGetParsedConfigFile.mockReturnValueOnce({ - errors: [], - fileNames: [], - options: compilerOptions, - }); - - const defaultProject = 'tsconfig.eslint.json'; - - const { service } = createProjectService( - { - allowDefaultProject: ['file.js'], - defaultProject, - }, - undefined, - undefined, - ); - - expect( - service.setCompilerOptionsForInferredProjects, - ).toHaveBeenCalledExactlyOnceWith(compilerOptions); - - expect(mockGetParsedConfigFile).toHaveBeenCalledExactlyOnceWith( - (await import('typescript/lib/tsserverlibrary.js')).default, - defaultProject, - undefined, - ); - }); - - it('uses tsconfigRootDir as getParsedConfigFile projectDirectory when provided', async () => { - const compilerOptions: ts.CompilerOptions = { strict: true }; - const tsconfigRootDir = 'path/to/repo'; - mockGetParsedConfigFile.mockReturnValueOnce({ - errors: [], - fileNames: [], - options: compilerOptions, - }); - - const { service } = createProjectService( - { - allowDefaultProject: ['file.js'], - }, - undefined, - tsconfigRootDir, - ); - - expect( - service.setCompilerOptionsForInferredProjects, - ).toHaveBeenCalledExactlyOnceWith(compilerOptions); - - expect(mockGetParsedConfigFile).toHaveBeenCalledExactlyOnceWith( - (await import('typescript/lib/tsserverlibrary.js')).default, - 'tsconfig.json', - tsconfigRootDir, - ); - }); - - it('uses the default projects error debugger for error messages when enabled', () => { - const { service } = createProjectService(undefined, 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(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( - expect.stringMatching( - /^.*typescript-eslint:typescript-estree:tsserver:err foo\n$/, - ), - ); - }); - - it('does not use the default projects error debugger for error messages when disabled', () => { - const { service } = createProjectService(undefined, undefined, undefined); - const enabled = service.logger.loggingEnabled(); - service.logger.msg('foo', ts.server.Msg.Err); - - expect(enabled).toBe(false); - expect(processStderrWriteSpy).not.toHaveBeenCalled(); - }); - - it('uses the default projects info debugger for info messages when enabled', () => { - const { service } = createProjectService(undefined, 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(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( - expect.stringMatching( - /^.*typescript-eslint:typescript-estree:tsserver:info foo\n$/, - ), - ); - }); - - it('does not use the default projects info debugger for info messages when disabled', () => { - const { service } = createProjectService(undefined, undefined, undefined); - const enabled = service.logger.loggingEnabled(); - service.logger.info('foo'); - - expect(enabled).toBe(false); - expect(processStderrWriteSpy).not.toHaveBeenCalled(); - }); - - it('uses the default projects perf debugger for perf messages when enabled', () => { - const { service } = createProjectService(undefined, 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(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( - expect.stringMatching( - /^.*typescript-eslint:typescript-estree:tsserver:perf foo\n$/, - ), - ); - }); - - it('does not use the default projects perf debugger for perf messages when disabled', () => { - const { service } = createProjectService(undefined, undefined, undefined); - const enabled = service.logger.loggingEnabled(); - service.logger.perftrc('foo'); - - expect(enabled).toBe(false); - expect(processStderrWriteSpy).not.toHaveBeenCalled(); - }); - - it('enables all log levels for the default projects logger', () => { - const { service } = createProjectService(undefined, 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, undefined); - - assert.isUndefined(service.logger.getLogFileName()); - }); - - it('uses the default projects event debugger for event handling when enabled', () => { - debug.enable('typescript-eslint:typescript-estree:tsserver:event'); - createProjectService(undefined, undefined, undefined); - debug.disable(); - - expect(processStderrWriteSpy).toHaveBeenCalledExactlyOnceWith( - 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', () => { - createProjectService(undefined, undefined, undefined); - - expect(processStderrWriteSpy).not.toHaveBeenCalled(); - }); - - it('provides a stub require to the host system when loadTypeScriptPlugins is falsy', () => { - const { service } = createProjectService({}, undefined, undefined); - - const required = service.host.require?.('', ''); - - expect(required).toStrictEqual({ - error: { - message: - 'TypeScript plugins are not required when using parserOptions.projectService.', - }, - module: undefined, - }); - }); - - it('does not provide a require to the host system when loadTypeScriptPlugins is truthy', async () => { - const { service } = createProjectService( - { - loadTypeScriptPlugins: true, - }, - undefined, - undefined, - ); - - expect(service.host.require).toBe( - ( - await vi.importActual>>( - 'typescript/lib/tsserverlibrary.js', - ) - ).sys.require, - ); - }); - - it('sets a host configuration', () => { - const { service } = createProjectService( - { - allowDefaultProject: ['file.js'], - }, - undefined, - undefined, - ); - - expect(service.setHostConfiguration).toHaveBeenCalledExactlyOnceWith({ - preferences: { - includePackageJsonAutoImports: 'off', - }, - }); - }); -}); diff --git a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts index c156392d3693..0d57007b90c5 100644 --- a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts @@ -2,9 +2,9 @@ import path from 'node:path'; import * as ts from 'typescript'; import type { - ProjectServiceSettings, + ProjectServiceAndMetadata, TypeScriptProjectService, -} from '../../src/create-program/createProjectService'; +} from '@typescript-eslint/project-service'; import type { ParseSettings } from '../../src/parseSettings'; import { useProgramFromProjectService } from '../../src/useProgramFromProjectService'; @@ -65,7 +65,7 @@ const mockParseSettings = { } as ParseSettings; const createProjectServiceSettings = < - T extends Partial, + T extends Partial, >( settings: T, ) => ({ diff --git a/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts b/packages/typescript-estree/tests/validateDefaultProjectForFilesGlob.test.ts similarity index 88% rename from packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts rename to packages/typescript-estree/tests/validateDefaultProjectForFilesGlob.test.ts index 9eccb60f8cc1..df8d59842f18 100644 --- a/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts +++ b/packages/typescript-estree/tests/validateDefaultProjectForFilesGlob.test.ts @@ -1,4 +1,4 @@ -import { validateDefaultProjectForFilesGlob } from '../../src/create-program/validateDefaultProjectForFilesGlob'; +import { validateDefaultProjectForFilesGlob } from '../src/create-program/validateDefaultProjectForFilesGlob.js'; describe(validateDefaultProjectForFilesGlob, () => { it('does not throw when options.allowDefaultProject is an empty array', () => { diff --git a/packages/typescript-estree/tsconfig.build.json b/packages/typescript-estree/tsconfig.build.json index 4ffc7ff2cc59..d497b8b60b4e 100644 --- a/packages/typescript-estree/tsconfig.build.json +++ b/packages/typescript-estree/tsconfig.build.json @@ -11,6 +11,12 @@ "include": ["src/**/*.ts", "typings"], "exclude": ["vitest.config.mts", "src/**/*.spec.ts", "src/**/*.test.ts"], "references": [ + { + "path": "../project-service/tsconfig.build.json" + }, + { + "path": "../tsconfig-utils/tsconfig.build.json" + }, { "path": "../visitor-keys/tsconfig.build.json" }, diff --git a/packages/typescript-estree/tsconfig.json b/packages/typescript-estree/tsconfig.json index 7b899170369f..77cec8e6ba10 100644 --- a/packages/typescript-estree/tsconfig.json +++ b/packages/typescript-estree/tsconfig.json @@ -3,6 +3,12 @@ "files": [], "include": [], "references": [ + { + "path": "../project-service" + }, + { + "path": "../tsconfig-utils" + }, { "path": "../visitor-keys" }, diff --git a/packages/website-eslint/build.mts b/packages/website-eslint/build.mts index 4c445e809ff5..0dea229911d0 100644 --- a/packages/website-eslint/build.mts +++ b/packages/website-eslint/build.mts @@ -93,6 +93,7 @@ async function buildPackage(name: string, file: string): Promise { setup(build): void { build.onLoad( makeFilter([ + '/getParsedConfigFile.ts', '/ts-eslint/ESLint.ts', '/ts-eslint/RuleTester.ts', '/ts-eslint/CLIEngine.ts', diff --git a/packages/website/docusaurus.config.mts b/packages/website/docusaurus.config.mts index 338a013d947e..246c42c4e936 100644 --- a/packages/website/docusaurus.config.mts +++ b/packages/website/docusaurus.config.mts @@ -344,28 +344,32 @@ const config: Config = { onBrokenMarkdownLinks: 'throw', organizationName: 'typescript-eslint', plugins: [ - ...['ast-spec', 'type-utils'].map(packageName => [ - 'docusaurus-plugin-typedoc', - { - entryPoints: [`../${packageName}/src/index.ts`], - enumMembersFormat: 'table', - exclude: '**/*.d.ts', - excludeExternals: true, - groupOrder: ['Functions', 'Variables', '*'], - hidePageTitle: true, - id: `typedoc-generated-${packageName}`, - indexFormat: 'table', - out: `../../docs/packages/${packageName}/generated`, - outputFileStrategy: 'modules', - parametersFormat: 'table', - plugin: [require.resolve('./tools/typedoc-plugin-no-inherit-fork.mjs')], - propertiesFormat: 'table', - readme: 'none', - tsconfig: `../${packageName}/tsconfig.json`, - typeDeclarationFormat: 'table', - useCodeBlocks: true, - }, - ]), + ...['ast-spec', 'project-service', 'tsconfig-utils', 'type-utils'].map( + packageName => [ + 'docusaurus-plugin-typedoc', + { + entryPoints: [`../${packageName}/src/index.ts`], + enumMembersFormat: 'table', + exclude: '**/*.d.ts', + excludeExternals: true, + groupOrder: ['Functions', 'Variables', '*'], + hidePageTitle: true, + id: `typedoc-generated-${packageName}`, + indexFormat: 'table', + out: `../../docs/packages/${packageName}/generated`, + outputFileStrategy: 'modules', + parametersFormat: 'table', + plugin: [ + require.resolve('./tools/typedoc-plugin-no-inherit-fork.mjs'), + ], + propertiesFormat: 'table', + readme: 'none', + tsconfig: `../${packageName}/tsconfig.json`, + typeDeclarationFormat: 'table', + useCodeBlocks: true, + }, + ], + ), require.resolve('./webpack.plugin'), ['@docusaurus/plugin-content-docs', pluginContentDocsOptions], ['@docusaurus/plugin-pwa', pluginPwaOptions], diff --git a/packages/website/sidebars/sidebar.base.js b/packages/website/sidebars/sidebar.base.js index b0d55ed91ffb..689a5bb14202 100644 --- a/packages/website/sidebars/sidebar.base.js +++ b/packages/website/sidebars/sidebar.base.js @@ -103,8 +103,10 @@ module.exports = { 'packages/eslint-plugin', 'packages/eslint-plugin-tslint', 'packages/parser', + 'packages/project-service', 'packages/rule-tester', 'packages/scope-manager', + 'packages/tsconfig-utils', { collapsible: false, items: ['packages/type-utils/type-or-value-specifier'], diff --git a/tsconfig.json b/tsconfig.json index 7b8fdb3fe33b..7e4513d0596a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ { "path": "./packages/eslint-plugin-internal" }, { "path": "./packages/integration-tests" }, { "path": "./packages/parser" }, + { "path": "./packages/project-service" }, { "path": "./packages/rule-schema-to-typescript-types" }, { "path": "./packages/rule-tester" }, { "path": "./packages/scope-manager" }, @@ -19,6 +20,7 @@ { "path": "./packages/visitor-keys" }, { "path": "./packages/website" }, { "path": "./packages/website-eslint" }, + { "path": "./packages/tsconfig-utils" }, { "path": "./tsconfig.repo-config-files.json" } ] } diff --git a/yarn.lock b/yarn.lock index 1bbd20ef8cae..38e99ef1b6bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5406,6 +5406,20 @@ __metadata: languageName: unknown linkType: soft +"@typescript-eslint/project-service@8.32.0, @typescript-eslint/project-service@workspace:packages/project-service": + version: 0.0.0-use.local + resolution: "@typescript-eslint/project-service@workspace:packages/project-service" + dependencies: + "@typescript-eslint/types": ^8.32.0 + "@vitest/coverage-v8": ^3.1.2 + prettier: ^3.2.5 + rimraf: "*" + tsx: "*" + typescript: "*" + vitest: ^3.1.2 + languageName: unknown + linkType: soft + "@typescript-eslint/rule-schema-to-typescript-types@8.32.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types" @@ -5465,6 +5479,21 @@ __metadata: languageName: unknown linkType: soft +"@typescript-eslint/tsconfig-utils@8.32.0, @typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils": + version: 0.0.0-use.local + resolution: "@typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils" + dependencies: + "@vitest/coverage-v8": ^3.1.2 + eslint: "*" + prettier: ^3.2.5 + rimraf: "*" + typescript: "*" + vitest: ^3.1.2 + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + languageName: unknown + linkType: soft + "@typescript-eslint/type-utils@8.32.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/type-utils@workspace:packages/type-utils" @@ -5487,7 +5516,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/types@8.32.0, @typescript-eslint/types@^8.9.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": +"@typescript-eslint/types@8.32.0, @typescript-eslint/types@^8.32.0, @typescript-eslint/types@^8.9.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@typescript-eslint/types@workspace:packages/types" dependencies: @@ -5576,6 +5605,8 @@ __metadata: resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: "@types/is-glob": ^4.0.4 + "@typescript-eslint/project-service": 8.32.0 + "@typescript-eslint/tsconfig-utils": 8.32.0 "@typescript-eslint/types": 8.32.0 "@typescript-eslint/visitor-keys": 8.32.0 "@vitest/coverage-v8": ^3.1.2 From 267055369413a6c28cdacb4657c66b478b8fbdcb Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 6 May 2025 15:08:45 -0400 Subject: [PATCH 02/20] fix: add missing dependency on tsconfig-utils --- packages/project-service/package.json | 1 + yarn.lock | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/project-service/package.json b/packages/project-service/package.json index 946e2e677899..6a41d21eabbc 100644 --- a/packages/project-service/package.json +++ b/packages/project-service/package.json @@ -46,6 +46,7 @@ "check-types": "npx nx typecheck" }, "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.32.0", "@typescript-eslint/types": "^8.32.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 38e99ef1b6bd..b3e1b4dac1b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5410,6 +5410,7 @@ __metadata: version: 0.0.0-use.local resolution: "@typescript-eslint/project-service@workspace:packages/project-service" dependencies: + "@typescript-eslint/tsconfig-utils": ^8.32.0 "@typescript-eslint/types": ^8.32.0 "@vitest/coverage-v8": ^3.1.2 prettier: ^3.2.5 @@ -5479,7 +5480,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/tsconfig-utils@8.32.0, @typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils": +"@typescript-eslint/tsconfig-utils@8.32.0, @typescript-eslint/tsconfig-utils@^8.32.0, @typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils" dependencies: From 2fe82d93d7d782ed0248fdcbb9d7ca4a7781f718 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 6 May 2025 15:33:25 -0400 Subject: [PATCH 03/20] chore: correct for Knip reports --- packages/project-service/package.json | 1 - packages/tsconfig-utils/package.json | 1 - .../tests/{ => lib}/validateDefaultProjectForFilesGlob.test.ts | 2 +- yarn.lock | 2 -- 4 files changed, 1 insertion(+), 5 deletions(-) rename packages/typescript-estree/tests/{ => lib}/validateDefaultProjectForFilesGlob.test.ts (87%) diff --git a/packages/project-service/package.json b/packages/project-service/package.json index 6a41d21eabbc..02a9004d8896 100644 --- a/packages/project-service/package.json +++ b/packages/project-service/package.json @@ -53,7 +53,6 @@ "@vitest/coverage-v8": "^3.1.2", "prettier": "^3.2.5", "rimraf": "*", - "tsx": "*", "typescript": "*", "vitest": "^3.1.2" }, diff --git a/packages/tsconfig-utils/package.json b/packages/tsconfig-utils/package.json index e5f6988a872c..352219f66a3a 100644 --- a/packages/tsconfig-utils/package.json +++ b/packages/tsconfig-utils/package.json @@ -48,7 +48,6 @@ }, "devDependencies": { "@vitest/coverage-v8": "^3.1.2", - "eslint": "*", "prettier": "^3.2.5", "rimraf": "*", "typescript": "*", diff --git a/packages/typescript-estree/tests/validateDefaultProjectForFilesGlob.test.ts b/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts similarity index 87% rename from packages/typescript-estree/tests/validateDefaultProjectForFilesGlob.test.ts rename to packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts index df8d59842f18..043724755b7b 100644 --- a/packages/typescript-estree/tests/validateDefaultProjectForFilesGlob.test.ts +++ b/packages/typescript-estree/tests/lib/validateDefaultProjectForFilesGlob.test.ts @@ -1,4 +1,4 @@ -import { validateDefaultProjectForFilesGlob } from '../src/create-program/validateDefaultProjectForFilesGlob.js'; +import { validateDefaultProjectForFilesGlob } from '../../src/create-program/validateDefaultProjectForFilesGlob.js'; describe(validateDefaultProjectForFilesGlob, () => { it('does not throw when options.allowDefaultProject is an empty array', () => { diff --git a/yarn.lock b/yarn.lock index b3e1b4dac1b7..e5f8dff760f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5415,7 +5415,6 @@ __metadata: "@vitest/coverage-v8": ^3.1.2 prettier: ^3.2.5 rimraf: "*" - tsx: "*" typescript: "*" vitest: ^3.1.2 languageName: unknown @@ -5485,7 +5484,6 @@ __metadata: resolution: "@typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils" dependencies: "@vitest/coverage-v8": ^3.1.2 - eslint: "*" prettier: ^3.2.5 rimraf: "*" typescript: "*" From 7e4902a3f7c97e34e7212361ffcfaccf6027587c Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 6 May 2025 21:14:12 -0400 Subject: [PATCH 04/20] lint fixes --- packages/project-service/package.json | 3 ++- .../src/createProjectService.ts | 21 ++++++++----------- .../tests/createProjectService.test.ts | 3 ++- yarn.lock | 1 + 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/project-service/package.json b/packages/project-service/package.json index 02a9004d8896..4ff156364f0f 100644 --- a/packages/project-service/package.json +++ b/packages/project-service/package.json @@ -47,7 +47,8 @@ }, "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.32.0", - "@typescript-eslint/types": "^8.32.0" + "@typescript-eslint/types": "^8.32.0", + "debug": "^4.3.4" }, "devDependencies": { "@vitest/coverage-v8": "^3.1.2", diff --git a/packages/project-service/src/createProjectService.ts b/packages/project-service/src/createProjectService.ts index 9ac5a4cb5458..a10c90d10b05 100644 --- a/packages/project-service/src/createProjectService.ts +++ b/packages/project-service/src/createProjectService.ts @@ -1,17 +1,12 @@ -/* eslint-disable @typescript-eslint/no-empty-function -- for TypeScript APIs*/ -import type * as ts from 'typescript/lib/tsserverlibrary'; - -import debug from 'debug'; - import type { ProjectServiceOptions } from '@typescript-eslint/types'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import { getParsedConfigFile } from '@typescript-eslint/tsconfig-utils'; +import debug from 'debug'; const DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD = 8; -const log = debug( - 'typescript-eslint:project-service:create-program:createProjectService', -); +const log = debug('typescript-eslint:project-service:createProjectService'); const logTsserverErr = debug('typescript-eslint:project-service:tsserver:err'); const logTsserverInfo = debug( 'typescript-eslint:project-service:tsserver:info', @@ -23,14 +18,14 @@ const logTsserverEvent = debug( 'typescript-eslint:project-service:tsserver:event', ); +// For TypeScript APIs that expect a function to be passed in +// eslint-disable-next-line @typescript-eslint/no-empty-function const doNothing = (): void => {}; const createStubFileWatcher = (): ts.FileWatcher => ({ close: doNothing, }); -export type { ProjectServiceOptions }; - export type TypeScriptProjectService = ts.server.ProjectService; /** @@ -73,12 +68,12 @@ export interface CreateProjectServiceSettings { * * const { service } = createProjectService(); * - * service.openClientFile('index.ts); + * service.openClientFile('index.ts'); * ``` */ export function createProjectService({ - options: optionsRaw, jsDocParsingMode, + options: optionsRaw, tsconfigRootDir, }: CreateProjectServiceSettings = {}): ProjectServiceAndMetadata { const options = { @@ -211,3 +206,5 @@ export function createProjectService({ service, }; } + +export { type ProjectServiceOptions } from '@typescript-eslint/types'; diff --git a/packages/project-service/tests/createProjectService.test.ts b/packages/project-service/tests/createProjectService.test.ts index 259e6bb852d3..c2d9d8d1a609 100644 --- a/packages/project-service/tests/createProjectService.test.ts +++ b/packages/project-service/tests/createProjectService.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ import debug from 'debug'; import * as ts from 'typescript'; @@ -139,7 +140,7 @@ describe(createProjectService, () => { ); }); - it('uses the default project compiler options when options.defaultProject is set and getParsedConfigFile succeeds', async () => { + it('uses the default project compiler options when options.defaultProject is set and getParsedConfigFile succeeds', () => { const compilerOptions: ts.CompilerOptions = { strict: true }; mockGetParsedConfigFile.mockReturnValueOnce({ errors: [], diff --git a/yarn.lock b/yarn.lock index e5f8dff760f4..0b42aecb61b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5413,6 +5413,7 @@ __metadata: "@typescript-eslint/tsconfig-utils": ^8.32.0 "@typescript-eslint/types": ^8.32.0 "@vitest/coverage-v8": ^3.1.2 + debug: ^4.3.4 prettier: ^3.2.5 rimraf: "*" typescript: "*" From 59a1d8d350361347cc1c051ed5b64d3d481a1a57 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 6 May 2025 21:14:33 -0400 Subject: [PATCH 05/20] lint fixes --- .../src/parseSettings/createParseSettings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index d522f880e55a..678b771ac3a5 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -2,17 +2,18 @@ import type { CreateProjectServiceSettings, ProjectServiceAndMetadata, } from '@typescript-eslint/project-service'; +import type { ProjectServiceOptions } from '@typescript-eslint/types'; import { createProjectService } from '@typescript-eslint/project-service'; import debug from 'debug'; import path from 'node:path'; import * as ts from 'typescript'; -import { validateDefaultProjectForFilesGlob } from '../create-program/validateDefaultProjectForFilesGlob'; import type { TSESTreeOptions } from '../parser-options'; import type { MutableParseSettings } from './index'; import { ensureAbsolutePath } from '../create-program/shared'; +import { validateDefaultProjectForFilesGlob } from '../create-program/validateDefaultProjectForFilesGlob'; import { isSourceFile } from '../source-files'; import { DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, @@ -22,7 +23,6 @@ import { getProjectConfigFiles } from './getProjectConfigFiles'; import { inferSingleRun } from './inferSingleRun'; import { resolveProjectList } from './resolveProjectList'; import { warnAboutTSVersion } from './warnAboutTSVersion'; -import { ProjectServiceOptions } from '@typescript-eslint/types'; const log = debug( 'typescript-eslint:typescript-estree:parseSettings:createParseSettings', From e01d3160aab4661720f2c8f6907e235f090bd045 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 7 May 2025 07:38:51 -0400 Subject: [PATCH 06/20] lint fixes --- .../tests/lib/useProgramFromProjectService.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts index 0d57007b90c5..e07add99d30e 100644 --- a/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/useProgramFromProjectService.test.ts @@ -1,10 +1,11 @@ -import path from 'node:path'; -import * as ts from 'typescript'; - import type { ProjectServiceAndMetadata, TypeScriptProjectService, } from '@typescript-eslint/project-service'; + +import path from 'node:path'; +import * as ts from 'typescript'; + import type { ParseSettings } from '../../src/parseSettings'; import { useProgramFromProjectService } from '../../src/useProgramFromProjectService'; From cd550db725e0901160e7498404779bf2d5d4629f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 07:45:29 -0400 Subject: [PATCH 07/20] Docs and lockfile updates --- docs/packages/Project_Service.mdx | 22 +++++++++++++++++++ .../src/createProjectService.ts | 20 ++++++++++++++++- yarn.lock | 4 ++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/packages/Project_Service.mdx b/docs/packages/Project_Service.mdx index 5d4d4fef3406..16ccf4ec4931 100644 --- a/docs/packages/Project_Service.mdx +++ b/docs/packages/Project_Service.mdx @@ -12,6 +12,28 @@ import GeneratedDocs from './project-service/generated/index.md'; > Standalone TypeScript project service wrapper for linting ✨ +The typescript-eslint Project Service is a wrapper around TypeScript's "project service" APIs. +These APIs are what editors such as VS Code use to programmatically "open" files and generate TypeScript projects for type information. + +:::note +See [Announcing typescript-eslint v8 > Project Service](/blog/announcing-typescript-eslint-v8#project-service) for more details on how lint users interact with the Project Service. +::: + +```ts +import { createProjectService } from '@typescript-eslint/project-service'; + +const filePathAbsolute = '/path/to/your/project/index.ts'; +const { service } = createProjectService(); + +service.openClientFile(filePathAbsolute); + +const scriptInfo = service.getScriptInfo(filePathAbsolute)!; +const program = service + .getDefaultProjectForFile(scriptInfo.fileName, true)! + .getLanguageService(true) + .getProgram()!; +``` + The following documentation is auto-generated from source code. diff --git a/packages/project-service/src/createProjectService.ts b/packages/project-service/src/createProjectService.ts index a10c90d10b05..f8c0bf9bb933 100644 --- a/packages/project-service/src/createProjectService.ts +++ b/packages/project-service/src/createProjectService.ts @@ -26,10 +26,13 @@ const createStubFileWatcher = (): ts.FileWatcher => ({ close: doNothing, }); +/** + * Shortcut type to refer to TypeScript's server ProjectService. + */ export type TypeScriptProjectService = ts.server.ProjectService; /** - * A created Project Service instances, as well as metadata on its creation. + * A created Project Service instance, as well as metadata on its creation. */ export interface ProjectServiceAndMetadata { /** @@ -53,13 +56,28 @@ export interface ProjectServiceAndMetadata { service: TypeScriptProjectService; } +/** + * Settings to create a new Project Service instance with {@link createProjectService}. + */ export interface CreateProjectServiceSettings { + /** + * Granular options to configure the project service. + */ options?: ProjectServiceOptions; + + /** + * How aggressively (and slowly) to parse JSDoc comments. + */ jsDocParsingMode?: ts.JSDocParsingMode; + + /** + * Root directory for the tsconfig.json file, if not the current directory. + */ tsconfigRootDir?: string; } /** + * Creates a new Project Service instance, as well as metadata on its creation. * @param settings Settings to create a new Project Service instance. * @returns A new Project Service instance, as well as metadata on its creation. * @example diff --git a/yarn.lock b/yarn.lock index 03766f6e35b7..2aa4663743fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5878,7 +5878,7 @@ __metadata: languageName: node linkType: hard -"@vitest/coverage-v8@npm:^3.1.3": +"@vitest/coverage-v8@npm:^3.1.2, @vitest/coverage-v8@npm:^3.1.3": version: 3.1.3 resolution: "@vitest/coverage-v8@npm:3.1.3" dependencies: @@ -19475,7 +19475,7 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^3.1.3": +"vitest@npm:^3.1.2, vitest@npm:^3.1.3": version: 3.1.3 resolution: "vitest@npm:3.1.3" dependencies: From f904504e25621c847a80f358cdb12bf74e4762aa Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 08:10:19 -0400 Subject: [PATCH 08/20] nit: 'programs', not 'projects' --- docs/packages/Project_Service.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/packages/Project_Service.mdx b/docs/packages/Project_Service.mdx index 16ccf4ec4931..7f841f3560b1 100644 --- a/docs/packages/Project_Service.mdx +++ b/docs/packages/Project_Service.mdx @@ -13,7 +13,7 @@ import GeneratedDocs from './project-service/generated/index.md'; > Standalone TypeScript project service wrapper for linting ✨ The typescript-eslint Project Service is a wrapper around TypeScript's "project service" APIs. -These APIs are what editors such as VS Code use to programmatically "open" files and generate TypeScript projects for type information. +These APIs are what editors such as VS Code use to programmatically "open" files and generate TypeScript programs for type information. :::note See [Announcing typescript-eslint v8 > Project Service](/blog/announcing-typescript-eslint-v8#project-service) for more details on how lint users interact with the Project Service. From f151e6608a1192b0b40e1484374abab4c691ec79 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 08:11:50 -0400 Subject: [PATCH 09/20] Updated y(a)ml files too --- .github/ISSUE_TEMPLATE/06-bug-report-other.yaml | 2 ++ .github/ISSUE_TEMPLATE/07-enhancement-other.yaml | 2 ++ .github/workflows/ci.yml | 2 ++ .github/workflows/semantic-pr-titles.yml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml b/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml index 59b8783d1e75..69fc7d6ef7d7 100644 --- a/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml +++ b/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml @@ -40,8 +40,10 @@ body: - ast-spec - eslint-plugin - parser + - project-service - rule-tester - scope-manager + - tsconfig-utils - type-utils - types - typescript-eslint diff --git a/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml b/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml index 604ce5468161..7250ea66e0f3 100644 --- a/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml +++ b/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml @@ -26,8 +26,10 @@ body: - ast-spec - eslint-plugin - parser + - project-service - rule-tester - scope-manager + - tsconfig-utils - type-utils - types - typescript-eslint diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 398c05e17baa..cfcc5d5409df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -170,8 +170,10 @@ jobs: 'eslint-plugin', 'eslint-plugin-internal', 'parser', + 'project-service', 'rule-tester', 'scope-manager', + 'tsconfig-utils', 'type-utils', 'typescript-eslint', 'typescript-estree', diff --git a/.github/workflows/semantic-pr-titles.yml b/.github/workflows/semantic-pr-titles.yml index 3a3c4a2c3164..b206297b3896 100644 --- a/.github/workflows/semantic-pr-titles.yml +++ b/.github/workflows/semantic-pr-titles.yml @@ -30,8 +30,10 @@ jobs: eslint-plugin eslint-plugin-internal parser + project-service rule-tester scope-manager + tsconfig-utils type-utils types typescript-eslint From 03a1ccc3c944144d00cf1de11ca23b80222e033e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 08:24:05 -0400 Subject: [PATCH 10/20] chore: correct tsconfig-utils type-utils config entries --- packages/tsconfig-utils/project.json | 6 +++--- packages/tsconfig-utils/tsconfig.spec.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/tsconfig-utils/project.json b/packages/tsconfig-utils/project.json index 8c91c0ace943..b435c1f765f9 100644 --- a/packages/tsconfig-utils/project.json +++ b/packages/tsconfig-utils/project.json @@ -1,9 +1,9 @@ { - "name": "type-utils", + "name": "tsconfig-utils", "$schema": "../../node_modules/nx/schemas/project-schema.json", "projectType": "library", - "root": "packages/type-utils", - "sourceRoot": "packages/type-utils/src", + "root": "packages/tsconfig-utils", + "sourceRoot": "packages/tsconfig-utils/src", "targets": { "lint": { "executor": "@nx/eslint:lint", diff --git a/packages/tsconfig-utils/tsconfig.spec.json b/packages/tsconfig-utils/tsconfig.spec.json index 7ec995aaf7dc..21fcf7e24f3f 100644 --- a/packages/tsconfig-utils/tsconfig.spec.json +++ b/packages/tsconfig-utils/tsconfig.spec.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "../../dist/out-tsc/packages/type-utils", + "outDir": "../../dist/out-tsc/packages/tsconfig-utils", "module": "NodeNext", "resolveJsonModule": true, "types": ["node", "vitest/globals", "vitest/importMeta"] From abd231594a1d7d3578f9fb97bcef216484b2f17c Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 08:24:43 -0400 Subject: [PATCH 11/20] chore: correct license year --- packages/project-service/LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/project-service/LICENSE b/packages/project-service/LICENSE index a1164108d4d6..310a18f8a6cb 100644 --- a/packages/project-service/LICENSE +++ b/packages/project-service/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 typescript-eslint and other contributors +Copyright (c) 2025 typescript-eslint and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c424f73cfec15ea3f835dab83ee3f9cb2d7aa8e8 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 09:27:29 -0400 Subject: [PATCH 12/20] tests: corrected --- .../src/createProjectService.ts | 24 ++---- .../src/getParsedConfigFileFromTSServer.ts | 22 +++++ .../tests/createProjectService.test.ts | 85 +++---------------- .../getParsedConfigFileFromTSServer.test.ts | 61 +++++++++++++ 4 files changed, 102 insertions(+), 90 deletions(-) create mode 100644 packages/project-service/src/getParsedConfigFileFromTSServer.ts create mode 100644 packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts diff --git a/packages/project-service/src/createProjectService.ts b/packages/project-service/src/createProjectService.ts index f8c0bf9bb933..1f3bfb1c2b74 100644 --- a/packages/project-service/src/createProjectService.ts +++ b/packages/project-service/src/createProjectService.ts @@ -1,8 +1,8 @@ import type { ProjectServiceOptions } from '@typescript-eslint/types'; import type * as ts from 'typescript/lib/tsserverlibrary'; -import { getParsedConfigFile } from '@typescript-eslint/tsconfig-utils'; import debug from 'debug'; +import { getParsedConfigFileFromTSServer } from './getParsedConfigFileFromTSServer.js'; const DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD = 8; @@ -91,7 +91,7 @@ export interface CreateProjectServiceSettings { */ export function createProjectService({ jsDocParsingMode, - options: optionsRaw, + options: optionsRaw = {}, tsconfigRootDir, }: CreateProjectServiceSettings = {}): ProjectServiceAndMetadata { const options = { @@ -189,21 +189,13 @@ export function createProjectService({ }); log('Enabling default project: %s', options.defaultProject); - let configFile: ts.ParsedCommandLine | undefined; - try { - configFile = getParsedConfigFile( - tsserver, - options.defaultProject, - tsconfigRootDir, - ); - } catch (error) { - if (options.defaultProject) { - throw new Error( - `Could not read Project Service default project '${options.defaultProject}': ${(error as Error).message}`, - ); - } - } + const configFile = getParsedConfigFileFromTSServer( + tsserver, + options.defaultProject, + !!optionsRaw.defaultProject, + tsconfigRootDir, + ); if (configFile) { service.setCompilerOptionsForInferredProjects( diff --git a/packages/project-service/src/getParsedConfigFileFromTSServer.ts b/packages/project-service/src/getParsedConfigFileFromTSServer.ts new file mode 100644 index 000000000000..7ff5177b0382 --- /dev/null +++ b/packages/project-service/src/getParsedConfigFileFromTSServer.ts @@ -0,0 +1,22 @@ +import type * as ts from 'typescript/lib/tsserverlibrary'; + +import { getParsedConfigFile } from '@typescript-eslint/tsconfig-utils'; + +export function getParsedConfigFileFromTSServer( + tsserver: typeof ts, + defaultProject: string, + throwOnFailure: boolean, + tsconfigRootDir?: string, +) { + try { + return getParsedConfigFile(tsserver, defaultProject, tsconfigRootDir); + } catch (error) { + if (throwOnFailure) { + throw new Error( + `Could not read Project Service default project '${defaultProject}': ${(error as Error).message}`, + ); + } + } + + return undefined; +} diff --git a/packages/project-service/tests/createProjectService.test.ts b/packages/project-service/tests/createProjectService.test.ts index c2d9d8d1a609..abc329108416 100644 --- a/packages/project-service/tests/createProjectService.test.ts +++ b/packages/project-service/tests/createProjectService.test.ts @@ -4,11 +4,11 @@ import * as ts from 'typescript'; import { createProjectService } from '../src/createProjectService.js'; -const mockGetParsedConfigFile = vi.fn(); +const mockGetParsedConfigFileFromTSServer = vi.fn(); -vi.mock('@typescript-eslint/tsconfig-utils', () => ({ - get getParsedConfigFile() { - return mockGetParsedConfigFile; +vi.mock('../src/getParsedConfigFileFromTSServer.js', () => ({ + get getParsedConfigFileFromTSServer() { + return mockGetParsedConfigFileFromTSServer; }, })); @@ -75,74 +75,9 @@ describe(createProjectService, () => { assert.isUndefined(settings.allowDefaultProject); }); - it('does not throw an error when options.defaultProject is not provided and getParsedConfigFile throws a diagnostic error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error('tsconfig.json(1,1): error TS1234: Oh no!'); - }); - - expect(() => - createProjectService({ - options: { allowDefaultProject: ['file.js'] }, - }), - ).not.toThrow(); - }); - - it('throws an error with a relative path when options.defaultProject is set to a relative path and getParsedConfigFile throws a diagnostic error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!'); - }); - - expect(() => - createProjectService({ - options: { - allowDefaultProject: ['file.js'], - defaultProject: './tsconfig.eslint.json', - }, - }), - ).toThrow( - /Could not read project service default project '\.\/tsconfig.eslint.json': .+ error TS1234: Oh no!/, - ); - }); - - it('throws an error with a local path when options.defaultProject is set to a local path and getParsedConfigFile throws a diagnostic error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!'); - }); - - expect(() => - createProjectService({ - options: { - allowDefaultProject: ['file.js'], - defaultProject: 'tsconfig.eslint.json', - }, - }), - ).toThrow( - /Could not read project service default project 'tsconfig.eslint.json': .+ error TS1234: Oh no!/, - ); - }); - - it('throws an error when options.defaultProject is set and getParsedConfigFile throws an environment error', () => { - mockGetParsedConfigFile.mockImplementation(() => { - throw new Error( - '`getParsedConfigFile` is only supported in a Node-like environment.', - ); - }); - - expect(() => - createProjectService({ - options: { - allowDefaultProject: ['file.js'], - defaultProject: 'tsconfig.json', - }, - }), - ).toThrow( - "Could not read project service default project 'tsconfig.json': `getParsedConfigFile` is only supported in a Node-like environment.", - ); - }); - - it('uses the default project compiler options when options.defaultProject is set and getParsedConfigFile succeeds', () => { + it('uses the default project compiler options when options.defaultProject is set', () => { const compilerOptions: ts.CompilerOptions = { strict: true }; - mockGetParsedConfigFile.mockReturnValueOnce({ + mockGetParsedConfigFileFromTSServer.mockReturnValueOnce({ errors: [], fileNames: [], options: compilerOptions, @@ -161,9 +96,10 @@ describe(createProjectService, () => { compilerOptions, ); - expect(mockGetParsedConfigFile).toHaveBeenCalledWith( + expect(mockGetParsedConfigFileFromTSServer).toHaveBeenCalledWith( expect.any(Object), defaultProject, + true, undefined, ); }); @@ -172,7 +108,7 @@ describe(createProjectService, () => { it('uses tsconfigRootDir as getParsedConfigFile projectDirectory when provided', async () => { const compilerOptions: ts.CompilerOptions = { strict: true }; const tsconfigRootDir = 'path/to/repo'; - mockGetParsedConfigFile.mockReturnValueOnce({ + mockGetParsedConfigFileFromTSServer.mockReturnValueOnce({ errors: [], fileNames: [], options: compilerOptions, @@ -187,9 +123,10 @@ describe(createProjectService, () => { compilerOptions, ); - expect(mockGetParsedConfigFile).toHaveBeenCalledWith( + expect(mockGetParsedConfigFileFromTSServer).toHaveBeenCalledWith( (await import('typescript/lib/tsserverlibrary.js')).default, 'tsconfig.json', + false, tsconfigRootDir, ); }); diff --git a/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts b/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts new file mode 100644 index 000000000000..f080ee8c2083 --- /dev/null +++ b/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts @@ -0,0 +1,61 @@ +import { getParsedConfigFileFromTSServer } from '../src/getParsedConfigFileFromTSServer'; + +const mockGetParsedConfigFile = vi.fn(); + +vi.mock('@typescript-eslint/tsconfig-utils', () => ({ + get getParsedConfigFile() { + return mockGetParsedConfigFile; + }, +})); + +const mockConfigFile = { + fileNames: [], +}; + +const mockTSServer = + {} as unknown as typeof import('typescript/lib/tsserverlibrary'); + +const mockTSConfigRootDir = '/mock/tsconfig/root/dir'; + +describe('getParsedConfigFileFromTSServer', () => { + it('returns the parsed config file when getParsedConfigFile succeeds', () => { + mockGetParsedConfigFile.mockReturnValueOnce(mockConfigFile); + + const actual = getParsedConfigFileFromTSServer( + mockTSServer, + 'tsconfig.json', + false, + mockTSConfigRootDir, + ); + + expect(actual).toBe(mockConfigFile); + }); + + it('returns undefined when getParsedConfigFile fails and throwOnFailure is false', () => { + mockGetParsedConfigFile.mockImplementationOnce(() => { + throw new Error('Oh no!'); + }); + + const actual = getParsedConfigFileFromTSServer( + mockTSServer, + 'tsconfig.json', + false, + ); + + expect(actual).toBeUndefined(); + }); + + it('throws the error when getParsedConfigFile fails and throwOnFailure is true', () => { + mockGetParsedConfigFile.mockImplementationOnce(() => { + throw new Error('Oh no!'); + }); + + expect(() => + getParsedConfigFileFromTSServer(mockTSServer, 'tsconfig.json', true), + ).toThrow( + new Error( + `Could not read Project Service default project 'tsconfig.json': Oh no!`, + ), + ); + }); +}); From ef91a38d26f547f6f640a617cb18e80ed1a3f91f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 09:41:12 -0400 Subject: [PATCH 13/20] Lint fixes --- packages/project-service/src/createProjectService.ts | 1 + .../project-service/src/getParsedConfigFileFromTSServer.ts | 2 +- .../tests/getParsedConfigFileFromTSServer.test.ts | 7 +++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/project-service/src/createProjectService.ts b/packages/project-service/src/createProjectService.ts index 1f3bfb1c2b74..ecf075ef7c5f 100644 --- a/packages/project-service/src/createProjectService.ts +++ b/packages/project-service/src/createProjectService.ts @@ -2,6 +2,7 @@ import type { ProjectServiceOptions } from '@typescript-eslint/types'; import type * as ts from 'typescript/lib/tsserverlibrary'; import debug from 'debug'; + import { getParsedConfigFileFromTSServer } from './getParsedConfigFileFromTSServer.js'; const DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD = 8; diff --git a/packages/project-service/src/getParsedConfigFileFromTSServer.ts b/packages/project-service/src/getParsedConfigFileFromTSServer.ts index 7ff5177b0382..c2e2f83612c4 100644 --- a/packages/project-service/src/getParsedConfigFileFromTSServer.ts +++ b/packages/project-service/src/getParsedConfigFileFromTSServer.ts @@ -7,7 +7,7 @@ export function getParsedConfigFileFromTSServer( defaultProject: string, throwOnFailure: boolean, tsconfigRootDir?: string, -) { +): ts.ParsedCommandLine | undefined { try { return getParsedConfigFile(tsserver, defaultProject, tsconfigRootDir); } catch (error) { diff --git a/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts b/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts index f080ee8c2083..793389069601 100644 --- a/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts +++ b/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts @@ -1,3 +1,4 @@ +import type * as ts from 'typescript/lib/tsserverlibrary'; import { getParsedConfigFileFromTSServer } from '../src/getParsedConfigFileFromTSServer'; const mockGetParsedConfigFile = vi.fn(); @@ -12,12 +13,10 @@ const mockConfigFile = { fileNames: [], }; -const mockTSServer = - {} as unknown as typeof import('typescript/lib/tsserverlibrary'); - +const mockTSServer = {} as unknown as typeof ts; const mockTSConfigRootDir = '/mock/tsconfig/root/dir'; -describe('getParsedConfigFileFromTSServer', () => { +describe(getParsedConfigFileFromTSServer, () => { it('returns the parsed config file when getParsedConfigFile succeeds', () => { mockGetParsedConfigFile.mockReturnValueOnce(mockConfigFile); From caa57bbb6210544948389c2f59df4082b7919a42 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 12 May 2025 09:52:42 -0400 Subject: [PATCH 14/20] Lint fixes --- .../tests/getParsedConfigFileFromTSServer.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts b/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts index 793389069601..f6f868bd0096 100644 --- a/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts +++ b/packages/project-service/tests/getParsedConfigFileFromTSServer.test.ts @@ -1,4 +1,5 @@ import type * as ts from 'typescript/lib/tsserverlibrary'; + import { getParsedConfigFileFromTSServer } from '../src/getParsedConfigFileFromTSServer'; const mockGetParsedConfigFile = vi.fn(); From 3d0e00ed6721933d025ce127abd8c56eb525c8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 20 May 2025 11:50:28 -0400 Subject: [PATCH 15/20] Apply suggestions from code review Co-authored-by: Brad Zacher --- packages/project-service/README.md | 4 ++-- packages/tsconfig-utils/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/project-service/README.md b/packages/project-service/README.md index 916084c3df70..cf1a671681d6 100644 --- a/packages/project-service/README.md +++ b/packages/project-service/README.md @@ -2,8 +2,8 @@ > Standalone TypeScript project service wrapper for linting. -[![NPM Version](https://img.shields.io/npm/v/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) -[![NPM Downloads](https://img.shields.io/npm/dm/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) +[![NPM Version](https://img.shields.io/npm/v/@typescript-eslint/project-service.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/project-service) +[![NPM Downloads](https://img.shields.io/npm/dm/@typescript-eslint/project-service.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/project-service) A standalone export of the "Project Service" that powers typed linting for typescript-eslint. diff --git a/packages/tsconfig-utils/README.md b/packages/tsconfig-utils/README.md index c9eb2fdff88e..075c3fbb7315 100644 --- a/packages/tsconfig-utils/README.md +++ b/packages/tsconfig-utils/README.md @@ -2,8 +2,8 @@ > Utilities for collecting TSConfigs for linting scenarios. -[![NPM Version](https://img.shields.io/npm/v/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) -[![NPM Downloads](https://img.shields.io/npm/dm/@typescript-eslint/utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/utils) +[![NPM Version](https://img.shields.io/npm/v/@typescript-eslint/tsconfig-utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/tsconfig-utils) +[![NPM Downloads](https://img.shields.io/npm/dm/@typescript-eslint/tsconfig-utils.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/tsconfig-utils) The utilities in this package are separated from `@typescript-eslint/utils` so that they do not have a dependency on `eslint` or `@typescript-eslint/typescript-estree`. From 06f7ae82cc426e2d6259a7a5f91c5a2fb7f5020b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 20 May 2025 11:50:37 -0400 Subject: [PATCH 16/20] filename fix --- docs/packages/{TSConfig_Utilsx.mdx => TSConfig_Utils.mdx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/packages/{TSConfig_Utilsx.mdx => TSConfig_Utils.mdx} (100%) diff --git a/docs/packages/TSConfig_Utilsx.mdx b/docs/packages/TSConfig_Utils.mdx similarity index 100% rename from docs/packages/TSConfig_Utilsx.mdx rename to docs/packages/TSConfig_Utils.mdx From 94e55522aae5298fcb835239ae8a15a9a5b5d0d0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 20 May 2025 11:51:08 -0400 Subject: [PATCH 17/20] an inline disable --- packages/project-service/tests/createProjectService.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/project-service/tests/createProjectService.test.ts b/packages/project-service/tests/createProjectService.test.ts index abc329108416..32fd4e971d85 100644 --- a/packages/project-service/tests/createProjectService.test.ts +++ b/packages/project-service/tests/createProjectService.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ import debug from 'debug'; import * as ts from 'typescript'; @@ -46,6 +45,7 @@ describe(createProjectService, () => { .mockImplementation(() => true); beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-require-imports const { ProjectService } = require('typescript/lib/tsserverlibrary').server; ProjectService.prototype.setCompilerOptionsForInferredProjects = From ba8b5070f25c4ab3d2bea27e39c7024f349b39f2 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 20 May 2025 12:01:11 -0400 Subject: [PATCH 18/20] Remove project.json files, and instead add package.json nx entries --- packages/project-service/package.json | 6 ++++++ packages/project-service/project.json | 13 ------------- packages/tsconfig-utils/package.json | 6 ++++++ packages/tsconfig-utils/project.json | 16 ---------------- 4 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 packages/project-service/project.json delete mode 100644 packages/tsconfig-utils/project.json diff --git a/packages/project-service/package.json b/packages/project-service/package.json index 1752e7b2a449..98e8cff4bc10 100644 --- a/packages/project-service/package.json +++ b/packages/project-service/package.json @@ -60,5 +60,11 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "nx": { + "name": "project-service", + "includedScripts": [ + "clean" + ] } } diff --git a/packages/project-service/project.json b/packages/project-service/project.json deleted file mode 100644 index 7d029ba93a59..000000000000 --- a/packages/project-service/project.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "project-service", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/project-service", - "sourceRoot": "packages/project-service/src", - "targets": { - "lint": { - "executor": "@nx/eslint:lint", - "outputs": ["{options.outputFile}"] - } - } -} diff --git a/packages/tsconfig-utils/package.json b/packages/tsconfig-utils/package.json index 9b7498f1cf14..b88f5894e958 100644 --- a/packages/tsconfig-utils/package.json +++ b/packages/tsconfig-utils/package.json @@ -56,5 +56,11 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "nx": { + "name": "tsconfig-utils", + "includedScripts": [ + "clean" + ] } } diff --git a/packages/tsconfig-utils/project.json b/packages/tsconfig-utils/project.json deleted file mode 100644 index b435c1f765f9..000000000000 --- a/packages/tsconfig-utils/project.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "tsconfig-utils", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/tsconfig-utils", - "sourceRoot": "packages/tsconfig-utils/src", - "targets": { - "lint": { - "executor": "@nx/eslint:lint", - "outputs": ["{options.outputFile}"] - }, - "test": { - "executor": "@nx/vite:test" - } - } -} From 5e52cf86a521e7efada00ec80413d198b7a3e1af Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 20 May 2025 12:01:55 -0400 Subject: [PATCH 19/20] Align scripts --- packages/project-service/package.json | 14 +++++++------- packages/tsconfig-utils/package.json | 11 ++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/project-service/package.json b/packages/project-service/package.json index 98e8cff4bc10..f4b06a88e527 100644 --- a/packages/project-service/package.json +++ b/packages/project-service/package.json @@ -37,13 +37,13 @@ "estree" ], "scripts": { - "build": "tsc -b tsconfig.build.json", - "clean": "tsc -b tsconfig.build.json --clean", - "postclean": "rimraf dist/ src/generated/ coverage/", - "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", - "lint": "npx nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "npx nx typecheck" + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", + "clean": "rimraf dist/ coverage/", + "format": "yarn run -T format", + "lint": "yarn run -BT nx lint", + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.32.1", diff --git a/packages/tsconfig-utils/package.json b/packages/tsconfig-utils/package.json index b88f5894e958..360e3d7a535e 100644 --- a/packages/tsconfig-utils/package.json +++ b/packages/tsconfig-utils/package.json @@ -36,12 +36,13 @@ "estree" ], "scripts": { - "build": "tsc -b tsconfig.build.json", + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", "clean": "rimraf dist/ coverage/", - "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", - "lint": "npx nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "npx nx typecheck" + "format": "yarn run -T format", + "lint": "yarn run -BT nx lint", + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" From 572acf56276473429dced9a4d1f4d5daef5cb322 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 20 May 2025 12:26:44 -0400 Subject: [PATCH 20/20] Remove unnecessary deps --- packages/project-service/package.json | 7 ------- packages/tsconfig-utils/package.json | 7 ------- yarn.lock | 15 ++------------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/packages/project-service/package.json b/packages/project-service/package.json index f4b06a88e527..6b84af53ee34 100644 --- a/packages/project-service/package.json +++ b/packages/project-service/package.json @@ -50,13 +50,6 @@ "@typescript-eslint/types": "^8.32.1", "debug": "^4.3.4" }, - "devDependencies": { - "@vitest/coverage-v8": "^3.1.2", - "prettier": "^3.2.5", - "rimraf": "*", - "typescript": "*", - "vitest": "^3.1.2" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" diff --git a/packages/tsconfig-utils/package.json b/packages/tsconfig-utils/package.json index 360e3d7a535e..d430af46082a 100644 --- a/packages/tsconfig-utils/package.json +++ b/packages/tsconfig-utils/package.json @@ -47,13 +47,6 @@ "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" }, - "devDependencies": { - "@vitest/coverage-v8": "^3.1.2", - "prettier": "^3.2.5", - "rimraf": "*", - "typescript": "*", - "vitest": "^3.1.2" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" diff --git a/yarn.lock b/yarn.lock index 6dab3e9b4ba0..09f7355bb869 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5499,12 +5499,7 @@ __metadata: dependencies: "@typescript-eslint/tsconfig-utils": ^8.32.1 "@typescript-eslint/types": ^8.32.1 - "@vitest/coverage-v8": ^3.1.2 debug: ^4.3.4 - prettier: ^3.2.5 - rimraf: "*" - typescript: "*" - vitest: ^3.1.2 languageName: unknown linkType: soft @@ -5565,12 +5560,6 @@ __metadata: "@typescript-eslint/tsconfig-utils@8.32.1, @typescript-eslint/tsconfig-utils@^8.32.1, @typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/tsconfig-utils@workspace:packages/tsconfig-utils" - dependencies: - "@vitest/coverage-v8": ^3.1.2 - prettier: ^3.2.5 - rimraf: "*" - typescript: "*" - vitest: ^3.1.2 peerDependencies: typescript: ">=4.8.4 <5.9.0" languageName: unknown @@ -5766,7 +5755,7 @@ __metadata: languageName: node linkType: hard -"@vitest/coverage-v8@npm:^3.1.2, @vitest/coverage-v8@npm:^3.1.3": +"@vitest/coverage-v8@npm:^3.1.3": version: 3.1.3 resolution: "@vitest/coverage-v8@npm:3.1.3" dependencies: @@ -19223,7 +19212,7 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^3.1.2, vitest@npm:^3.1.3": +"vitest@npm:^3.1.3": version: 3.1.3 resolution: "vitest@npm:3.1.3" dependencies: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy