Skip to content

Commit

Permalink
don't treat client files as root in configured service so it can be r…
Browse files Browse the repository at this point in the history
…emoved from the service
  • Loading branch information
jasonlyu123 committed Sep 16, 2024
1 parent 07691a6 commit 318f56d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 23 deletions.
42 changes: 20 additions & 22 deletions packages/language-server/src/plugins/typescript/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ const dependedConfigWatchers = new FileMap<ts.FileWatcher>();
const configPathToDependedProject = new FileMap<FileSet>();
const configFileModifiedTime = new FileMap<Date | undefined>();
const configFileForOpenFiles = new FileMap<string>();
const containingServices = new FileMap<string>();
const pendingReloads = new FileSet();
const documentRegistries = new Map<string, ts.DocumentRegistry>();
const pendingForAllServices = new Set<Promise<void>>();
Expand Down Expand Up @@ -172,24 +171,19 @@ export async function getService(
* Prevent infinite loop when the project reference is circular
*/
const triedTsConfig = new Set<string>();
const possibleConfigPath = new Set<string>();
const needAssign = !configFileForOpenFiles.has(path);
let service = await getConfiguredService(tsconfigPath);
if (!needAssign) {
return service;
}

const defaultService = await findDefaultServiceForFile(
service,
triedTsConfig,
possibleConfigPath
);
const defaultService = await findDefaultServiceForFile(service, triedTsConfig);
if (defaultService) {
configFileForOpenFiles.set(path, defaultService.tsconfigPath);
return defaultService;
}

for (const configPath of possibleConfigPath) {
for (const configPath of triedTsConfig) {
const service = await getConfiguredService(configPath);
const ls = service.getService();
if (ls.getProgram()?.getSourceFile(path)) {
Expand Down Expand Up @@ -225,8 +219,7 @@ export async function getService(

async function findDefaultServiceForFile(
service: LanguageServiceContainer,
triedTsConfig: Set<string>,
possibleConfigPath: Set<string>
triedTsConfig: Set<string>
): Promise<LanguageServiceContainer | undefined> {
service.ensureProjectFileUpdates();
if (service.snapshotManager.isProjectFile(path)) {
Expand All @@ -236,16 +229,15 @@ export async function getService(
return;
}

possibleConfigPath.add(service.tsconfigPath);
triedTsConfig.add(service.tsconfigPath);

// TODO: maybe add support for ts 5.6's ancestor searching
return findDefaultFromProjectReferences(service, triedTsConfig, possibleConfigPath);
return findDefaultFromProjectReferences(service, triedTsConfig);
}

async function findDefaultFromProjectReferences(
service: LanguageServiceContainer,
triedTsConfig: Set<string>,
possibleConfigPath: Set<string>
triedTsConfig: Set<string>
) {
const projectReferences = service.getResolvedProjectReferences();
if (projectReferences.length === 0) {
Expand All @@ -265,11 +257,7 @@ export async function getService(

for (const ref of possibleSubPaths) {
const subService = await getConfiguredService(ref);
const defaultService = await findDefaultServiceForFile(
subService,
triedTsConfig,
possibleConfigPath
);
const defaultService = await findDefaultServiceForFile(subService, triedTsConfig);
if (defaultService) {
return defaultService;
}
Expand Down Expand Up @@ -342,6 +330,7 @@ async function createLanguageService(
const projectConfig = getParsedConfig();
const { options: compilerOptions, raw, errors: configErrors } = projectConfig;
const allowJs = compilerOptions.allowJs ?? !!compilerOptions.checkJs;
const virtualDocuments = new FileMap<Document>(tsSystem.useCaseSensitiveFileNames);

const getCanonicalFileName = createGetCanonicalFileName(tsSystem.useCaseSensitiveFileNames);
watchWildCardDirectories(projectConfig);
Expand Down Expand Up @@ -675,14 +664,22 @@ async function createLanguageService(
: snapshotManager.getProjectFileNames();
const canonicalProjectFileNames = new Set(projectFiles.map(getCanonicalFileName));

// We only assign project files or files already in the program to the language service
// so don't need to include other client files otherwise it will stay in the program and not be removed
const clientFiles = tsconfigPath
? Array.from(virtualDocuments.values())
.map((v) => v.getFilePath())
.filter(isNotNullOrUndefined)
: snapshotManager.getClientFileNames();

return Array.from(
new Set([
...projectFiles,
// project file is read from the file system so it's more likely to have
// the correct casing
...snapshotManager
.getClientFileNames()
.filter((file) => !canonicalProjectFileNames.has(getCanonicalFileName(file))),
...clientFiles.filter(
(file) => !canonicalProjectFileNames.has(getCanonicalFileName(file))
),
...svelteTsxFiles
])
);
Expand Down Expand Up @@ -1188,6 +1185,7 @@ async function createLanguageService(
if (!filePath) {
return;
}
virtualDocuments.set(filePath, document);
configFileForOpenFiles.set(filePath, tsconfigPath || workspacePath);
updateSnapshot(document);
scheduleUpdate(filePath);
Expand Down
55 changes: 54 additions & 1 deletion packages/language-server/test/plugins/typescript/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,10 +604,63 @@ describe('service', () => {
sinon.assert.calledWith(watchDirectory.firstCall, <RelativePattern[]>[]);
});

it('assigns files to service with the file in the program', async () => {
const dirPath = getRandomVirtualDirPath(testDir);
const { virtualSystem, lsDocumentContext, rootUris } = setup();

const tsconfigPath = path.join(dirPath, 'tsconfig.json');
virtualSystem.writeFile(
tsconfigPath,
JSON.stringify({
compilerOptions: <ts.CompilerOptions>{
noImplicitOverride: true
},
include: ['src/*.ts']
})
);

const referencedFile = path.join(dirPath, 'anotherPackage', 'index.svelte');
const tsFilePath = path.join(dirPath, 'src', 'random.ts');

virtualSystem.readDirectory = () => [tsFilePath];
virtualSystem.writeFile(
referencedFile,
'<script lang="ts">class A { a =1 }; class B extends A { a =2 };</script>'
);
virtualSystem.writeFile(tsFilePath, 'import "../anotherPackage/index.svelte";');

const document = new Document(
pathToUrl(referencedFile),
virtualSystem.readFile(referencedFile)!
);
document.openedByClient = true;
const ls = await getService(referencedFile, rootUris, lsDocumentContext);
ls.updateSnapshot(document);

assert.equal(normalizePath(ls.tsconfigPath), normalizePath(tsconfigPath));

const noImplicitOverrideErrorCode = 4114;
const findError = (ls: LanguageServiceContainer) =>
ls
.getService()
.getSemanticDiagnostics(referencedFile)
.find((f) => f.code === noImplicitOverrideErrorCode);

assert.ok(findError(ls));

virtualSystem.writeFile(tsFilePath, '');
ls.updateTsOrJsFile(tsFilePath);

const ls2 = await getService(referencedFile, rootUris, lsDocumentContext);
ls2.updateSnapshot(document);

assert.deepStrictEqual(findError(ls2), undefined);
});

function getSemanticDiagnosticsMessages(ls: LanguageServiceContainer, filePath: string) {
return ls
.getService()
.getSemanticDiagnostics(filePath)
.map((d) => d.messageText);
.map((d) => ts.flattenDiagnosticMessageText(d.messageText, '\n'));
}
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"allowJs": true,
/**
This is actually not needed, but makes the tests faster
because TS does not look up other types.
Expand Down

0 comments on commit 318f56d

Please sign in to comment.