1namespace ts.projectSystem { 2 export function verifyDynamic(service: server.ProjectService, path: string) { 3 const info = Debug.checkDefined(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" /> 12var 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 it("opening and closing untitled files when projectRootPath is different from currentDirectory", () => { 102 const config: File = { 103 path: `${tscWatch.projectRoot}/tsconfig.json`, 104 content: "{}" 105 }; 106 const file: File = { 107 path: `${tscWatch.projectRoot}/file.ts`, 108 content: "const y = 10" 109 }; 110 const host = createServerHost([config, file, libFile], { useCaseSensitiveFileNames: true }); 111 const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); 112 service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); 113 checkNumberOfProjects(service, { inferredProjects: 1 }); 114 checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); 115 verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); 116 117 // Close untitled file 118 service.closeClientFile(untitledFile); 119 120 // Open file from configured project which should collect inferredProject 121 service.openClientFile(file.path); 122 checkNumberOfProjects(service, { configuredProjects: 1 }); 123 }); 124 125 it("when changing scriptKind of the untitled files", () => { 126 const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); 127 const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); 128 service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TS, tscWatch.projectRoot); 129 checkNumberOfProjects(service, { inferredProjects: 1 }); 130 checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); 131 const program = service.inferredProjects[0].getCurrentProgram()!; 132 const sourceFile = program.getSourceFile(untitledFile)!; 133 134 // Close untitled file 135 service.closeClientFile(untitledFile); 136 137 // Open untitled file with different mode 138 service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TSX, tscWatch.projectRoot); 139 checkNumberOfProjects(service, { inferredProjects: 1 }); 140 checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); 141 const newProgram = service.inferredProjects[0].getCurrentProgram()!; 142 const newSourceFile = newProgram.getSourceFile(untitledFile)!; 143 assert.notStrictEqual(newProgram, program); 144 assert.notStrictEqual(newSourceFile, sourceFile); 145 }); 146 }); 147 148 describe("unittests:: tsserver:: dynamicFiles:: ", () => { 149 it("dynamic file without external project", () => { 150 const file: File = { 151 path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", 152 content: "var x = 10;" 153 }; 154 const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); 155 const projectService = createProjectService(host); 156 projectService.setCompilerOptionsForInferredProjects({ 157 module: ModuleKind.CommonJS, 158 allowJs: true, 159 allowSyntheticDefaultImports: true, 160 allowNonTsExtensions: true 161 }); 162 projectService.openClientFile(file.path, "var x = 10;"); 163 164 projectService.checkNumberOfProjects({ inferredProjects: 1 }); 165 const project = projectService.inferredProjects[0]; 166 checkProjectRootFiles(project, [file.path]); 167 checkProjectActualFiles(project, [file.path, libFile.path]); 168 verifyDynamic(projectService, `/${file.path}`); 169 170 assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project); 171 const indexOfX = file.content.indexOf("x"); 172 assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), { 173 kind: ScriptElementKind.variableElement, 174 kindModifiers: "", 175 textSpan: { start: indexOfX, length: 1 }, 176 displayParts: [ 177 { text: "var", kind: "keyword" }, 178 { text: " ", kind: "space" }, 179 { text: "x", kind: "localName" }, 180 { text: ":", kind: "punctuation" }, 181 { text: " ", kind: "space" }, 182 { text: "number", kind: "keyword" } 183 ], 184 documentation: [], 185 tags: undefined, 186 }); 187 }); 188 189 it("dynamic file with reference paths without external project", () => { 190 verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js"); 191 }); 192 193 describe("dynamic file with projectRootPath", () => { 194 const file: File = { 195 path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", 196 content: "var x = 10;" 197 }; 198 const configFile: File = { 199 path: `${tscWatch.projectRoot}/tsconfig.json`, 200 content: "{}" 201 }; 202 const configProjectFile: File = { 203 path: `${tscWatch.projectRoot}/a.ts`, 204 content: "let y = 10;" 205 }; 206 it("with useInferredProjectPerProjectRoot", () => { 207 const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); 208 const session = createSession(host, { useInferredProjectPerProjectRoot: true }); 209 openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session); 210 211 const projectService = session.getProjectService(); 212 checkNumberOfProjects(projectService, { inferredProjects: 1 }); 213 checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); 214 verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.path}`); 215 216 session.executeCommandSeq<protocol.OutliningSpansRequest>({ 217 command: protocol.CommandTypes.GetOutliningSpans, 218 arguments: { 219 file: file.path 220 } 221 }); 222 223 // Without project root 224 const file2Path = file.path.replace("#1", "#2"); 225 projectService.openClientFile(file2Path, file.content); 226 checkNumberOfProjects(projectService, { inferredProjects: 2 }); 227 checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); 228 checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]); 229 }); 230 231 it("fails when useInferredProjectPerProjectRoot is false", () => { 232 const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); 233 const projectService = createProjectService(host); 234 try { 235 projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot); 236 } 237 catch (e) { 238 assert.strictEqual( 239 e.message.replace(/\r?\n/, "\n"), 240 `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.` 241 ); 242 } 243 const file2Path = file.path.replace("#1", "#2"); 244 projectService.openClientFile(file2Path, file.content); 245 projectService.checkNumberOfProjects({ inferredProjects: 1 }); 246 checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]); 247 }); 248 }); 249 250 describe("verify accepts known schemas as dynamic file", () => { 251 it("walkThroughSnippet", () => { 252 verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts"); 253 }); 254 255 it("untitled", () => { 256 verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts"); 257 }); 258 }); 259 }); 260} 261