Skip to content

Commit

Permalink
fix(compiler-cli): allow linker to process minified booleans
Browse files Browse the repository at this point in the history
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 angular#41655
  • Loading branch information
petebacondarwin committed Apr 22, 2021
1 parent a4a55f0 commit ea85ff5
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 5 deletions.
19 changes: 17 additions & 2 deletions packages/compiler-cli/linker/babel/src/ast/babel_ast_host.ts
Expand Up @@ -38,10 +38,15 @@ export class BabelAstHost implements AstHost<t.Expression> {
return num.value;
}

isBooleanLiteral = t.isBooleanLiteral;
isBooleanLiteral(bool: t.Expression): bool is t.BooleanLiteral|MinifiedBooleanLiteral {
return t.isBooleanLiteral(bool) || isMinifiedBooleanLiteral(bool);
}

parseBooleanLiteral(bool: t.Expression): boolean {
assert(bool, t.isBooleanLiteral, 'a boolean literal');
assert(bool, this.isBooleanLiteral, 'a boolean literal');
if (isMinifiedBooleanLiteral(bool)) {
return !(bool as MinifiedBooleanLiteral).argument.value;
}
return bool.value;
}

Expand Down Expand Up @@ -165,3 +170,13 @@ type ArgumentType = t.CallExpression['arguments'][number];
function isNotSpreadArgument(arg: ArgumentType): arg is Exclude<ArgumentType, t.SpreadElement> {
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);
}
10 changes: 10 additions & 0 deletions packages/compiler-cli/linker/babel/test/ast/babel_ast_host_spec.ts
Expand Up @@ -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);
Expand All @@ -115,6 +120,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.');
Expand Down
8 changes: 7 additions & 1 deletion packages/compiler-cli/linker/src/ast/ast_host.ts
Expand Up @@ -36,11 +36,17 @@ export interface AstHost<TExpression> {
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;

Expand Down
Expand Up @@ -47,12 +47,18 @@ export class TypeScriptAstHost implements AstHost<ts.Expression> {
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): node is ts.FalseLiteral|ts.TrueLiteral
|MinifiedBooleanLiteral {
return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword ||
isMinifiedBooleanLiteral(node);
}

parseBooleanLiteral(bool: ts.Expression): boolean {
assert(bool, this.isBooleanLiteral, 'a boolean literal');
if (isMinifiedBooleanLiteral(bool)) {
// Convert the string operand ("0" or "1") to a number and negate it to get the boolean.
return !(+bool.operand.text);
}
return bool.kind === ts.SyntaxKind.TrueKeyword;
}

Expand Down Expand Up @@ -160,3 +166,13 @@ 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);
}

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');
}
Expand Up @@ -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);
Expand All @@ -113,6 +118,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.');
Expand Down

0 comments on commit ea85ff5

Please sign in to comment.