From a65d3fa1de4c67f717b98e20d8ad8b7e921ab3a0 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Fri, 6 Sep 2019 14:57:05 -0700 Subject: [PATCH] fix(language-service): Return empty external files during project initialization (#32519) This PR partially fixes a circular dependency problem whereby the creation of a project queries Angular plugin for external files, but the discovery of external files requires root files to be defined in a Project. The right approach is to return empty array if Project has no root files. PR Close #32519 --- packages/language-service/bundles/BUILD.bazel | 1 + .../language-service/bundles/banner.js.txt | 5 +- packages/language-service/src/ts_plugin.ts | 46 ++++++++++++++++--- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/language-service/bundles/BUILD.bazel b/packages/language-service/bundles/BUILD.bazel index 0a5e7a01eb..7ddb1345de 100644 --- a/packages/language-service/bundles/BUILD.bazel +++ b/packages/language-service/bundles/BUILD.bazel @@ -7,6 +7,7 @@ ls_rollup_bundle( "fs": "fs", "path": "path", "typescript": "ts", + "typescript/lib/tsserverlibrary": "tss", }, license_banner = "banner.js.txt", visibility = ["//packages/language-service:__pkg__"], diff --git a/packages/language-service/bundles/banner.js.txt b/packages/language-service/bundles/banner.js.txt index 3224f53c79..f4d2e9579d 100644 --- a/packages/language-service/bundles/banner.js.txt +++ b/packages/language-service/bundles/banner.js.txt @@ -8,7 +8,10 @@ var $reflect = {defineMetadata: function() {}, getOwnMetadata: function() {}}; var Reflect = (typeof global !== 'undefined' ? global : {})['Reflect'] || {}; Object.keys($reflect).forEach(function(key) { Reflect[key] = Reflect[key] || $reflect[key]; }); var $deferred, $resolved, $provided; -function $getModule(name) { return $provided[name] || require(name); } +function $getModule(name) { + if (name === 'typescript/lib/tsserverlibrary') return $provided['typescript'] || require(name); + return $provided[name] || require(name); +} function define(modules, cb) { $deferred = { modules: modules, cb: cb }; } module.exports = function(provided) { if ($resolved) return $resolved; diff --git a/packages/language-service/src/ts_plugin.ts b/packages/language-service/src/ts_plugin.ts index c6c6c149dc..f0261636c9 100644 --- a/packages/language-service/src/ts_plugin.ts +++ b/packages/language-service/src/ts_plugin.ts @@ -6,20 +6,52 @@ * found in the LICENSE file at https://angular.io/license */ -import * as tss from 'typescript/lib/tsserverlibrary'; // used as type only +import * as tss from 'typescript/lib/tsserverlibrary'; import {createLanguageService} from './language_service'; import {TypeScriptServiceHost} from './typescript_host'; +/** + * A note about importing TypeScript module. + * The TypeScript module is supplied by tsserver at runtime to ensure version + * compatibility. In Angular language service, the rollup output is augmented + * with a "banner" shim that overwrites 'typescript' and + * 'typescript/lib/tsserverlibrary' imports with the value supplied by tsserver. + * This means import of either modules will not be "required", but they'll work + * just like regular imports. + */ + const projectHostMap = new WeakMap(); -export function getExternalFiles(project: tss.server.Project): string[]|undefined { - const host = projectHostMap.get(project); - if (host) { - host.getAnalyzedModules(); - const externalFiles = host.getTemplateReferences(); - return externalFiles; +/** + * Return the external templates discovered through processing all NgModules in + * the specified `project`. + * This function is called in a few situations: + * 1. When a ConfiguredProject is created + * https://github.com/microsoft/TypeScript/blob/c26c44d5fceb04ea14da20b6ed23449df777ff34/src/server/editorServices.ts#L1755 + * 2. When updateGraph() is called on a Project + * https://github.com/microsoft/TypeScript/blob/c26c44d5fceb04ea14da20b6ed23449df777ff34/src/server/project.ts#L915 + * @param project Most likely a ConfiguredProject + */ +export function getExternalFiles(project: tss.server.Project): string[] { + if (!project.hasRoots()) { + // During project initialization where there is no root files yet we should + // not do any work. + return []; } + const ngLSHost = projectHostMap.get(project); + if (!ngLSHost) { + // Without an Angular host there is no way to get template references. + return []; + } + ngLSHost.getAnalyzedModules(); + const templates = ngLSHost.getTemplateReferences(); + const logger = project.projectService.logger; + if (logger.hasLevel(tss.server.LogLevel.verbose)) { + // Log external files to help debugging. + logger.info(`External files in ${project.projectName}: ${JSON.stringify(templates)}`); + } + return templates; } export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {