Skip to content

Support dynamic file names with project root path #35111

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

Merged
merged 2 commits into from
Nov 14, 2019
Merged
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
12 changes: 8 additions & 4 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1141,7 +1141,7 @@ namespace ts.server {

const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) ||
this.getOrCreateSingleInferredProjectIfEnabled() ||
this.getOrCreateSingleInferredWithoutProjectRoot(info.isDynamic ? this.currentDirectory : getDirectoryPath(info.path));
this.getOrCreateSingleInferredWithoutProjectRoot(info.isDynamic ? projectRootPath || this.currentDirectory : getDirectoryPath(info.path));

project.addRoot(info);
if (info.containingProjects[0] !== project) {
Expand Down Expand Up @@ -1503,6 +1503,8 @@ namespace ts.server {

Debug.assert(!isOpenScriptInfo(info) || this.openFiles.has(info.path));
const projectRootPath = this.openFiles.get(info.path);
const scriptInfo = Debug.assertDefined(this.getScriptInfo(info.path));
if (scriptInfo.isDynamic) return undefined;

let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
const isSearchPathInProjectRoot = () => containsPath(projectRootPath!, searchPath, this.currentDirectory, !this.host.useCaseSensitiveFileNames);
Expand Down Expand Up @@ -1943,7 +1945,9 @@ namespace ts.server {
}

private getOrCreateInferredProjectForProjectRootPathIfEnabled(info: ScriptInfo, projectRootPath: NormalizedPath | undefined): InferredProject | undefined {
if (info.isDynamic || !this.useInferredProjectPerProjectRoot) {
if (!this.useInferredProjectPerProjectRoot ||
// Its a dynamic info opened without project root
(info.isDynamic && projectRootPath === undefined)) {
return undefined;
}

Expand Down Expand Up @@ -2228,7 +2232,7 @@ namespace ts.server {
const isDynamic = isDynamicFileName(fileName);
Debug.assert(isRootedDiskPath(fileName) || isDynamic || openedByClient, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nScript info with non-dynamic relative file name can only be open script info or in context of host currentDirectory`);
Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nOpen script files with non rooted disk path opened with current directory context cannot have same canonical names`);
Debug.assert(!isDynamic || this.currentDirectory === currentDirectory, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nDynamic files must always have current directory context since containing external project name will always match the script info name.`);
Debug.assert(!isDynamic || this.currentDirectory === currentDirectory || this.useInferredProjectPerProjectRoot, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.`);
// If the file is not opened by client and the file doesnot exist on the disk, return
if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) {
return;
Expand All @@ -2239,7 +2243,7 @@ namespace ts.server {
if (!openedByClient) {
this.watchClosedScriptInfo(info);
}
else if (!isRootedDiskPath(fileName) && !isDynamic) {
else if (!isRootedDiskPath(fileName) && (!isDynamic || this.currentDirectory !== currentDirectory)) {
// File that is opened by user but isn't rooted disk path
this.openFilesWithNonRootedDiskPath.set(this.toCanonicalFileName(fileName), info);
}
Expand Down
56 changes: 56 additions & 0 deletions src/testRunner/unittests/tsserver/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,62 @@ var x = 10;`
checkProjectActualFiles(project, [file.path, libFile.path]);
});

describe("dynamic file with projectRootPath", () => {
const file: File = {
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
content: "var x = 10;"
};
const configFile: File = {
path: `${projectRoot}/tsconfig.json`,
content: "{}"
};
const configProjectFile: File = {
path: `${projectRoot}/a.ts`,
content: "let y = 10;"
};
it("with useInferredProjectPerProjectRoot", () => {
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
const session = createSession(host, { useInferredProjectPerProjectRoot: true });
openFilesForSession([{ file: file.path, projectRootPath: projectRoot }], session);

const projectService = session.getProjectService();
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);

session.executeCommandSeq<protocol.OutliningSpansRequest>({
command: protocol.CommandTypes.GetOutliningSpans,
arguments: {
file: file.path
}
});

// Without project root
const file2Path = file.path.replace("#1", "#2");
projectService.openClientFile(file2Path, file.content);
checkNumberOfProjects(projectService, { inferredProjects: 2 });
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]);
});

it("fails when useInferredProjectPerProjectRoot is false", () => {
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
const projectService = createProjectService(host);
try {
projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, projectRoot);
}
catch (e) {
assert.strictEqual(
e.message.replace(/\r?\n/, "\n"),
`Debug Failure. False expression: \nVerbose Debug Information: {"fileName":"^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js","currentDirectory":"/user/username/projects/myproject","hostCurrentDirectory":"/","openKeys":[]}\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.`
);
}
const file2Path = file.path.replace("#1", "#2");
projectService.openClientFile(file2Path, file.content);
projectService.checkNumberOfProjects({ inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]);
});
});

it("files opened, closed affecting multiple projects", () => {
const file: File = {
path: "/a/b/projects/config/file.ts",
Expand Down