From 1fb6724b1f66c4681ddb201bc9b5441d20f5b920 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 21 Apr 2021 10:16:45 +0100 Subject: [PATCH] fix(compiler-cli): allow linker to process minified booleans (#41747) Some partial libraries have been minified, which results in boolean literals being converted to `!0` and `!1`. This commit ensures that the linker can process these values. Fixes #41655 PR Close #41747 --- .../linker/babel/src/ast/babel_ast_host.ts | 23 ++++++++++++-- .../babel/test/ast/babel_ast_host_spec.ts | 12 ++++++++ .../compiler-cli/linker/src/ast/ast_host.ts | 8 ++++- .../src/ast/typescript/typescript_ast_host.ts | 30 ++++++++++++++++--- .../typescript/typescript_ast_host_spec.ts | 12 ++++++++ 5 files changed, 77 insertions(+), 8 deletions(-) diff --git a/packages/compiler-cli/linker/babel/src/ast/babel_ast_host.ts b/packages/compiler-cli/linker/babel/src/ast/babel_ast_host.ts index 5f9426847a25a..39490b7f4bd61 100644 --- a/packages/compiler-cli/linker/babel/src/ast/babel_ast_host.ts +++ b/packages/compiler-cli/linker/babel/src/ast/babel_ast_host.ts @@ -38,11 +38,18 @@ export class BabelAstHost implements AstHost { return num.value; } - isBooleanLiteral = t.isBooleanLiteral; + isBooleanLiteral(bool: t.Expression): boolean { + return t.isBooleanLiteral(bool) || isMinifiedBooleanLiteral(bool); + } parseBooleanLiteral(bool: t.Expression): boolean { - assert(bool, t.isBooleanLiteral, 'a boolean literal'); - return bool.value; + if (t.isBooleanLiteral(bool)) { + return bool.value; + } else if (isMinifiedBooleanLiteral(bool)) { + return !bool.argument.value; + } else { + throw new FatalLinkerError(bool, 'Unsupported syntax, expected a boolean literal.'); + } } isArrayLiteral = t.isArrayExpression; @@ -165,3 +172,13 @@ type ArgumentType = t.CallExpression['arguments'][number]; function isNotSpreadArgument(arg: ArgumentType): arg is Exclude { return !t.isSpreadElement(arg); } + +type MinifiedBooleanLiteral = t.Expression&t.UnaryExpression&{argument: t.NumericLiteral}; + +/** + * Return true if the node is either `!0` or `!1`. + */ +function isMinifiedBooleanLiteral(node: t.Expression): node is MinifiedBooleanLiteral { + return t.isUnaryExpression(node) && node.prefix && node.operator === '!' && + t.isNumericLiteral(node.argument) && (node.argument.value === 0 || node.argument.value === 1); +} diff --git a/packages/compiler-cli/linker/babel/test/ast/babel_ast_host_spec.ts b/packages/compiler-cli/linker/babel/test/ast/babel_ast_host_spec.ts index 81b4571e9311d..2540a6508dde3 100644 --- a/packages/compiler-cli/linker/babel/test/ast/babel_ast_host_spec.ts +++ b/packages/compiler-cli/linker/babel/test/ast/babel_ast_host_spec.ts @@ -96,6 +96,11 @@ describe('BabelAstHost', () => { expect(host.isBooleanLiteral(expr('false'))).toBe(true); }); + it('should return true if the expression is a minified boolean literal', () => { + expect(host.isBooleanLiteral(expr('!0'))).toBe(true); + expect(host.isBooleanLiteral(expr('!1'))).toBe(true); + }); + it('should return false if the expression is not a boolean literal', () => { expect(host.isBooleanLiteral(expr('"moo"'))).toBe(false); expect(host.isBooleanLiteral(expr('\'moo\''))).toBe(false); @@ -106,6 +111,8 @@ describe('BabelAstHost', () => { expect(host.isBooleanLiteral(expr('null'))).toBe(false); expect(host.isBooleanLiteral(expr('\'a\' + \'b\''))).toBe(false); expect(host.isBooleanLiteral(expr('\`moo\`'))).toBe(false); + expect(host.isBooleanLiteral(expr('!2'))).toBe(false); + expect(host.isBooleanLiteral(expr('~1'))).toBe(false); }); }); @@ -115,6 +122,11 @@ describe('BabelAstHost', () => { expect(host.parseBooleanLiteral(expr('false'))).toEqual(false); }); + it('should extract a minified boolean value', () => { + expect(host.parseBooleanLiteral(expr('!0'))).toEqual(true); + expect(host.parseBooleanLiteral(expr('!1'))).toEqual(false); + }); + it('should error if the value is not a boolean literal', () => { expect(() => host.parseBooleanLiteral(expr('"moo"'))) .toThrowError('Unsupported syntax, expected a boolean literal.'); diff --git a/packages/compiler-cli/linker/src/ast/ast_host.ts b/packages/compiler-cli/linker/src/ast/ast_host.ts index 11907f6115b38..ff7a5a9222e46 100644 --- a/packages/compiler-cli/linker/src/ast/ast_host.ts +++ b/packages/compiler-cli/linker/src/ast/ast_host.ts @@ -36,11 +36,17 @@ export interface AstHost { parseNumericLiteral(num: TExpression): number; /** - * Return `true` if the given expression is a boolean literal, or false otherwise. + * Return `true` if the given expression can be considered a boolean literal, or false otherwise. + * + * Note that this should also cover the special case of some minified code where `true` and + * `false` are replaced by `!0` and `!1` respectively. */ isBooleanLiteral(node: TExpression): boolean; /** * Parse the boolean value from the given expression, or throw if it is not a boolean literal. + * + * Note that this should also cover the special case of some minified code where `true` and + * `false` are replaced by `!0` and `!1` respectively. */ parseBooleanLiteral(bool: TExpression): boolean; diff --git a/packages/compiler-cli/linker/src/ast/typescript/typescript_ast_host.ts b/packages/compiler-cli/linker/src/ast/typescript/typescript_ast_host.ts index f23223d0b804b..9b6bb9069ff93 100644 --- a/packages/compiler-cli/linker/src/ast/typescript/typescript_ast_host.ts +++ b/packages/compiler-cli/linker/src/ast/typescript/typescript_ast_host.ts @@ -47,13 +47,18 @@ export class TypeScriptAstHost implements AstHost { return parseInt(num.text); } - isBooleanLiteral(node: ts.Expression): node is ts.FalseLiteral|ts.TrueLiteral { - return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword; + isBooleanLiteral(node: ts.Expression): boolean { + return isBooleanLiteral(node) || isMinifiedBooleanLiteral(node); } parseBooleanLiteral(bool: ts.Expression): boolean { - assert(bool, this.isBooleanLiteral, 'a boolean literal'); - return bool.kind === ts.SyntaxKind.TrueKeyword; + if (isBooleanLiteral(bool)) { + return bool.kind === ts.SyntaxKind.TrueKeyword; + } else if (isMinifiedBooleanLiteral(bool)) { + return !(+bool.operand.text); + } else { + throw new FatalLinkerError(bool, 'Unsupported syntax, expected a boolean literal.'); + } } isArrayLiteral = ts.isArrayLiteralExpression; @@ -160,3 +165,20 @@ function isNotSpreadElement(e: ts.Expression|ts.SpreadElement): e is ts.Expressi function isPropertyName(e: ts.PropertyName): e is ts.Identifier|ts.StringLiteral|ts.NumericLiteral { return ts.isIdentifier(e) || ts.isStringLiteral(e) || ts.isNumericLiteral(e); } + +/** + * Return true if the node is either `true` or `false` literals. + */ +function isBooleanLiteral(node: ts.Expression): node is ts.TrueLiteral|ts.FalseLiteral { + return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword; +} + +type MinifiedBooleanLiteral = ts.PrefixUnaryExpression&{operand: ts.NumericLiteral}; + +/** + * Return true if the node is either `!0` or `!1`. + */ +function isMinifiedBooleanLiteral(node: ts.Expression): node is MinifiedBooleanLiteral { + return ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken && + ts.isNumericLiteral(node.operand) && (node.operand.text === '0' || node.operand.text === '1'); +} diff --git a/packages/compiler-cli/linker/test/ast/typescript/typescript_ast_host_spec.ts b/packages/compiler-cli/linker/test/ast/typescript/typescript_ast_host_spec.ts index c904b50a4c6fd..1b840eee42aef 100644 --- a/packages/compiler-cli/linker/test/ast/typescript/typescript_ast_host_spec.ts +++ b/packages/compiler-cli/linker/test/ast/typescript/typescript_ast_host_spec.ts @@ -94,6 +94,11 @@ describe('TypeScriptAstHost', () => { expect(host.isBooleanLiteral(expr('false'))).toBe(true); }); + it('should return true if the expression is a minified boolean literal', () => { + expect(host.isBooleanLiteral(expr('!0'))).toBe(true); + expect(host.isBooleanLiteral(expr('!1'))).toBe(true); + }); + it('should return false if the expression is not a boolean literal', () => { expect(host.isBooleanLiteral(expr('"moo"'))).toBe(false); expect(host.isBooleanLiteral(expr('\'moo\''))).toBe(false); @@ -104,6 +109,8 @@ describe('TypeScriptAstHost', () => { expect(host.isBooleanLiteral(expr('null'))).toBe(false); expect(host.isBooleanLiteral(expr('\'a\' + \'b\''))).toBe(false); expect(host.isBooleanLiteral(expr('\`moo\`'))).toBe(false); + expect(host.isBooleanLiteral(expr('!2'))).toBe(false); + expect(host.isBooleanLiteral(expr('~1'))).toBe(false); }); }); @@ -113,6 +120,11 @@ describe('TypeScriptAstHost', () => { expect(host.parseBooleanLiteral(expr('false'))).toEqual(false); }); + it('should extract a minified boolean value', () => { + expect(host.parseBooleanLiteral(expr('!0'))).toEqual(true); + expect(host.parseBooleanLiteral(expr('!1'))).toEqual(false); + }); + it('should error if the value is not a boolean literal', () => { expect(() => host.parseBooleanLiteral(expr('"moo"'))) .toThrowError('Unsupported syntax, expected a boolean literal.');