1namespace ts.projectSystem { 2 describe("unittests:: tsserver:: plugins loading", () => { 3 const testProtocolCommand = "testProtocolCommand"; 4 const testProtocolCommandRequest = "testProtocolCommandRequest"; 5 const testProtocolCommandResponse = "testProtocolCommandResponse"; 6 7 function createHostWithPlugin(files: readonly File[]) { 8 const host = createServerHost(files); 9 const pluginsLoaded: string[] = []; 10 const protocolHandlerRequests: [string, string][] = []; 11 host.require = (_initialPath, moduleName) => { 12 pluginsLoaded.push(moduleName); 13 return { 14 module: () => ({ 15 create(info: server.PluginCreateInfo) { 16 info.session?.addProtocolHandler(testProtocolCommand, request => { 17 protocolHandlerRequests.push([request.command, request.arguments]); 18 return { 19 response: testProtocolCommandResponse 20 }; 21 }); 22 return Harness.LanguageService.makeDefaultProxy(info); 23 } 24 }), 25 error: undefined 26 }; 27 }; 28 return { host, pluginsLoaded, protocolHandlerRequests }; 29 } 30 31 it("With local plugins", () => { 32 const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; 33 const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; 34 const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; 35 const tsconfig: File = { 36 path: "/tsconfig.json", 37 content: JSON.stringify({ 38 compilerOptions: { 39 plugins: [ 40 ...[...expectedToLoad, ...notToLoad].map(name => ({ name })), 41 { transform: "some-transform" } 42 ] 43 } 44 }) 45 }; 46 const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); 47 const service = createProjectService(host); 48 service.openClientFile(aTs.path); 49 assert.deepEqual(pluginsLoaded, expectedToLoad); 50 }); 51 52 it("With global plugins", () => { 53 const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; 54 const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; 55 const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; 56 const tsconfig: File = { 57 path: "/tsconfig.json", 58 content: "{}" 59 }; 60 const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); 61 const service = createProjectService(host, { globalPlugins: [...expectedToLoad, ...notToLoad] }); 62 service.openClientFile(aTs.path); 63 assert.deepEqual(pluginsLoaded, expectedToLoad); 64 }); 65 66 it("With session and custom protocol message", () => { 67 const pluginName = "some-plugin"; 68 const expectedToLoad = [pluginName]; 69 const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; 70 const tsconfig: File = { 71 path: "/tsconfig.json", 72 content: JSON.stringify({ 73 compilerOptions: { 74 plugins: [ 75 { name: pluginName } 76 ] 77 } 78 }) 79 }; 80 81 const { host, pluginsLoaded, protocolHandlerRequests } = createHostWithPlugin([aTs, tsconfig, libFile]); 82 const session = createSession(host); 83 84 const service = createProjectService(host, { session }); 85 service.openClientFile(aTs.path); 86 assert.deepEqual(pluginsLoaded, expectedToLoad); 87 88 const resp = session.executeCommandSeq({ 89 command: testProtocolCommand, 90 arguments: testProtocolCommandRequest 91 }); 92 93 assert.strictEqual(protocolHandlerRequests.length, 1); 94 const [command, args] = protocolHandlerRequests[0]; 95 assert.strictEqual(command, testProtocolCommand); 96 assert.strictEqual(args, testProtocolCommandRequest); 97 98 const expectedResp: server.HandlerResponse = { 99 response: testProtocolCommandResponse 100 }; 101 assert.deepEqual(resp, expectedResp); 102 }); 103 104 it("gets external files with config file reload", () => { 105 const aTs: File = { path: `${tscWatch.projectRoot}/a.ts`, content: `export const x = 10;` }; 106 const tsconfig: File = { 107 path: `${tscWatch.projectRoot}/tsconfig.json`, 108 content: JSON.stringify({ 109 compilerOptions: { 110 plugins: [{ name: "some-plugin" }] 111 } 112 }) 113 }; 114 115 const externalFiles: MapLike<string[]> = { 116 "some-plugin": ["someFile.txt"], 117 "some-other-plugin": ["someOtherFile.txt"], 118 }; 119 120 const host = createServerHost([aTs, tsconfig, libFile]); 121 host.require = (_initialPath, moduleName) => { 122 session.logger.logs.push(`Require:: ${moduleName}`); 123 return { 124 module: (): server.PluginModule => { 125 session.logger.logs.push(`PluginFactory Invoke`); 126 return { 127 create: Harness.LanguageService.makeDefaultProxy, 128 getExternalFiles: () => externalFiles[moduleName] 129 }; 130 }, 131 error: undefined 132 }; 133 }; 134 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 135 openFilesForSession([aTs], session); 136 session.logger.logs.push(`ExternalFiles:: ${JSON.stringify(session.getProjectService().configuredProjects.get(tsconfig.path)!.getExternalFiles())}`); 137 138 host.writeFile(tsconfig.path, JSON.stringify({ 139 compilerOptions: { 140 plugins: [{ name: "some-other-plugin" }] 141 } 142 })); 143 host.runQueuedTimeoutCallbacks(); 144 session.logger.logs.push(`ExternalFiles:: ${JSON.stringify(session.getProjectService().configuredProjects.get(tsconfig.path)!.getExternalFiles())}`); 145 146 baselineTsserverLogs("plugins", "gets external files with config file reload", session); 147 }); 148 }); 149}