Skip to content

Resolve only relative references tree in the project (in syntax only mode) #39477

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
27 changes: 23 additions & 4 deletions src/compiler/resolutionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,21 @@ namespace ts {
type GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> =
(resolution: T) => R | undefined;

export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache {
export enum ResolutionKind {
All,
RelativeReferences
}

const noResolveResolvedModule: ResolvedModuleWithFailedLookupLocations = {
resolvedModule: undefined,
failedLookupLocations: []
};
const noResolveResolvedTypeReferenceDirective: ResolvedTypeReferenceDirectiveWithFailedLookupLocations = {
resolvedTypeReferenceDirective: undefined,
failedLookupLocations: []
};

export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, resolutionKind: ResolutionKind, logChangesWhenResolvingModule: boolean): ResolutionCache {
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
let filesWithInvalidatedResolutions: Set<Path> | undefined;
let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap<Path, readonly string[]> | undefined;
Expand Down Expand Up @@ -341,11 +355,12 @@ namespace ts {
shouldRetryResolution: (t: T) => boolean;
reusedNames?: readonly string[];
logChanges?: boolean;
noResolveResolution: T;
}
function resolveNamesWithLocalCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>({
names, containingFile, redirectedReference,
cache, perDirectoryCacheWithRedirects,
loader, getResolutionWithResolvedFileName,
loader, getResolutionWithResolvedFileName, noResolveResolution,
shouldRetryResolution, reusedNames, logChanges
}: ResolveNamesWithLocalCacheInput<T, R>): (R | undefined)[] {
const path = resolutionHost.toPath(containingFile);
Expand Down Expand Up @@ -382,7 +397,9 @@ namespace ts {
resolution = resolutionInDirectory;
}
else {
resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference);
resolution = resolutionKind === ResolutionKind.All || isExternalModuleNameRelative(name) ?
loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference) :
noResolveResolution;
perDirectoryResolution.set(name, resolution);
}
resolutionsInFile.set(name, resolution);
Expand Down Expand Up @@ -441,6 +458,7 @@ namespace ts {
loader: resolveTypeReferenceDirective,
getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective,
shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined,
noResolveResolution: noResolveResolvedTypeReferenceDirective,
});
}

Expand All @@ -455,7 +473,8 @@ namespace ts {
getResolutionWithResolvedFileName: getResolvedModule,
shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
reusedNames,
logChanges: logChangesWhenResolvingModule
logChanges: logChangesWhenResolvingModule,
noResolveResolution: noResolveResolvedModule,
});
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/watchPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ namespace ts {
configFileName ?
getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) :
currentDirectory,
ResolutionKind.All,
/*logChangesWhenResolvingModule*/ false
);
// Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names
Expand Down
9 changes: 6 additions & 3 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,8 @@ namespace ts.server {

this.languageServiceEnabled = true;
if (projectService.syntaxOnly) {
this.compilerOptions.noResolve = true;
this.compilerOptions.types = [];
}

this.setInternalCompilerOptionsForEmittingJsFiles();
const host = this.projectService.host;
if (this.projectService.logger.loggingEnabled()) {
Expand All @@ -296,7 +294,12 @@ namespace ts.server {
this.realpath = maybeBind(host, host.realpath);

// Use the current directory as resolution root only if the project created using current directory string
this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory, /*logChangesWhenResolvingModule*/ true);
this.resolutionCache = createResolutionCache(
this,
currentDirectory && this.currentDirectory,
projectService.syntaxOnly ? ResolutionKind.RelativeReferences : ResolutionKind.All,
/*logChangesWhenResolvingModule*/ true
);
this.languageService = createLanguageService(this, this.documentRegistry, this.projectService.syntaxOnly);
if (lastFileExceededProgramSize) {
this.disableLanguageService(lastFileExceededProgramSize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,67 @@ namespace ts.projectSystem {
function setup() {
const file1: File = {
path: `${tscWatch.projectRoot}/a.ts`,
content: `import { y } from "./b";
content: `import { y, cc } from "./b";
import { something } from "something";
class c { prop = "hello"; foo() { return this.prop; } }`
};
const file2: File = {
path: `${tscWatch.projectRoot}/b.ts`,
content: "export const y = 10;"
content: `export { cc } from "./c";
import { something } from "something";
export const y = 10;`
};
const file3: File = {
path: `${tscWatch.projectRoot}/c.ts`,
content: `export const cc = 10;`
};
const something: File = {
path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`,
content: "export const something = 10;"
};
const configFile: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
const host = createServerHost([file1, file2, libFile, configFile]);
const host = createServerHost([file1, file2, file3, something, libFile, configFile]);
const session = createSession(host, { syntaxOnly: true, useSingleInferredProject: true });
return { host, session, file1, file2, configFile };
return { host, session, file1, file2, file3, something, configFile };
}

it("open files are added to inferred project even if config file is present and semantic operations succeed", () => {
const { host, session, file1, file2 } = setup();
const { host, session, file1, file2, file3, something } = setup();
const service = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path]); // Import is not resolved
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]); // Relative import is resolved but not non relative
verifyCompletions();
verifyGoToDefToB();
verifyGoToDefToC();

openFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]);
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]);
verifyCompletions();
verifyGoToDefToB();
verifyGoToDefToC();

openFilesForSession([file3], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]);

openFilesForSession([something], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);

// Close open files and verify resolutions
closeFilesForSession([file3], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);

closeFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);

function verifyCompletions() {
assert.isTrue(project.languageServiceEnabled);
Expand Down Expand Up @@ -62,6 +94,34 @@ class c { prop = "hello"; foo() { return this.prop; } }`
source: undefined
};
}

function verifyGoToDefToB() {
const response = session.executeCommandSeq<protocol.DefinitionAndBoundSpanRequest>({
command: protocol.CommandTypes.DefinitionAndBoundSpan,
arguments: protocolFileLocationFromSubstring(file1, "y")
}).response as protocol.DefinitionInfoAndBoundSpan;
assert.deepEqual(response, {
definitions: [{
file: file2.path,
...protocolTextSpanWithContextFromSubstring({ fileText: file2.content, text: "y", contextText: "export const y = 10;" })
}],
textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "y" })
});
}

function verifyGoToDefToC() {
const response = session.executeCommandSeq<protocol.DefinitionAndBoundSpanRequest>({
command: protocol.CommandTypes.DefinitionAndBoundSpan,
arguments: protocolFileLocationFromSubstring(file1, "cc")
}).response as protocol.DefinitionInfoAndBoundSpan;
assert.deepEqual(response, {
definitions: [{
file: file3.path,
...protocolTextSpanWithContextFromSubstring({ fileText: file3.content, text: "cc", contextText: "export const cc = 10;" })
}],
textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "cc" })
});
}
});

it("throws on unsupported commands", () => {
Expand Down Expand Up @@ -97,7 +157,7 @@ class c { prop = "hello"; foo() { return this.prop; } }`
});

it("should not include auto type reference directives", () => {
const { host, session, file1 } = setup();
const { host, session, file1, file2, file3 } = setup();
const atTypes: File = {
path: `/node_modules/@types/somemodule/index.d.ts`,
content: "export const something = 10;"
Expand All @@ -107,7 +167,7 @@ class c { prop = "hello"; foo() { return this.prop; } }`
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path]); // Should not contain atTypes
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]); // Should not contain atTypes
});
});
}