Handle walkThroughSnippet:/ and untitled:/ as dynamic files (#36722)

Handle walkThroughSnippet:/ and untitled:/ as dynamic files
Fixes #36681
This commit is contained in:
Sheetal Nandi 2020-02-10 13:55:43 -08:00 committed by GitHub
parent fa3173f8f6
commit 70e6f5b8a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 217 additions and 199 deletions

View File

@ -273,6 +273,8 @@ namespace ts.server {
/*@internal*/
export function isDynamicFileName(fileName: NormalizedPath) {
return fileName[0] === "^" ||
((stringContains(fileName, "walkThroughSnippet:/") || stringContains(fileName, "untitled:/")) &&
getBaseFileName(fileName)[0] === "^") ||
(stringContains(fileName, ":^") && !stringContains(fileName, directorySeparator));
}

View File

@ -149,6 +149,7 @@
"unittests/tsserver/declarationFileMaps.ts",
"unittests/tsserver/documentRegistry.ts",
"unittests/tsserver/duplicatePackages.ts",
"unittests/tsserver/dynamicFiles.ts",
"unittests/tsserver/events/largeFileReferenced.ts",
"unittests/tsserver/events/projectLanguageServiceState.ts",
"unittests/tsserver/events/projectLoading.ts",
@ -189,7 +190,6 @@
"unittests/tsserver/typeOnlyImportChains.ts",
"unittests/tsserver/typeReferenceDirectives.ts",
"unittests/tsserver/typingsInstaller.ts",
"unittests/tsserver/untitledFiles.ts",
"unittests/tsserver/versionCache.ts",
"unittests/tsserver/watchEnvironment.ts"
]

View File

@ -0,0 +1,214 @@
namespace ts.projectSystem {
export function verifyDynamic(service: server.ProjectService, path: string) {
const info = Debug.assertDefined(service.filenameToScriptInfo.get(path), `Expected ${path} in :: ${JSON.stringify(arrayFrom(service.filenameToScriptInfo.entries(), ([key, f]) => ({ key, fileName: f.fileName, path: f.path })))}`);
assert.isTrue(info.isDynamic);
}
function verifyPathRecognizedAsDynamic(path: string) {
const file: File = {
path,
content: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />
/// <reference path="../../../../../../typings/@epic/Shell.d.ts" />
var x = 10;`
};
const host = createServerHost([libFile]);
const projectService = createProjectService(host);
projectService.openClientFile(file.path, file.content);
verifyDynamic(projectService, projectService.toPath(file.path));
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const project = projectService.inferredProjects[0];
checkProjectRootFiles(project, [file.path]);
checkProjectActualFiles(project, [file.path, libFile.path]);
}
describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => {
const untitledFile = "untitled:^Untitled-1";
it("Can convert positions to locations", () => {
const aTs: File = { path: "/proj/a.ts", content: "" };
const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };
const session = createSession(createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true });
openFilesForSession([aTs], session);
executeSessionRequestNoResponse<protocol.OpenRequest>(session, protocol.CommandTypes.Open, {
file: untitledFile,
fileContent: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />\nlet foo = 1;\nfooo/**/`,
scriptKindName: "TS",
projectRootPath: "/proj",
});
verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`);
const response = executeSessionRequest<protocol.CodeFixRequest, protocol.CodeFixResponse>(session, protocol.CommandTypes.GetCodeFixes, {
file: untitledFile,
startLine: 3,
startOffset: 1,
endLine: 3,
endOffset: 5,
errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code],
});
assert.deepEqual<readonly protocol.CodeFixAction[] | undefined>(response, [
{
description: "Change spelling to 'foo'",
fixName: "spelling",
changes: [{
fileName: untitledFile,
textChanges: [{
start: { line: 3, offset: 1 },
end: { line: 3, offset: 5 },
newText: "foo",
}],
}],
commands: undefined,
fixId: undefined,
fixAllDescription: undefined
},
]);
});
it("opening untitled files", () => {
const config: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: tscWatch.projectRoot });
const service = createProjectService(host);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`);
const untitled: File = {
path: `${tscWatch.projectRoot}/Untitled-1.ts`,
content: "const x = 10;"
};
host.writeFile(untitled.path, untitled.content);
host.checkTimeoutQueueLength(0);
service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, tscWatch.projectRoot);
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
service.closeClientFile(untitledFile);
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot);
verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`);
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
});
});
describe("unittests:: tsserver:: dynamicFiles:: ", () => {
it("dynamic file without external project", () => {
const file: File = {
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
content: "var x = 10;"
};
const host = createServerHost([libFile], { useCaseSensitiveFileNames: true });
const projectService = createProjectService(host);
projectService.setCompilerOptionsForInferredProjects({
module: ModuleKind.CommonJS,
allowJs: true,
allowSyntheticDefaultImports: true,
allowNonTsExtensions: true
});
projectService.openClientFile(file.path, "var x = 10;");
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const project = projectService.inferredProjects[0];
checkProjectRootFiles(project, [file.path]);
checkProjectActualFiles(project, [file.path, libFile.path]);
verifyDynamic(projectService, `/${file.path}`);
assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project);
const indexOfX = file.content.indexOf("x");
assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), {
kind: ScriptElementKind.variableElement,
kindModifiers: "",
textSpan: { start: indexOfX, length: 1 },
displayParts: [
{ text: "var", kind: "keyword" },
{ text: " ", kind: "space" },
{ text: "x", kind: "localName" },
{ text: ":", kind: "punctuation" },
{ text: " ", kind: "space" },
{ text: "number", kind: "keyword" }
],
documentation: [],
tags: undefined,
});
});
it("dynamic file with reference paths without external project", () => {
verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js");
});
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: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
const configProjectFile: File = {
path: `${tscWatch.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: tscWatch.projectRoot }], session);
const projectService = session.getProjectService();
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.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, tscWatch.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]);
});
});
describe("verify accepts known schemas as dynamic file", () => {
it("walkThroughSnippet", () => {
verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts");
});
it("untitled", () => {
verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts");
});
});
});
}

View File

@ -1075,121 +1075,6 @@ namespace ts.projectSystem {
assert.equal(options.outDir, "C:/a/b", "");
});
it("dynamic file without external project", () => {
const file: File = {
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
content: "var x = 10;"
};
const host = createServerHost([libFile], { useCaseSensitiveFileNames: true });
const projectService = createProjectService(host);
projectService.setCompilerOptionsForInferredProjects({
module: ModuleKind.CommonJS,
allowJs: true,
allowSyntheticDefaultImports: true,
allowNonTsExtensions: true
});
projectService.openClientFile(file.path, "var x = 10;");
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const project = projectService.inferredProjects[0];
checkProjectRootFiles(project, [file.path]);
checkProjectActualFiles(project, [file.path, libFile.path]);
verifyDynamic(projectService, `/${file.path}`);
assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project);
const indexOfX = file.content.indexOf("x");
assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), {
kind: ScriptElementKind.variableElement,
kindModifiers: "",
textSpan: { start: indexOfX, length: 1 },
displayParts: [
{ text: "var", kind: "keyword" },
{ text: " ", kind: "space" },
{ text: "x", kind: "localName" },
{ text: ":", kind: "punctuation" },
{ text: " ", kind: "space" },
{ text: "number", kind: "keyword" }
],
documentation: [],
tags: undefined,
});
});
it("dynamic file with reference paths without external project", () => {
const file: File = {
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
content: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />
/// <reference path="../../../../../../typings/@epic/Shell.d.ts" />
var x = 10;`
};
const host = createServerHost([libFile]);
const projectService = createProjectService(host);
projectService.openClientFile(file.path, file.content);
verifyDynamic(projectService, projectService.toPath(file.path));
projectService.checkNumberOfProjects({ inferredProjects: 1 });
const project = projectService.inferredProjects[0];
checkProjectRootFiles(project, [file.path]);
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: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
const configProjectFile: File = {
path: `${tscWatch.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: tscWatch.projectRoot }], session);
const projectService = session.getProjectService();
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.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, tscWatch.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",

View File

@ -1,83 +0,0 @@
namespace ts.projectSystem {
export function verifyDynamic(service: server.ProjectService, path: string) {
const info = Debug.assertDefined(service.filenameToScriptInfo.get(path), `Expected ${path} in :: ${JSON.stringify(arrayFrom(service.filenameToScriptInfo.entries(), ([key, f]) => ({ key, fileName: f.fileName, path: f.path })))}`);
assert.isTrue(info.isDynamic);
}
describe("unittests:: tsserver:: Untitled files", () => {
const untitledFile = "untitled:^Untitled-1";
it("Can convert positions to locations", () => {
const aTs: File = { path: "/proj/a.ts", content: "" };
const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };
const session = createSession(createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true });
openFilesForSession([aTs], session);
executeSessionRequestNoResponse<protocol.OpenRequest>(session, protocol.CommandTypes.Open, {
file: untitledFile,
fileContent: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />\nlet foo = 1;\nfooo/**/`,
scriptKindName: "TS",
projectRootPath: "/proj",
});
verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`);
const response = executeSessionRequest<protocol.CodeFixRequest, protocol.CodeFixResponse>(session, protocol.CommandTypes.GetCodeFixes, {
file: untitledFile,
startLine: 3,
startOffset: 1,
endLine: 3,
endOffset: 5,
errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code],
});
assert.deepEqual<readonly protocol.CodeFixAction[] | undefined>(response, [
{
description: "Change spelling to 'foo'",
fixName: "spelling",
changes: [{
fileName: untitledFile,
textChanges: [{
start: { line: 3, offset: 1 },
end: { line: 3, offset: 5 },
newText: "foo",
}],
}],
commands: undefined,
fixId: undefined,
fixAllDescription: undefined
},
]);
});
it("opening untitled files", () => {
const config: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: tscWatch.projectRoot });
const service = createProjectService(host);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`);
const untitled: File = {
path: `${tscWatch.projectRoot}/Untitled-1.ts`,
content: "const x = 10;"
};
host.writeFile(untitled.path, untitled.content);
host.checkTimeoutQueueLength(0);
service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, tscWatch.projectRoot);
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
service.closeClientFile(untitledFile);
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot);
verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`);
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
});
});
}