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): add allowDefaultProjectForFiles project service allowlist option#7752

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
Show all changes
17 commits
Select commitHold shift + click to select a range
6fed396
fix(typescript-estree): allow project service for unknown client file
JoshuaKGoldbergOct 15, 2023
2f0f053
Merge branch 'main' into project-service-allow-no-client-file
JoshuaKGoldbergOct 15, 2023
f8e275d
Fixed up project throwing tests
JoshuaKGoldbergOct 15, 2023
3d083d6
Added allowDefaultProjectForFiles as nested option
JoshuaKGoldbergNov 7, 2023
7130d6c
Added a bit more testing
JoshuaKGoldbergNov 13, 2023
d639ef1
Merge branch 'main' into project-service-allow-no-client-file
JoshuaKGoldbergNov 13, 2023
bf1d150
fix: only throw on missing file path if hasFullTypeInformation
JoshuaKGoldbergNov 13, 2023
8c5c67d
Merge branch 'main'
JoshuaKGoldbergNov 14, 2023
0cf53d3
Fix test snapshot
JoshuaKGoldbergNov 14, 2023
4766f76
Remove unnecessary assertion
JoshuaKGoldbergNov 17, 2023
adba9ba
Merge branch 'main'
JoshuaKGoldbergDec 6, 2023
e9e475f
TypeScript_ESTree.mdx docs
JoshuaKGoldbergDec 6, 2023
23a32b1
Absolute and canonical file paths
JoshuaKGoldbergDec 6, 2023
294a957
Use minimatch instead of fs globbing
JoshuaKGoldbergDec 24, 2023
3f1eea1
lint: import sorting, missing return type
JoshuaKGoldbergDec 24, 2023
f3700a4
Ignore some tests
JoshuaKGoldbergDec 24, 2023
2ce23c0
Merge branch 'main' into project-service-allow-no-client-file
JoshuaKGoldbergDec 24, 2023
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
12 changes: 11 additions & 1 deletiondocs/packages/TypeScript_ESTree.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -167,7 +167,7 @@ interface ParseAndGenerateServicesOptions extends ParseOptions {
*
* @see https://github.com/typescript-eslint/typescript-eslint/issues/6575
*/
EXPERIMENTAL_useProjectService?: boolean;
EXPERIMENTAL_useProjectService?: boolean | ProjectServiceOptions;

/**
* ***EXPERIMENTAL FLAG*** - Use this at your own risk.
Expand DownExpand Up@@ -270,6 +270,16 @@ interface ParseAndGenerateServicesOptions extends ParseOptions {
};
}

/**
* Granular options to configure the project service.
*/
interface ProjectServiceOptions {
/**
* Globs of files to allow running with the default inferred project settings.
*/
allowDefaultProjectForFiles?: string[];
}

interface ParserServices {
program: ts.Program;
esTreeNodeToTSNodeMap: WeakMap<TSESTree.Node, ts.Node | ts.Token>;
Expand Down
126 changes: 64 additions & 62 deletionspackages/eslint-plugin-tslint/tests/index.spec.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -166,71 +166,73 @@ ruleTester.run('tslint/config', rule, {
],
});

describe('tslint/error', () => {
function testOutput(code: string, config: ClassicConfig.Config): void {
const linter = new TSESLint.Linter();
linter.defineRule('tslint/config', rule);
linter.defineParser('@typescript-eslint/parser', parser);

expect(() => linter.verify(code, config)).toThrow(
'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.',
);
}

it('should error on missing project', () => {
testOutput('foo;', {
rules: {
'tslint/config': [2, tslintRulesConfig],
},
parser: '@typescript-eslint/parser',
if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') {
describe('tslint/error', () => {
function testOutput(code: string, config: ClassicConfig.Config): void {
const linter = new TSESLint.Linter();
linter.defineRule('tslint/config', rule);
linter.defineParser('@typescript-eslint/parser', parser);

expect(() => linter.verify(code, config)).toThrow(
'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.',
);
}

it('should error on missing project', () => {
testOutput('foo;', {
rules: {
'tslint/config': [2, tslintRulesConfig],
},
parser: '@typescript-eslint/parser',
});
});
});

it('should error on default parser', () => {
testOutput('foo;', {
parserOptions: {
project: TEST_PROJECT_PATH,
},
rules: {
'tslint/config': [2, tslintRulesConfig],
},
it('should error on default parser', () => {
testOutput('foo;', {
parserOptions: {
project: TEST_PROJECT_PATH,
},
rules: {
'tslint/config': [2, tslintRulesConfig],
},
});
});
});

it('should not crash if there are no tslint rules specified', () => {
const linter = new TSESLint.Linter();
jest.spyOn(console, 'warn').mockImplementation();
linter.defineRule('tslint/config', rule);
linter.defineParser('@typescript-eslint/parser', parser);

const filePath = path.resolve(
__dirname,
'fixtures',
'test-project',
'extra.ts',
);

expect(() =>
linter.verify(
'foo;',
{
parserOptions: {
project: TEST_PROJECT_PATH,
},
rules: {
'tslint/config': [2, {}],
it('should not crash if there are no tslint rules specified', () => {
const linter = new TSESLint.Linter();
jest.spyOn(console, 'warn').mockImplementation();
linter.defineRule('tslint/config', rule);
linter.defineParser('@typescript-eslint/parser', parser);

const filePath = path.resolve(
__dirname,
'fixtures',
'test-project',
'extra.ts',
);

expect(() =>
linter.verify(
'foo;',
{
parserOptions: {
project: TEST_PROJECT_PATH,
},
rules: {
'tslint/config': [2, {}],
},
parser: '@typescript-eslint/parser',
},
parser: '@typescript-eslint/parser',
},
filePath,
),
).not.toThrow();

expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining(
`Tried to lint ${filePath} but found no valid, enabled rules for this file type and file path in the resolved configuration.`,
),
);
jest.resetAllMocks();
filePath,
),
).not.toThrow();

expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining(
`Tried to lint ${filePath} but found no valid, enabled rules for this file type and file path in the resolved configuration.`,
),
);
jest.resetAllMocks();
});
});
});
}
1 change: 1 addition & 0 deletionspackages/typescript-estree/package.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/no-empty-function -- for TypeScript APIs*/
import type * as ts from 'typescript/lib/tsserverlibrary';

// eslint-disable-next-line @typescript-eslint/no-empty-function
import type { ProjectServiceOptions } from '../parser-options';

const doNothing = (): void => {};

const createStubFileWatcher = (): ts.FileWatcher => ({
Expand All@@ -9,9 +11,15 @@ const createStubFileWatcher = (): ts.FileWatcher => ({

export type TypeScriptProjectService = ts.server.ProjectService;

export interface ProjectServiceSettings {
allowDefaultProjectForFiles: string[] | undefined;
service: TypeScriptProjectService;
}

export function createProjectService(
jsDocParsingMode?: ts.JSDocParsingMode,
): TypeScriptProjectService {
options: boolean | ProjectServiceOptions | undefined,
jsDocParsingMode: ts.JSDocParsingMode | undefined,
): ProjectServiceSettings {
// 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
const tsserver = require('typescript/lib/tsserverlibrary') as typeof ts;
Expand All@@ -30,7 +38,7 @@ export function createProjectService(
watchFile: createStubFileWatcher,
};

return new tsserver.server.ProjectService({
const service = new tsserver.server.ProjectService({
host: system,
cancellationToken: { isCancellationRequested: (): boolean => false },
useSingleInferredProject: false,
Expand All@@ -49,4 +57,12 @@ export function createProjectService(
session: undefined,
jsDocParsingMode,
});

return {
allowDefaultProjectForFiles:
typeof options === 'object'
? options.allowDefaultProjectForFiles
: undefined,
service,
};
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import debug from 'debug';
import * as ts from 'typescript';

import type {TypeScriptProjectService } from '../create-program/createProjectService';
import type {ProjectServiceSettings } from '../create-program/createProjectService';
import { createProjectService } from '../create-program/createProjectService';
import { ensureAbsolutePath } from '../create-program/shared';
import type { TSESTreeOptions } from '../parser-options';
Expand All@@ -21,7 +21,7 @@ const log = debug(
);

let TSCONFIG_MATCH_CACHE: ExpiringCache<string, string> | null;
let TSSERVER_PROJECT_SERVICE:TypeScriptProjectService | null = null;
let TSSERVER_PROJECT_SERVICE:ProjectServiceSettings | 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
Expand DownExpand Up@@ -80,11 +80,14 @@ export function createParseSettings(
errorOnTypeScriptSyntacticAndSemanticIssues: false,
errorOnUnknownASTType: options.errorOnUnknownASTType === true,
EXPERIMENTAL_projectService:
(options.EXPERIMENTAL_useProjectService=== true&&
(options.EXPERIMENTAL_useProjectService &&
process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'false') ||
(process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true' &&
options.EXPERIMENTAL_useProjectService !== false)
? (TSSERVER_PROJECT_SERVICE ??= createProjectService(jsDocParsingMode))
? (TSSERVER_PROJECT_SERVICE ??= createProjectService(
options.EXPERIMENTAL_useProjectService,
jsDocParsingMode,
))
: undefined,
EXPERIMENTAL_useSourceOfProjectReferenceRedirect:
options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === true,
Expand Down
6 changes: 2 additions & 4 deletionspackages/typescript-estree/src/parseSettings/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type * as ts from 'typescript';
import type * as tsserverlibrary from 'typescript/lib/tsserverlibrary';

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';
Expand DownExpand Up@@ -67,9 +67,7 @@ export interface MutableParseSettings {
/**
* Experimental: TypeScript server to power program creation.
*/
EXPERIMENTAL_projectService:
| tsserverlibrary.server.ProjectService
| undefined;
EXPERIMENTAL_projectService: ProjectServiceSettings | undefined;

/**
* Whether TS should use the source files for referenced projects instead of the compiled .d.ts files.
Expand Down
12 changes: 11 additions & 1 deletionpackages/typescript-estree/src/parser-options.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,6 +101,16 @@ interface ParseOptions {
suppressDeprecatedPropertyWarnings?: boolean;
}

/**
* Granular options to configure the project service.
*/
export interface ProjectServiceOptions {
/**
* Globs of files to allow running with the default inferred project settings.
*/
allowDefaultProjectForFiles?: string[];
}

interface ParseAndGenerateServicesOptions extends ParseOptions {
/**
* Causes the parser to error if the TypeScript compiler returns any unexpected syntax/semantic errors.
Expand All@@ -114,7 +124,7 @@ interface ParseAndGenerateServicesOptions extends ParseOptions {
*
* @see https://github.com/typescript-eslint/typescript-eslint/issues/6575
*/
EXPERIMENTAL_useProjectService?: boolean;
EXPERIMENTAL_useProjectService?: boolean | ProjectServiceOptions;

/**
* ***EXPERIMENTAL FLAG*** - Use this at your own risk.
Expand Down
1 change: 1 addition & 0 deletionspackages/typescript-estree/src/parser.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,7 @@ function getProgramAndAST(
const fromProjectService = useProgramFromProjectService(
parseSettings.EXPERIMENTAL_projectService,
parseSettings,
hasFullTypeInformation,
);
if (fromProjectService) {
return fromProjectService;
Expand Down
51 changes: 36 additions & 15 deletionspackages/typescript-estree/src/useProgramFromProjectService.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
import path from 'path';
import type { server } from 'typescript/lib/tsserverlibrary';
import { minimatch } from 'minimatch';

import { createProjectProgram } from './create-program/createProjectProgram';
import { type ASTAndDefiniteProgram } from './create-program/shared';
import type { ProjectServiceSettings } from './create-program/createProjectService';
import {
type ASTAndDefiniteProgram,
ensureAbsolutePath,
getCanonicalFileName,
} from './create-program/shared';
import type { MutableParseSettings } from './parseSettings';

export function useProgramFromProjectService(
projectService: server.ProjectService,
{ allowDefaultProjectForFiles, service }: ProjectServiceSettings,
parseSettings: Readonly<MutableParseSettings>,
hasFullTypeInformation: boolean,
): ASTAndDefiniteProgram | undefined {
const opened = projectService.openClientFile(
absolutify(parseSettings.filePath),
const filePath = getCanonicalFileName(parseSettings.filePath);

const opened = service.openClientFile(
ensureAbsolutePath(filePath, service.host.getCurrentDirectory()),
parseSettings.codeFullText,
/* scriptKind */ undefined,
parseSettings.tsconfigRootDir,
);
if (!opened.configFileName) {
return undefined;

if (hasFullTypeInformation) {
if (opened.configFileName) {
if (filePathMatchedBy(filePath, allowDefaultProjectForFiles)) {
throw new Error(
`${filePath} was included by allowDefaultProjectForFiles but also was found in the project service. Consider removing it from allowDefaultProjectForFiles.`,
);
}
} else if (!filePathMatchedBy(filePath, allowDefaultProjectForFiles)) {
throw new Error(
`${filePath} was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProjectForFiles.`,
);
}
}

const scriptInfo =projectService.getScriptInfo(parseSettings.filePath);
const program =projectService
const scriptInfo =service.getScriptInfo(filePath);
const program =service
.getDefaultProjectForFile(scriptInfo!.fileName, true)!
.getLanguageService(/*ensureSynchronized*/ true)
.getProgram();
Expand All@@ -30,10 +48,13 @@ export function useProgramFromProjectService(
}

return createProjectProgram(parseSettings, [program]);
}

function absolutify(filePath: string): string {
return path.isAbsolute(filePath)
? filePath
: path.join(projectService.host.getCurrentDirectory(), filePath);
}
function filePathMatchedBy(
filePath: string,
allowDefaultProjectForFiles: string[] | undefined,
): boolean {
return !!allowDefaultProjectForFiles?.some(pattern =>
minimatch(filePath, pattern),
);
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp