Skip to content

Commit 87a3d6f

Browse files
committed
Handle walkThroughSnippet:/ and untitled:/ as dynamic files (#36722)
Handle walkThroughSnippet:/ and untitled:/ as dynamic files Fixes #36681
1 parent 94b98f4 commit 87a3d6f

File tree

5 files changed

+217
-199
lines changed

5 files changed

+217
-199
lines changed

src/server/scriptInfo.ts

+2
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ namespace ts.server {
273273
/*@internal*/
274274
export function isDynamicFileName(fileName: NormalizedPath) {
275275
return fileName[0] === "^" ||
276+
((stringContains(fileName, "walkThroughSnippet:/") || stringContains(fileName, "untitled:/")) &&
277+
getBaseFileName(fileName)[0] === "^") ||
276278
(stringContains(fileName, ":^") && !stringContains(fileName, directorySeparator));
277279
}
278280

src/testRunner/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
"unittests/tsserver/declarationFileMaps.ts",
150150
"unittests/tsserver/documentRegistry.ts",
151151
"unittests/tsserver/duplicatePackages.ts",
152+
"unittests/tsserver/dynamicFiles.ts",
152153
"unittests/tsserver/events/largeFileReferenced.ts",
153154
"unittests/tsserver/events/projectLanguageServiceState.ts",
154155
"unittests/tsserver/events/projectLoading.ts",
@@ -189,7 +190,6 @@
189190
"unittests/tsserver/typeOnlyImportChains.ts",
190191
"unittests/tsserver/typeReferenceDirectives.ts",
191192
"unittests/tsserver/typingsInstaller.ts",
192-
"unittests/tsserver/untitledFiles.ts",
193193
"unittests/tsserver/versionCache.ts",
194194
"unittests/tsserver/watchEnvironment.ts"
195195
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
namespace ts.projectSystem {
2+
export function verifyDynamic(service: server.ProjectService, path: string) {
3+
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 })))}`);
4+
assert.isTrue(info.isDynamic);
5+
}
6+
7+
function verifyPathRecognizedAsDynamic(path: string) {
8+
const file: File = {
9+
path,
10+
content: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />
11+
/// <reference path="../../../../../../typings/@epic/Shell.d.ts" />
12+
var x = 10;`
13+
};
14+
const host = createServerHost([libFile]);
15+
const projectService = createProjectService(host);
16+
projectService.openClientFile(file.path, file.content);
17+
verifyDynamic(projectService, projectService.toPath(file.path));
18+
19+
projectService.checkNumberOfProjects({ inferredProjects: 1 });
20+
const project = projectService.inferredProjects[0];
21+
checkProjectRootFiles(project, [file.path]);
22+
checkProjectActualFiles(project, [file.path, libFile.path]);
23+
}
24+
25+
describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => {
26+
const untitledFile = "untitled:^Untitled-1";
27+
it("Can convert positions to locations", () => {
28+
const aTs: File = { path: "/proj/a.ts", content: "" };
29+
const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" };
30+
const session = createSession(createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true });
31+
32+
openFilesForSession([aTs], session);
33+
34+
executeSessionRequestNoResponse<protocol.OpenRequest>(session, protocol.CommandTypes.Open, {
35+
file: untitledFile,
36+
fileContent: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />\nlet foo = 1;\nfooo/**/`,
37+
scriptKindName: "TS",
38+
projectRootPath: "/proj",
39+
});
40+
verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`);
41+
const response = executeSessionRequest<protocol.CodeFixRequest, protocol.CodeFixResponse>(session, protocol.CommandTypes.GetCodeFixes, {
42+
file: untitledFile,
43+
startLine: 3,
44+
startOffset: 1,
45+
endLine: 3,
46+
endOffset: 5,
47+
errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code],
48+
});
49+
assert.deepEqual<readonly protocol.CodeFixAction[] | undefined>(response, [
50+
{
51+
description: "Change spelling to 'foo'",
52+
fixName: "spelling",
53+
changes: [{
54+
fileName: untitledFile,
55+
textChanges: [{
56+
start: { line: 3, offset: 1 },
57+
end: { line: 3, offset: 5 },
58+
newText: "foo",
59+
}],
60+
}],
61+
commands: undefined,
62+
fixId: undefined,
63+
fixAllDescription: undefined
64+
},
65+
]);
66+
});
67+
68+
it("opening untitled files", () => {
69+
const config: File = {
70+
path: `${tscWatch.projectRoot}/tsconfig.json`,
71+
content: "{}"
72+
};
73+
const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: tscWatch.projectRoot });
74+
const service = createProjectService(host);
75+
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot);
76+
checkNumberOfProjects(service, { inferredProjects: 1 });
77+
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
78+
verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`);
79+
80+
const untitled: File = {
81+
path: `${tscWatch.projectRoot}/Untitled-1.ts`,
82+
content: "const x = 10;"
83+
};
84+
host.writeFile(untitled.path, untitled.content);
85+
host.checkTimeoutQueueLength(0);
86+
service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, tscWatch.projectRoot);
87+
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
88+
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
89+
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
90+
91+
service.closeClientFile(untitledFile);
92+
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
93+
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
94+
95+
service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot);
96+
verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`);
97+
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]);
98+
checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]);
99+
});
100+
});
101+
102+
describe("unittests:: tsserver:: dynamicFiles:: ", () => {
103+
it("dynamic file without external project", () => {
104+
const file: File = {
105+
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
106+
content: "var x = 10;"
107+
};
108+
const host = createServerHost([libFile], { useCaseSensitiveFileNames: true });
109+
const projectService = createProjectService(host);
110+
projectService.setCompilerOptionsForInferredProjects({
111+
module: ModuleKind.CommonJS,
112+
allowJs: true,
113+
allowSyntheticDefaultImports: true,
114+
allowNonTsExtensions: true
115+
});
116+
projectService.openClientFile(file.path, "var x = 10;");
117+
118+
projectService.checkNumberOfProjects({ inferredProjects: 1 });
119+
const project = projectService.inferredProjects[0];
120+
checkProjectRootFiles(project, [file.path]);
121+
checkProjectActualFiles(project, [file.path, libFile.path]);
122+
verifyDynamic(projectService, `/${file.path}`);
123+
124+
assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project);
125+
const indexOfX = file.content.indexOf("x");
126+
assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), {
127+
kind: ScriptElementKind.variableElement,
128+
kindModifiers: "",
129+
textSpan: { start: indexOfX, length: 1 },
130+
displayParts: [
131+
{ text: "var", kind: "keyword" },
132+
{ text: " ", kind: "space" },
133+
{ text: "x", kind: "localName" },
134+
{ text: ":", kind: "punctuation" },
135+
{ text: " ", kind: "space" },
136+
{ text: "number", kind: "keyword" }
137+
],
138+
documentation: [],
139+
tags: undefined,
140+
});
141+
});
142+
143+
it("dynamic file with reference paths without external project", () => {
144+
verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js");
145+
});
146+
147+
describe("dynamic file with projectRootPath", () => {
148+
const file: File = {
149+
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
150+
content: "var x = 10;"
151+
};
152+
const configFile: File = {
153+
path: `${tscWatch.projectRoot}/tsconfig.json`,
154+
content: "{}"
155+
};
156+
const configProjectFile: File = {
157+
path: `${tscWatch.projectRoot}/a.ts`,
158+
content: "let y = 10;"
159+
};
160+
it("with useInferredProjectPerProjectRoot", () => {
161+
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
162+
const session = createSession(host, { useInferredProjectPerProjectRoot: true });
163+
openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session);
164+
165+
const projectService = session.getProjectService();
166+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
167+
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
168+
verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.path}`);
169+
170+
session.executeCommandSeq<protocol.OutliningSpansRequest>({
171+
command: protocol.CommandTypes.GetOutliningSpans,
172+
arguments: {
173+
file: file.path
174+
}
175+
});
176+
177+
// Without project root
178+
const file2Path = file.path.replace("#1", "#2");
179+
projectService.openClientFile(file2Path, file.content);
180+
checkNumberOfProjects(projectService, { inferredProjects: 2 });
181+
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
182+
checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]);
183+
});
184+
185+
it("fails when useInferredProjectPerProjectRoot is false", () => {
186+
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
187+
const projectService = createProjectService(host);
188+
try {
189+
projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot);
190+
}
191+
catch (e) {
192+
assert.strictEqual(
193+
e.message.replace(/\r?\n/, "\n"),
194+
`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.`
195+
);
196+
}
197+
const file2Path = file.path.replace("#1", "#2");
198+
projectService.openClientFile(file2Path, file.content);
199+
projectService.checkNumberOfProjects({ inferredProjects: 1 });
200+
checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]);
201+
});
202+
});
203+
204+
describe("verify accepts known schemas as dynamic file", () => {
205+
it("walkThroughSnippet", () => {
206+
verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts");
207+
});
208+
209+
it("untitled", () => {
210+
verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts");
211+
});
212+
});
213+
});
214+
}

src/testRunner/unittests/tsserver/projects.ts

-115
Original file line numberDiff line numberDiff line change
@@ -1075,121 +1075,6 @@ namespace ts.projectSystem {
10751075
assert.equal(options.outDir, "C:/a/b", "");
10761076
});
10771077

1078-
it("dynamic file without external project", () => {
1079-
const file: File = {
1080-
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
1081-
content: "var x = 10;"
1082-
};
1083-
const host = createServerHost([libFile], { useCaseSensitiveFileNames: true });
1084-
const projectService = createProjectService(host);
1085-
projectService.setCompilerOptionsForInferredProjects({
1086-
module: ModuleKind.CommonJS,
1087-
allowJs: true,
1088-
allowSyntheticDefaultImports: true,
1089-
allowNonTsExtensions: true
1090-
});
1091-
projectService.openClientFile(file.path, "var x = 10;");
1092-
1093-
projectService.checkNumberOfProjects({ inferredProjects: 1 });
1094-
const project = projectService.inferredProjects[0];
1095-
checkProjectRootFiles(project, [file.path]);
1096-
checkProjectActualFiles(project, [file.path, libFile.path]);
1097-
verifyDynamic(projectService, `/${file.path}`);
1098-
1099-
assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project);
1100-
const indexOfX = file.content.indexOf("x");
1101-
assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), {
1102-
kind: ScriptElementKind.variableElement,
1103-
kindModifiers: "",
1104-
textSpan: { start: indexOfX, length: 1 },
1105-
displayParts: [
1106-
{ text: "var", kind: "keyword" },
1107-
{ text: " ", kind: "space" },
1108-
{ text: "x", kind: "localName" },
1109-
{ text: ":", kind: "punctuation" },
1110-
{ text: " ", kind: "space" },
1111-
{ text: "number", kind: "keyword" }
1112-
],
1113-
documentation: [],
1114-
tags: undefined,
1115-
});
1116-
});
1117-
1118-
it("dynamic file with reference paths without external project", () => {
1119-
const file: File = {
1120-
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
1121-
content: `/// <reference path="../../../../../../typings/@epic/Core.d.ts" />
1122-
/// <reference path="../../../../../../typings/@epic/Shell.d.ts" />
1123-
var x = 10;`
1124-
};
1125-
const host = createServerHost([libFile]);
1126-
const projectService = createProjectService(host);
1127-
projectService.openClientFile(file.path, file.content);
1128-
verifyDynamic(projectService, projectService.toPath(file.path));
1129-
1130-
projectService.checkNumberOfProjects({ inferredProjects: 1 });
1131-
const project = projectService.inferredProjects[0];
1132-
checkProjectRootFiles(project, [file.path]);
1133-
checkProjectActualFiles(project, [file.path, libFile.path]);
1134-
});
1135-
1136-
describe("dynamic file with projectRootPath", () => {
1137-
const file: File = {
1138-
path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js",
1139-
content: "var x = 10;"
1140-
};
1141-
const configFile: File = {
1142-
path: `${tscWatch.projectRoot}/tsconfig.json`,
1143-
content: "{}"
1144-
};
1145-
const configProjectFile: File = {
1146-
path: `${tscWatch.projectRoot}/a.ts`,
1147-
content: "let y = 10;"
1148-
};
1149-
it("with useInferredProjectPerProjectRoot", () => {
1150-
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
1151-
const session = createSession(host, { useInferredProjectPerProjectRoot: true });
1152-
openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session);
1153-
1154-
const projectService = session.getProjectService();
1155-
checkNumberOfProjects(projectService, { inferredProjects: 1 });
1156-
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
1157-
verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.path}`);
1158-
1159-
session.executeCommandSeq<protocol.OutliningSpansRequest>({
1160-
command: protocol.CommandTypes.GetOutliningSpans,
1161-
arguments: {
1162-
file: file.path
1163-
}
1164-
});
1165-
1166-
// Without project root
1167-
const file2Path = file.path.replace("#1", "#2");
1168-
projectService.openClientFile(file2Path, file.content);
1169-
checkNumberOfProjects(projectService, { inferredProjects: 2 });
1170-
checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]);
1171-
checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]);
1172-
});
1173-
1174-
it("fails when useInferredProjectPerProjectRoot is false", () => {
1175-
const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true });
1176-
const projectService = createProjectService(host);
1177-
try {
1178-
projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot);
1179-
}
1180-
catch (e) {
1181-
assert.strictEqual(
1182-
e.message.replace(/\r?\n/, "\n"),
1183-
`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.`
1184-
);
1185-
}
1186-
const file2Path = file.path.replace("#1", "#2");
1187-
projectService.openClientFile(file2Path, file.content);
1188-
projectService.checkNumberOfProjects({ inferredProjects: 1 });
1189-
checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]);
1190-
});
1191-
});
1192-
11931078
it("files opened, closed affecting multiple projects", () => {
11941079
const file: File = {
11951080
path: "/a/b/projects/config/file.ts",

0 commit comments

Comments
 (0)