• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}