1namespace ts.projectSystem { 2 describe("unittests:: tsserver:: Semantic operations on PartialSemantic server", () => { 3 function setup() { 4 const file1: File = { 5 path: `${tscWatch.projectRoot}/a.ts`, 6 content: `import { y, cc } from "./b"; 7import { something } from "something"; 8class c { prop = "hello"; foo() { return this.prop; } }` 9 }; 10 const file2: File = { 11 path: `${tscWatch.projectRoot}/b.ts`, 12 content: `export { cc } from "./c"; 13import { something } from "something"; 14 export const y = 10;` 15 }; 16 const file3: File = { 17 path: `${tscWatch.projectRoot}/c.ts`, 18 content: `export const cc = 10;` 19 }; 20 const something: File = { 21 path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, 22 content: "export const something = 10;" 23 }; 24 const configFile: File = { 25 path: `${tscWatch.projectRoot}/tsconfig.json`, 26 content: "{}" 27 }; 28 const host = createServerHost([file1, file2, file3, something, libFile, configFile]); 29 const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); 30 return { host, session, file1, file2, file3, something, configFile }; 31 } 32 33 it("open files are added to inferred project even if config file is present and semantic operations succeed", () => { 34 const { host, session, file1, file2 } = setup(); 35 const service = session.getProjectService(); 36 openFilesForSession([file1], session); 37 checkNumberOfProjects(service, { inferredProjects: 1 }); 38 const project = service.inferredProjects[0]; 39 checkProjectActualFiles(project, [libFile.path, file1.path]); // no imports are resolved 40 verifyCompletions(); 41 42 openFilesForSession([file2], session); 43 checkNumberOfProjects(service, { inferredProjects: 1 }); 44 checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); 45 verifyCompletions(); 46 47 function verifyCompletions() { 48 assert.isTrue(project.languageServiceEnabled); 49 checkWatchedFiles(host, emptyArray); 50 checkWatchedDirectories(host, emptyArray, /*recursive*/ true); 51 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 52 const response = session.executeCommandSeq<protocol.CompletionsRequest>({ 53 command: protocol.CommandTypes.Completions, 54 arguments: protocolFileLocationFromSubstring(file1, "prop", { index: 1 }) 55 }).response as protocol.CompletionEntry[]; 56 const displayPartsProp = [ 57 { 58 text: "(", 59 kind: "punctuation" 60 },{ 61 text: "property", 62 kind: "text" 63 },{ 64 text: ")", 65 kind: "punctuation" 66 },{ 67 text: " ", 68 kind: "space" 69 },{ 70 text: "c", 71 kind: "className" 72 },{ 73 text: ".", 74 kind: "punctuation" 75 },{ 76 text: "prop", 77 kind: "propertyName" 78 },{ 79 text: ":", 80 kind: "punctuation" 81 },{ 82 text: " ", 83 kind: "space" 84 },{ 85 text: "string", 86 kind: "keyword" 87 } 88 ]; 89 const displayPartsFoo = [ 90 { 91 text: "(", 92 kind: "punctuation" 93 },{ 94 text: "method", 95 kind: "text" 96 },{ 97 text: ")", 98 kind: "punctuation" 99 },{ 100 text: " ", 101 kind: "space" 102 },{ 103 text: "c", 104 kind: "className" 105 },{ 106 text: ".", 107 kind: "punctuation" 108 },{ 109 text: "foo", 110 kind: "methodName" 111 },{ 112 text: "(", 113 kind: "punctuation" 114 },{ 115 text: ")", 116 kind: "punctuation" 117 },{ 118 text: ":", 119 kind: "punctuation" 120 },{ 121 text: " ", 122 kind: "space" 123 },{ 124 text: "string", 125 kind: "keyword" 126 } 127 ]; 128 assert.deepEqual(response, [ 129 completionEntry("foo", ScriptElementKind.memberFunctionElement, displayPartsFoo), 130 completionEntry("prop", ScriptElementKind.memberVariableElement, displayPartsProp), 131 ]); 132 } 133 134 function completionEntry(name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[]): protocol.CompletionEntry { 135 return { 136 name, 137 kind, 138 kindModifiers: "", 139 sortText: Completions.SortText.LocationPriority, 140 hasAction: undefined, 141 insertText: undefined, 142 isPackageJsonImport: undefined, 143 isRecommended: undefined, 144 replacementSpan: undefined, 145 source: undefined, 146 jsDoc: undefined, 147 displayParts 148 }; 149 } 150 }); 151 152 it("throws on unsupported commands", () => { 153 const { session, file1 } = setup(); 154 const service = session.getProjectService(); 155 openFilesForSession([file1], session); 156 let hasException = false; 157 const request: protocol.SemanticDiagnosticsSyncRequest = { 158 type: "request", 159 seq: 1, 160 command: protocol.CommandTypes.SemanticDiagnosticsSync, 161 arguments: { file: file1.path } 162 }; 163 try { 164 session.executeCommand(request); 165 } 166 catch (e) { 167 assert.equal(e.message, `Request: semanticDiagnosticsSync not allowed in LanguageServiceMode.PartialSemantic`); 168 hasException = true; 169 } 170 assert.isTrue(hasException); 171 172 hasException = false; 173 const project = service.inferredProjects[0]; 174 try { 175 project.getLanguageService().getSemanticDiagnostics(file1.path); 176 } 177 catch (e) { 178 assert.equal(e.message, `LanguageService Operation: getSemanticDiagnostics not allowed in LanguageServiceMode.PartialSemantic`); 179 hasException = true; 180 } 181 assert.isTrue(hasException); 182 }); 183 184 it("should not include auto type reference directives", () => { 185 const { host, session, file1 } = setup(); 186 const atTypes: File = { 187 path: `/node_modules/@types/somemodule/index.d.ts`, 188 content: "export const something = 10;" 189 }; 190 host.ensureFileOrFolder(atTypes); 191 const service = session.getProjectService(); 192 openFilesForSession([file1], session); 193 checkNumberOfProjects(service, { inferredProjects: 1 }); 194 const project = service.inferredProjects[0]; 195 checkProjectActualFiles(project, [libFile.path, file1.path]); // Should not contain atTypes 196 }); 197 198 it("should not include referenced files from unopened files", () => { 199 const file1: File = { 200 path: `${tscWatch.projectRoot}/a.ts`, 201 content: `///<reference path="b.ts"/> 202///<reference path="${tscWatch.projectRoot}/node_modules/something/index.d.ts"/> 203function fooA() { }` 204 }; 205 const file2: File = { 206 path: `${tscWatch.projectRoot}/b.ts`, 207 content: `///<reference path="./c.ts"/> 208///<reference path="${tscWatch.projectRoot}/node_modules/something/index.d.ts"/> 209function fooB() { }` 210 }; 211 const file3: File = { 212 path: `${tscWatch.projectRoot}/c.ts`, 213 content: `function fooC() { }` 214 }; 215 const something: File = { 216 path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, 217 content: "function something() {}" 218 }; 219 const configFile: File = { 220 path: `${tscWatch.projectRoot}/tsconfig.json`, 221 content: "{}" 222 }; 223 const host = createServerHost([file1, file2, file3, something, libFile, configFile]); 224 const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); 225 const service = session.getProjectService(); 226 openFilesForSession([file1], session); 227 checkNumberOfProjects(service, { inferredProjects: 1 }); 228 const project = service.inferredProjects[0]; 229 checkProjectActualFiles(project, [libFile.path, file1.path]); // no resolve 230 }); 231 232 it("should not crash when external module name resolution is reused", () => { 233 const { session, file1, file2, file3 } = setup(); 234 const service = session.getProjectService(); 235 openFilesForSession([file1], session); 236 checkNumberOfProjects(service, { inferredProjects: 1 }); 237 const project = service.inferredProjects[0]; 238 checkProjectActualFiles(project, [libFile.path, file1.path]); 239 240 // Close the file that contains non relative external module name and open some file that doesnt have non relative external module import 241 closeFilesForSession([file1], session); 242 openFilesForSession([file3], session); 243 checkProjectActualFiles(project, [libFile.path, file3.path]); 244 245 // Open file with non relative external module name 246 openFilesForSession([file2], session); 247 checkProjectActualFiles(project, [libFile.path, file2.path, file3.path]); 248 }); 249 250 it("should not create autoImportProvider or handle package jsons", () => { 251 const angularFormsDts: File = { 252 path: "/node_modules/@angular/forms/forms.d.ts", 253 content: "export declare class PatternValidator {}", 254 }; 255 const angularFormsPackageJson: File = { 256 path: "/node_modules/@angular/forms/package.json", 257 content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, 258 }; 259 const tsconfig: File = { 260 path: "/tsconfig.json", 261 content: `{ "compilerOptions": { "module": "commonjs" } }`, 262 }; 263 const packageJson: File = { 264 path: "/package.json", 265 content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` 266 }; 267 const indexTs: File = { 268 path: "/index.ts", 269 content: "" 270 }; 271 const host = createServerHost([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs, libFile]); 272 const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); 273 const service = session.getProjectService(); 274 openFilesForSession([indexTs], session); 275 const project = service.inferredProjects[0]; 276 assert.isFalse(project.autoImportProviderHost); 277 assert.isUndefined(project.getPackageJsonAutoImportProvider()); 278 assert.deepEqual(project.getPackageJsonsForAutoImport(), emptyArray); 279 }); 280 }); 281} 282