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

fix(typescript-estree): use simpler absolutify behavior for project service client file paths#8520

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
11 commits
Select commitHold shift + click to select a range
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: 1 addition & 0 deletions.cspell.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,7 @@
"blurple",
"bradzacher",
"camelcase",
"canonicalize",
"Cena",
"codebases",
"Codecov",
Expand Down
50 changes: 34 additions & 16 deletionspackages/typescript-estree/src/useProgramFromProjectService.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
import debug from 'debug';
import { minimatch } from 'minimatch';
import path from 'path';

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

const log = debug(
Expand All@@ -19,11 +16,17 @@ export function useProgramFromProjectService(
parseSettings: Readonly<MutableParseSettings>,
hasFullTypeInformation: boolean,
): ASTAndDefiniteProgram | undefined {
const filePath = getCanonicalFileName(parseSettings.filePath);
log('Opening project service file for: %s', filePath);
// 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);
log(
'Opening project service file for: %s at absolute path %s',
parseSettings.filePath,
filePathAbsolute,
);

const opened = service.openClientFile(
ensureAbsolutePath(filePath, service.host.getCurrentDirectory()),
filePathAbsolute,
parseSettings.codeFullText,
/* scriptKind */ undefined,
parseSettings.tsconfigRootDir,
Expand All@@ -36,36 +39,51 @@ export function useProgramFromProjectService(
'Project service type information enabled; checking for file path match on: %o',
allowDefaultProjectForFiles,
);
const isDefaultProjectAllowedPath = filePathMatchedBy(
parseSettings.filePath,
allowDefaultProjectForFiles,
);

log(
'Default project allowed path: %s, based on config file: %s',
isDefaultProjectAllowedPath,
opened.configFileName,
);

if (opened.configFileName) {
if (filePathMatchedBy(filePath, allowDefaultProjectForFiles)) {
if (isDefaultProjectAllowedPath) {
throw new Error(
`${filePath} was included by allowDefaultProjectForFiles but also was found in the project service. Consider removing it from allowDefaultProjectForFiles.`,
`${parseSettings.filePath} was included by allowDefaultProjectForFiles but also was found in the project service. Consider removing it from allowDefaultProjectForFiles.`,
);
}
} else if (!filePathMatchedBy(filePath, allowDefaultProjectForFiles)) {
} else if (!isDefaultProjectAllowedPath) {
throw new Error(
`${filePath} was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProjectForFiles.`,
`${parseSettings.filePath} was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProjectForFiles.`,
);
}
}
log('Retrieving script info and then program for: %s', filePathAbsolute);

log('Retrieving script info and then program for: %s', filePath);

const scriptInfo = service.getScriptInfo(filePath);
const scriptInfo = service.getScriptInfo(filePathAbsolute);
const program = service
.getDefaultProjectForFile(scriptInfo!.fileName, true)!
.getLanguageService(/*ensureSynchronized*/ true)
.getProgram();

if (!program) {
log('Could not find project service program for: %s',filePath);
log('Could not find project service program for: %s',filePathAbsolute);
return undefined;
}

log('Found project service program for: %s',filePath);
log('Found project service program for: %s',filePathAbsolute);

return createProjectProgram(parseSettings, [program]);

function absolutify(filePath: string): string {
return path.isAbsolute(filePath)
? filePath
: path.join(service.host.getCurrentDirectory(), filePath);
}
}

function filePathMatchedBy(
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type -- Fancy mocks */
import path from 'path';

import type { TypeScriptProjectService } from '../../src/create-program/createProjectService';
import type { ParseSettings } from '../../src/parseSettings';
import { useProgramFromProjectService } from '../../src/useProgramFromProjectService';

const mockCreateProjectProgram = jest.fn();

jest.mock('../../src/create-program/createProjectProgram', () => ({
get createProjectProgram() {
return mockCreateProjectProgram;
},
}));

const mockGetProgram = jest.fn();

const currentDirectory = '/repos/repo';

function createMockProjectService() {
const openClientFile = jest.fn();
const service = {
getDefaultProjectForFile: () => ({
getLanguageService: () => ({
getProgram: mockGetProgram,
}),
}),
getScriptInfo: () => ({}),
host: {
getCurrentDirectory: () => currentDirectory,
},
openClientFile,
};

return {
service: service as typeof service & TypeScriptProjectService,
openClientFile,
};
}

const mockParseSettings = {
filePath: 'path/PascalCaseDirectory/camelCaseFile.ts',
} as ParseSettings;

describe('useProgramFromProjectService', () => {
it('passes an absolute, case-matching file path to service.openClientFile', () => {
const { service } = createMockProjectService();

useProgramFromProjectService(
{ allowDefaultProjectForFiles: undefined, service },
mockParseSettings,
false,
);

expect(service.openClientFile).toHaveBeenCalledWith(
path.normalize('/repos/repo/path/PascalCaseDirectory/camelCaseFile.ts'),
undefined,
undefined,
undefined,
);
});

it('throws an error when hasFullTypeInformation is enabled and the file is both in the project service and allowDefaultProjectForFiles', () => {
const { service } = createMockProjectService();

service.openClientFile.mockReturnValueOnce({
configFileName: 'tsconfig.json',
});

expect(() =>
useProgramFromProjectService(
{ allowDefaultProjectForFiles: [mockParseSettings.filePath], service },
mockParseSettings,
true,
),
).toThrow(
`${mockParseSettings.filePath} was included by allowDefaultProjectForFiles but also was found in the project service. Consider removing it from allowDefaultProjectForFiles.`,
);
});

it('throws an error when hasFullTypeInformation is enabled and the file is neither in the project service nor allowDefaultProjectForFiles', () => {
const { service } = createMockProjectService();

service.openClientFile.mockReturnValueOnce({});

expect(() =>
useProgramFromProjectService(
{ allowDefaultProjectForFiles: [], service },
mockParseSettings,
true,
),
).toThrow(
`${mockParseSettings.filePath} was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProjectForFiles.`,
);
});

it('returns undefined when hasFullTypeInformation is disabled, the file is both in the project service and allowDefaultProjectForFiles, and the service does not have a matching program', () => {
const { service } = createMockProjectService();

mockGetProgram.mockReturnValue(undefined);

service.openClientFile.mockReturnValueOnce({
configFileName: 'tsconfig.json',
});

const actual = useProgramFromProjectService(
{ allowDefaultProjectForFiles: [mockParseSettings.filePath], service },
mockParseSettings,
false,
);

expect(actual).toBeUndefined();
});

it('returns a created program when hasFullTypeInformation is disabled, the file is both in the project service and allowDefaultProjectForFiles, and the service has a matching program', () => {
const { service } = createMockProjectService();
const program = { getSourceFile: jest.fn() };

mockGetProgram.mockReturnValue(program);

service.openClientFile.mockReturnValueOnce({
configFileName: 'tsconfig.json',
});
mockCreateProjectProgram.mockReturnValueOnce(program);

const actual = useProgramFromProjectService(
{ allowDefaultProjectForFiles: [mockParseSettings.filePath], service },
mockParseSettings,
false,
);

expect(actual).toBe(program);
});

it('returns a created program when hasFullTypeInformation is disabled, the file is neither in the project service nor allowDefaultProjectForFiles, and the service has a matching program', () => {
const { service } = createMockProjectService();
const program = { getSourceFile: jest.fn() };

mockGetProgram.mockReturnValue(program);

service.openClientFile.mockReturnValueOnce({});
mockCreateProjectProgram.mockReturnValueOnce(program);

const actual = useProgramFromProjectService(
{ allowDefaultProjectForFiles: [], service },
mockParseSettings,
false,
);

expect(actual).toBe(program);
});
});

[8]ページ先頭

©2009-2025 Movatter.jp