diff --git a/src/server/project.ts b/src/server/project.ts index 0e4f937552ea8..a593466c780c8 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1550,6 +1550,10 @@ namespace ts.server { protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map | undefined) { this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`); + if (parsePackageName(pluginConfigEntry.name).rest) { + this.projectService.logger.info(`kipped loading plugin ${pluginConfigEntry.name} because only package name is allowed plugin name`); + return; + } const log = (message: string) => this.projectService.logger.info(message); let errorLogs: string[] | undefined; diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 7c07d226c077e..c468f56423cb2 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -177,6 +177,7 @@ "unittests/tsserver/openFile.ts", "unittests/tsserver/packageJsonInfo.ts", "unittests/tsserver/partialSemanticServer.ts", + "unittests/tsserver/plugins.ts", "unittests/tsserver/projectErrors.ts", "unittests/tsserver/projectReferenceCompileOnSave.ts", "unittests/tsserver/projectReferenceErrors.ts", diff --git a/src/testRunner/unittests/tsserver/plugins.ts b/src/testRunner/unittests/tsserver/plugins.ts new file mode 100644 index 0000000000000..4cd692f0576d6 --- /dev/null +++ b/src/testRunner/unittests/tsserver/plugins.ts @@ -0,0 +1,50 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: plugins loading", () => { + function createHostWithPlugin(files: readonly File[]) { + const host = createServerHost(files); + const pluginsLoaded: string[] = []; + host.require = (_initialPath, moduleName) => { + pluginsLoaded.push(moduleName); + return { + module: () => ({ + create(info: server.PluginCreateInfo) { + return Harness.LanguageService.makeDefaultProxy(info); + } + }), + error: undefined + }; + }; + return { host, pluginsLoaded }; + } + + it("With local plugins", () => { + const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; + const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { plugins: [...expectedToLoad, ...notToLoad].map(name => ({ name })) } + }) + }; + const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); + const service = createProjectService(host); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); + }); + + it("With global plugins", () => { + const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; + const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}" + }; + const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); + const service = createProjectService(host, /*parameters*/ undefined, { globalPlugins: [...expectedToLoad, ...notToLoad] }); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); + }); + }); +} \ No newline at end of file