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 126 describe("unittests:: tsserver:: dynamicFiles:: ", () => { 127 it("dynamic file without external project", () => { 128 const file: File = { 129 path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", 130 content: "var x = 10;" 131 }; 132 const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); 133 const projectService = createProjectService(host); 134 projectService.setCompilerOptionsForInferredProjects({ 135 module: ModuleKind.CommonJS, 136 allowJs: true, 137 allowSyntheticDefaultImports: true, 138 allowNonTsExtensions: true 139 }); 140 projectService.openClientFile(file.path, "var x = 10;"); 141 142 projectService.checkNumberOfProjects({ inferredProjects: 1 }); 143 const project = projectService.inferredProjects[0]; 144 checkProjectRootFiles(project, [file.path]); 145 checkProjectActualFiles(project, [file.path, libFile.path]); 146 verifyDynamic(projectService, `/${file.path}`); 147 148 assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project); 149 const indexOfX = file.content.indexOf("x"); 150 assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), { 151 kind: ScriptElementKind.variableElement, 152 kindModifiers: "", 153 textSpan: { start: indexOfX, length: 1 }, 154 displayParts: [ 155 { text: "var", kind: "keyword" }, 156 { text: " ", kind: "space" }, 157 { text: "x", kind: "localName" }, 158 { text: ":", kind: "punctuation" }, 159 { text: " ", kind: "space" }, 160 { text: "number", kind: "keyword" } 161 ], 162 documentation: [], 163 tags: undefined, 164 }); 165 }); 166 167 it("dynamic file with reference paths without external project", () => { 168 verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js"); 169 }); 170 171 describe("dynamic file with projectRootPath", () => { 172 const file: File = { 173 path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", 174 content: "var x = 10;" 175 }; 176 const configFile: File = { 177 path: `${tscWatch.projectRoot}/tsconfig.json`, 178 content: "{}" 179 }; 180 const configProjectFile: File = { 181 path: `${tscWatch.projectRoot}/a.ts`, 182 content: "let y = 10;" 183 }; 184 it("with useInferredProjectPerProjectRoot", () => { 185 const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); 186 const session = createSession(host, { useInferredProjectPerProjectRoot: true }); 187 openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session); 188 189 const projectService = session.getProjectService(); 190 checkNumberOfProjects(projectService, { inferredProjects: 1 }); 191 checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); 192 verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.path}`); 193 194 session.executeCommandSeq<protocol.OutliningSpansRequest>({ 195 command: protocol.CommandTypes.GetOutliningSpans, 196 arguments: { 197 file: file.path 198 } 199 }); 200 201 // Without project root 202 const file2Path = file.path.replace("#1", "#2"); 203 projectService.openClientFile(file2Path, file.content); 204 checkNumberOfProjects(projectService, { inferredProjects: 2 }); 205 checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); 206 checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]); 207 }); 208 209 it("fails when useInferredProjectPerProjectRoot is false", () => { 210 const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); 211 const projectService = createProjectService(host); 212 try { 213 projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot); 214 } 215 catch (e) { 216 assert.strictEqual( 217 e.message.replace(/\r?\n/, "\n"), 218 `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.` 219 ); 220 } 221 const file2Path = file.path.replace("#1", "#2"); 222 projectService.openClientFile(file2Path, file.content); 223 projectService.checkNumberOfProjects({ inferredProjects: 1 }); 224 checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]); 225 }); 226 }); 227 228 describe("verify accepts known schemas as dynamic file", () => { 229 it("walkThroughSnippet", () => { 230 verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts"); 231 }); 232 233 it("untitled", () => { 234 verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts"); 235 }); 236 }); 237 }); 238} 239