From bbb9412a17e7a6a4228b46934d6413e38917f0bb Mon Sep 17 00:00:00 2001 From: JoostK Date: Sat, 7 Dec 2019 17:26:25 +0100 Subject: [PATCH] fix(compiler-cli): allow declaration-only template type check members (#34296) The metadata collector for View Engine compilations emits error symbols for static class members that have not been initialized, which prevents a library from building successfully when `strictMetadataEmit` is enabled, which is recommended for libraries to avoid issues in library consumers. This is troublesome for libraries that are adopting static members for the Ivy template type checker: these members don't need a value assignment as only their type is of importance, however this causes metadata errors. As such, a library used to be required to initialize the special static members to workaround this error, undesirably introducing a code-size overhead in terms of emitted JavaScript code. This commit modifies the collector logic to specifically ignore the special static members for Ivy's template type checker, preventing any errors from being recorded during the metadata collection. PR Close #34296 --- .../compiler-cli/src/metadata/collector.ts | 6 ++++- .../test/metadata/collector_spec.ts | 25 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/compiler-cli/src/metadata/collector.ts b/packages/compiler-cli/src/metadata/collector.ts index 34a2040d87..d7960785af 100644 --- a/packages/compiler-cli/src/metadata/collector.ts +++ b/packages/compiler-cli/src/metadata/collector.ts @@ -230,7 +230,7 @@ export class MetadataCollector { const property = member; if (isStatic(property)) { const name = evaluator.nameOf(property.name); - if (!isMetadataError(name)) { + if (!isMetadataError(name) && !shouldIgnoreStaticMember(name)) { if (property.initializer) { const value = evaluator.evaluateNode(property.initializer); recordStaticMember(name, value); @@ -744,6 +744,10 @@ function namesOf(parameters: ts.NodeArray): string[] { return result; } +function shouldIgnoreStaticMember(memberName: string): boolean { + return memberName.startsWith('ngAcceptInputType_') || memberName.startsWith('ngTemplateGuard_'); +} + function expandedMessage(error: any): string { switch (error.message) { case 'Reference to non-exported class': diff --git a/packages/compiler-cli/test/metadata/collector_spec.ts b/packages/compiler-cli/test/metadata/collector_spec.ts index e0263eedf8..dffe560d32 100644 --- a/packages/compiler-cli/test/metadata/collector_spec.ts +++ b/packages/compiler-cli/test/metadata/collector_spec.ts @@ -37,7 +37,7 @@ describe('Collector', () => { 'static-method.ts', 'static-method-call.ts', 'static-method-with-if.ts', 'static-method-with-default.ts', 'class-inheritance.ts', 'class-inheritance-parent.ts', - 'interface-reference.ts' + 'interface-reference.ts', 'static-type-check-members.ts', ]); service = ts.createLanguageService(host, documentRegistry); program = service.getProgram() !; @@ -547,6 +547,18 @@ describe('Collector', () => { expect(classData.statics).toEqual({VALUE: 'Some string'}); }); + it('should ignore static type check members without a value', () => { + const typeCheckMembers = program.getSourceFile('/static-type-check-members.ts') !; + const metadata = collector.getMetadata(typeCheckMembers) !; + const classData = metadata.metadata['MyDirective']; + expect(classData.statics).toEqual({ + foo: 'bar', + declared: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 13}, + ngTemplateContextGuard: {__symbolic: 'function', parameters: ['dir', 'ctx'], value: true}, + ngTemplateGuard_value: {__symbolic: 'function', parameters: ['dir', 'expr'], value: true}, + }); + }); + it('should be able to collect a reference to a static field', () => { const staticSource = program.getSourceFile('/static-field-reference.ts') !; const metadata = collector.getMetadata(staticSource) !; @@ -1472,6 +1484,17 @@ const FILES: Directory = { static VALUE = 'Some string'; } `, + 'static-type-check-members.ts': ` + export class MyDirective { + static foo = 'bar'; + static declared: string; + + static ngAcceptInputType_disabled: boolean|string; + static ngTemplateContextGuard(dir: any, ctx: any): any { return true; }; + static ngTemplateGuard_declared: 'binding'; + static ngTemplateGuard_value(dir: any, expr: any): any { return true; }; + } + `, 'static-field-reference.ts': ` import {Component} from 'angular2/core'; import {MyModule} from './static-field';