Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(typescript-estree): default projectService.defaultProject to 'tsconfig.json'#9893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletiondocs/developers/Custom_Rules.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -400,7 +400,6 @@ const ruleTester = new RuleTester({
parserOptions: {
projectService: {
allowDefaultProjectForFiles: ['*.ts*'],
defaultProject: 'tsconfig.json',
},
tsconfigRootDir: __dirname,
},
Expand Down
12 changes: 9 additions & 3 deletionsdocs/packages/Parser.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -335,17 +335,21 @@ The behavior of `parserOptions.projectService` can be customized by setting it t
parserOptions: {
projectService: {
allowDefaultProject: ['*.js'],
defaultProject: 'tsconfig.json',
},
},
};
```

:::tip
See [Troubleshooting & FAQs > Typed Linting > Project Service Issues](../troubleshooting/typed-linting/index.mdx#project-service-issues) for help using these options.
:::

##### `allowDefaultProject`

> Default `[]` _(none)_

Globs of files to allow running with the default project compiler options despite not being matched by the project service.
It takes in an array of string paths that will be resolved relative to the [`tsconfigRootDir`](#tsconfigrootdir).
When set, [`projectService.defaultProject`](#defaultproject) must be set as well.

This is intended to produce type information for config files such as `eslint.config.js` that aren't included in their sibling `tsconfig.json`.
Every file with type information retrieved from the default project incurs a non-trivial performance overhead to linting.
Expand All@@ -358,10 +362,12 @@ There are several restrictions on this option to prevent it from being overused:

##### `defaultProject`

> Default `'tsconfig.json'`

Path to a TSConfig to use instead of TypeScript's default project configuration.
It takes in a string path that will be resolved relative to the [`tsconfigRootDir`](#tsconfigrootdir).

This is required to specify which TSConfig file on disk will be used for [`projectService.allowDefaultProject`](#allowdefaultproject).
`projectService.defaultProject` only impacts the "out-of-project" files included by [`allowDefaultProject`](#allowdefaultproject).

##### `maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING`

Expand Down
8 changes: 2 additions & 6 deletionsdocs/packages/Rule_Tester.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,10 +123,7 @@ ruleTester.run('my-rule', rule, {
### Type-Aware Testing

Type-aware rules can be tested in almost exactly the same way as regular code, using `parserOptions.projectService`.
Most rule tests can use settings like:

- `allowDefaultProjectForFiles: ["*.ts*"]`: to include files in your tests
- `defaultProject: "tsconfig.json"`: to use the same TSConfig as other files
Most rule tests specify `parserOptions.allowDefaultProjectForFiles: ["*.ts*"]` to enable type checking on all test files.

You can then test your rule by providing the type-aware config:

Expand All@@ -135,9 +132,8 @@ const ruleTester = new RuleTester({
// Added lines start
languageOptions: {
parserOptions: {
projectServices: {
projectService: {
allowDefaultProject: ['*.ts*'],
defaultProject: 'tsconfig.json',
},
tsconfigRootDir: './path/to/your/folder/fixture',
},
Expand Down
1 change: 1 addition & 0 deletionsdocs/packages/TypeScript_ESTree.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -275,6 +275,7 @@ interface ProjectServiceOptions {

/**
* Path to a TSConfig to use instead of TypeScript's default project configuration.
* @default 'tsconfig.json'
*/
defaultProject?: string;

Expand Down
1 change: 1 addition & 0 deletionspackages/types/src/parser-options.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,7 @@ interface ProjectServiceOptions {

/**
* Path to a TSConfig to use instead of TypeScript's default project configuration.
* @default 'tsconfig.json'
*/
defaultProject?: string;

Expand Down
3 changes: 3 additions & 0 deletionspackages/typescript-estree/jest.config.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,4 +5,7 @@
module.exports = {
...require('../../jest.config.base.js'),
testRegex: ['./tests/lib/.*\\.test\\.ts$'],
testPathIgnorePatterns: process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE
? ['/node_modules/', 'project-true']
: [],
Copy link
MemberAuthor

@JoshuaKGoldbergJoshuaKGoldbergAug 30, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

parse.project-true.test.ts never really needed to be tested with the project service on anyway. Otherwise:

Could not read project service default project 'tsconfig.json': error TS5012: Cannot read file 'tsconfig.json': ENOENT: no such file or directory, open '/Users/josh/repos/typescript-eslint/packages/typescript-estree/tests/fixtures/projectTrue/tsconfig.json'.

};
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,8 +42,11 @@ export function createProjectService(
jsDocParsingMode: ts.JSDocParsingMode | undefined,
tsconfigRootDir: string | undefined,
): ProjectServiceSettings {
const options = typeof optionsRaw === 'object' ? optionsRaw : {};
validateDefaultProjectForFilesGlob(options);
const options = {
defaultProject: 'tsconfig.json',
...(typeof optionsRaw === 'object' && 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
Expand DownExpand Up@@ -122,31 +125,29 @@ export function createProjectService(
},
});

if (options.defaultProject) {
log('Enabling default project: %s', options.defaultProject);
let configFile: ts.ParsedCommandLine;

try {
configFile = getParsedConfigFile(
tsserver,
options.defaultProject,
tsconfigRootDir,
);
} catch (error) {
throw new Error(
`Could not read default project '${options.defaultProject}': ${(error as Error).message}`,
);
}

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
// 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,
log('Enabling default project: %s', options.defaultProject);
let configFile: ts.ParsedCommandLine;

try {
configFile = getParsedConfigFile(
tsserver,
options.defaultProject,
tsconfigRootDir,
);
} catch (error) {
throw new Error(
`Could not read project service default project '${options.defaultProject}': ${(error as Error).message}`,
);
}

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
// 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,
);

return {
allowDefaultProject: options.allowDefaultProject,
lastReloadTimestamp: performance.now(),
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,11 @@ function getParsedConfigFile(
fileExists: fs.existsSync,
getCurrentDirectory,
readDirectory: tsserver.sys.readDirectory,
readFile: file => fs.readFileSync(file, 'utf-8'),
readFile: file =>
fs.readFileSync(
path.isAbsolute(file) ? file : path.join(getCurrentDirectory(), file),
'utf-8',
),
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Without this absolute checking:

      A fatal parsing error occurred: Parsing error: Could not read project service default project 'tsconfig.json': error TS5012: Cannot read file '/home/runner/work/typescript-eslint/typescript-eslint/packages/eslint-plugin-internal/tests/fixtures/tsconfig.build.json': ENOENT: no such file or directory, open '/home/runner/work/typescript-eslint/typescript-eslint/packages/eslint-plugin-internal/tests/fixtures/tsconfig.build.json'.

Thattsconfig.build.json comes frompackages/typescript-estree/tsconfig.json. The test was reading with cwdpackages/typescript-estree.

useCaseSensitiveFileNames: tsserver.sys.useCaseSensitiveFileNames,
},
);
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
import type { ProjectServiceOptions } from '../parser-options';

export const DEFAULT_PROJECT_FILES_ERROR_EXPLANATION = `

Having many files run with the default project is known to cause performance issues and slow down linting.
Expand All@@ -8,13 +6,13 @@ See https://typescript-eslint.io/troubleshooting/typed-linting#allowdefaultproje
`;

export function validateDefaultProjectForFilesGlob(
options: ProjectServiceOptions,
allowDefaultProject: string[] | undefined,
): void {
if (!options.allowDefaultProject?.length) {
if (!allowDefaultProject?.length) {
return;
}

for (const glob ofoptions.allowDefaultProject) {
for (const glob of allowDefaultProject) {
if (glob === '*') {
throw new Error(
`allowDefaultProject contains the overly wide '*'.${DEFAULT_PROJECT_FILES_ERROR_EXPLANATION}`,
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,10 @@ const {
} = require('../../src/create-program/createProjectService');

describe('createProjectService', () => {
beforeEach(() => {
mockGetParsedConfigFile.mockReturnValue({ options: {} });
});

afterEach(() => {
jest.resetAllMocks();
});
Expand All@@ -61,41 +65,59 @@ describe('createProjectService', () => {
expect(settings.allowDefaultProject).toBeUndefined();
});

it('throws an error with a local path 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,
),
).toThrow(
/Could not read project service default project 'tsconfig.json': .+ error TS1234: Oh no!/,
);
});

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.json(1,1): error TS1234: Oh no!');
throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!');
});

expect(() =>
createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: './tsconfig.json',
defaultProject: './tsconfig.eslint.json',
},
undefined,
undefined,
),
).toThrow(
/Could not read default project '\.\/tsconfig.json': .+ error TS1234: Oh no!/,
/Could not readproject servicedefault 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.json(1,1): error TS1234: Oh no!');
throw new Error('./tsconfig.eslint.json(1,1): error TS1234: Oh no!');
});

expect(() =>
createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: 'tsconfig.json',
defaultProject: 'tsconfig.eslint.json',
},
undefined,
undefined,
),
).toThrow(
/Could not read default project 'tsconfig.json': .+ error TS1234: Oh no!/,
/Could not readproject servicedefault project 'tsconfig.eslint.json': .+ error TS1234: Oh no!/,
);
});

Expand All@@ -110,24 +132,24 @@ describe('createProjectService', () => {
createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: 'tsconfig.json',
},
undefined,
undefined,
),
).toThrow(
"Could not read default project 'tsconfig.json': `getParsedConfigFile` is only supported in a Node-like environment.",
"Could not readproject servicedefault project 'tsconfig.json': `getParsedConfigFile` is only supported in a Node-like environment.",
);
});

it('uses the defaultprojects compiler options when options.defaultProject is set and getParsedConfigFile succeeds', () => {
it('uses the defaultproject compiler options when options.defaultProject is set and getParsedConfigFile succeeds', () => {
const compilerOptions = { strict: true };
mockGetParsedConfigFile.mockReturnValue({ options: compilerOptions });
const defaultProject = 'tsconfig.eslint.json';

const { service } = createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: 'tsconfig.json',
defaultProject,
},
undefined,
undefined,
Expand All@@ -136,6 +158,12 @@ describe('createProjectService', () => {
expect(service.setCompilerOptionsForInferredProjects).toHaveBeenCalledWith(
compilerOptions,
);
expect(mockGetParsedConfigFile).toHaveBeenCalledWith(
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('typescript/lib/tsserverlibrary'),
defaultProject,
undefined,
);
});

it('uses tsconfigRootDir as getParsedConfigFile projectDirectory when provided', () => {
Expand All@@ -146,7 +174,6 @@ describe('createProjectService', () => {
const { service } = createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: 'tsconfig.json',
},
undefined,
tsconfigRootDir,
Expand All@@ -165,6 +192,7 @@ describe('createProjectService', () => {

it('uses the default projects error debugger for error messages when enabled', () => {
jest.spyOn(process.stderr, 'write').mockImplementation();

const { service } = createProjectService(undefined, undefined, undefined);
debug.enable('typescript-eslint:typescript-estree:tsserver:err');
const enabled = service.logger.loggingEnabled();
Expand All@@ -181,6 +209,7 @@ describe('createProjectService', () => {

it('does not use the default projects error debugger for error messages when disabled', () => {
jest.spyOn(process.stderr, 'write').mockImplementation();

const { service } = createProjectService(undefined, undefined, undefined);
const enabled = service.logger.loggingEnabled();
service.logger.msg('foo', ts.server.Msg.Err);
Expand All@@ -191,6 +220,7 @@ describe('createProjectService', () => {

it('uses the default projects info debugger for info messages when enabled', () => {
jest.spyOn(process.stderr, 'write').mockImplementation();

const { service } = createProjectService(undefined, undefined, undefined);
debug.enable('typescript-eslint:typescript-estree:tsserver:info');
const enabled = service.logger.loggingEnabled();
Expand All@@ -207,6 +237,7 @@ describe('createProjectService', () => {

it('does not use the default projects info debugger for info messages when disabled', () => {
jest.spyOn(process.stderr, 'write').mockImplementation();

const { service } = createProjectService(undefined, undefined, undefined);
const enabled = service.logger.loggingEnabled();
service.logger.info('foo');
Expand All@@ -217,6 +248,7 @@ describe('createProjectService', () => {

it('uses the default projects perf debugger for perf messages when enabled', () => {
jest.spyOn(process.stderr, 'write').mockImplementation();

const { service } = createProjectService(undefined, undefined, undefined);
debug.enable('typescript-eslint:typescript-estree:tsserver:perf');
const enabled = service.logger.loggingEnabled();
Expand All@@ -233,6 +265,7 @@ describe('createProjectService', () => {

it('does not use the default projects perf debugger for perf messages when disabled', () => {
jest.spyOn(process.stderr, 'write').mockImplementation();

const { service } = createProjectService(undefined, undefined, undefined);
const enabled = service.logger.loggingEnabled();
service.logger.perftrc('foo');
Expand Down
22 changes: 10 additions & 12 deletionspackages/typescript-estree/tests/lib/parse.project-true.test.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,17 +35,15 @@ describe('parseAndGenerateServices', () => {
});
});

if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') {
it('throws an error when a parent project does not exist', () => {
expect(() =>
parser.parseAndGenerateServices('const a = true', {
...config,
filePath: join(PROJECT_DIR, 'notIncluded.ts'),
}),
).toThrow(
/project was set to `true` but couldn't find any tsconfig.json relative to '.+[/\\]tests[/\\]fixtures[/\\]projectTrue[/\\]notIncluded.ts' within '.+[/\\]tests[/\\]fixtures[/\\]projectTrue'./,
);
});
}
it('throws an error when a parent project does not exist', () => {
expect(() =>
parser.parseAndGenerateServices('const a = true', {
...config,
filePath: join(PROJECT_DIR, 'notIncluded.ts'),
}),
).toThrow(
/project was set to `true` but couldn't find any tsconfig.json relative to '.+[/\\]tests[/\\]fixtures[/\\]projectTrue[/\\]notIncluded.ts' within '.+[/\\]tests[/\\]fixtures[/\\]projectTrue'./,
);
});
});
});
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp