Skip to content

Commit 7c9b489

Browse files
committed
fix(compiler-cli): preserve required parens in exponentiation expressions (#60101)
Parentheses are required around a unary operator used in the base of an exponentiation expression. For example: `(-1) ** 3` PR Close #60101
1 parent 4fe489f commit 7c9b489

File tree

7 files changed

+70
-13
lines changed

7 files changed

+70
-13
lines changed

packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe('type check blocks', () => {
7979
expect(tcb('{{a * b ** c + d}}')).toContain(
8080
'(((((this).a)) * ((((this).b)) ** (((this).c)))) + (((this).d)))',
8181
);
82-
expect(tcb('{{a ** b ** c}}')).toContain('blah');
82+
expect(tcb('{{a ** b ** c}}')).toContain('((((this).a)) ** ((((this).b)) ** (((this).c))))');
8383
});
8484

8585
it('should handle attribute values for directive inputs', () => {

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/GOLDEN_PARTIAL.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-
9898
{{ typeof foo?.bar === 'string' }}
9999
{{ typeof foo?.bar | identity }}
100100
{{ void 'test' }}
101+
{{ (-1) ** 3 }}
101102
`, isInline: true, dependencies: [{ kind: "pipe", type: IdentityPipe, name: "identity" }] });
102103
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
103104
type: Component,
@@ -111,6 +112,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
111112
{{ typeof foo?.bar === 'string' }}
112113
{{ typeof foo?.bar | identity }}
113114
{{ void 'test' }}
115+
{{ (-1) ** 3 }}
114116
`,
115117
imports: [IdentityPipe],
116118
}]

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/operators.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class IdentityPipe {
1717
{{ typeof foo?.bar === 'string' }}
1818
{{ typeof foo?.bar | identity }}
1919
{{ void 'test' }}
20+
{{ (-1) ** 3 }}
2021
`,
2122
imports: [IdentityPipe],
2223
})

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/operators_template.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ template: function MyApp_Template(rf, $ctx$) {
33
$i0$.ɵɵtext(0);
44
i0.ɵɵpipe(1, "identity");
55
} if (rf & 2) {
6-
i0.ɵɵtextInterpolate8(" ",
6+
i0.ɵɵtextInterpolateV([" ",
77
1 + 2, " ",
88
1 % 2 + 3 / 4 * 5 ** 6, " ",
99
+1, " ",
10-
typeof i0.ɵɵpureFunction0(10, _c0) === "object", " ",
11-
!(typeof i0.ɵɵpureFunction0(11, _c0) === "object"), " ",
10+
typeof i0.ɵɵpureFunction0(11, _c0) === "object", " ",
11+
!(typeof i0.ɵɵpureFunction0(12, _c0) === "object"), " ",
1212
typeof (ctx.foo == null ? null : ctx.foo.bar) === "string", " ",
13-
i0.ɵɵpipeBind1(1, 8, typeof (ctx.foo == null ? null : ctx.foo.bar)), " ",
14-
void "test", " "
15-
);
13+
i0.ɵɵpipeBind1(1, 9, typeof (ctx.foo == null ? null : ctx.foo.bar)), " ",
14+
void "test", " ",
15+
(-1) ** 3, " "
16+
]);
1617
}
1718
}
1819

packages/compiler/src/template/pipeline/src/emit.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ import {
2222
import {deleteAnyCasts} from './phases/any_cast';
2323
import {applyI18nExpressions} from './phases/apply_i18n_expressions';
2424
import {assignI18nSlotDependencies} from './phases/assign_i18n_slot_dependencies';
25+
import {attachSourceLocations} from './phases/attach_source_locations';
2526
import {extractAttributes} from './phases/attribute_extraction';
2627
import {specializeBindings} from './phases/binding_specialization';
2728
import {chain} from './phases/chaining';
2829
import {collapseSingletonInterpolations} from './phases/collapse_singleton_interpolations';
2930
import {generateConditionalExpressions} from './phases/conditionals';
3031
import {collectElementConsts} from './phases/const_collection';
3132
import {convertI18nBindings} from './phases/convert_i18n_bindings';
32-
import {resolveDeferDepsFns} from './phases/resolve_defer_deps_fns';
3333
import {createI18nContexts} from './phases/create_i18n_contexts';
3434
import {deduplicateTextBindings} from './phases/deduplicate_text_bindings';
3535
import {configureDeferInstructions} from './phases/defer_configs';
@@ -38,6 +38,7 @@ import {collapseEmptyInstructions} from './phases/empty_elements';
3838
import {expandSafeReads} from './phases/expand_safe_reads';
3939
import {extractI18nMessages} from './phases/extract_i18n_messages';
4040
import {generateAdvance} from './phases/generate_advance';
41+
import {generateLocalLetReferences} from './phases/generate_local_let_references';
4142
import {generateProjectionDefs} from './phases/generate_projection_def';
4243
import {generateVariables} from './phases/generate_variables';
4344
import {collectConstExpressions} from './phases/has_const_expression_collection';
@@ -62,27 +63,27 @@ import {generatePureLiteralStructures} from './phases/pure_literal_structures';
6263
import {reify} from './phases/reify';
6364
import {removeEmptyBindings} from './phases/remove_empty_bindings';
6465
import {removeI18nContexts} from './phases/remove_i18n_contexts';
66+
import {removeIllegalLetReferences} from './phases/remove_illegal_let_references';
6567
import {removeUnusedI18nAttributesOps} from './phases/remove_unused_i18n_attrs';
68+
import {requiredParentheses} from './phases/required_parentheses';
6669
import {resolveContexts} from './phases/resolve_contexts';
70+
import {resolveDeferDepsFns} from './phases/resolve_defer_deps_fns';
6771
import {resolveDollarEvent} from './phases/resolve_dollar_event';
6872
import {resolveI18nElementPlaceholders} from './phases/resolve_i18n_element_placeholders';
6973
import {resolveI18nExpressionPlaceholders} from './phases/resolve_i18n_expression_placeholders';
7074
import {resolveNames} from './phases/resolve_names';
7175
import {resolveSanitizers} from './phases/resolve_sanitizers';
72-
import {transformTwoWayBindingSet} from './phases/transform_two_way_binding_set';
7376
import {saveAndRestoreView} from './phases/save_restore_view';
7477
import {allocateSlots} from './phases/slot_allocation';
78+
import {optimizeStoreLet} from './phases/store_let_optimization';
7579
import {specializeStyleBindings} from './phases/style_binding_specialization';
7680
import {generateTemporaryVariables} from './phases/temporary_variables';
7781
import {optimizeTrackFns} from './phases/track_fn_optimization';
7882
import {generateTrackVariables} from './phases/track_variables';
83+
import {transformTwoWayBindingSet} from './phases/transform_two_way_binding_set';
7984
import {countVariables} from './phases/var_counting';
8085
import {optimizeVariables} from './phases/variable_optimization';
8186
import {wrapI18nIcus} from './phases/wrap_icus';
82-
import {optimizeStoreLet} from './phases/store_let_optimization';
83-
import {removeIllegalLetReferences} from './phases/remove_illegal_let_references';
84-
import {generateLocalLetReferences} from './phases/generate_local_let_references';
85-
import {attachSourceLocations} from './phases/attach_source_locations';
8687

8788
type Phase =
8889
| {
@@ -139,6 +140,7 @@ const phases: Phase[] = [
139140
{kind: Kind.Both, fn: resolveSanitizers},
140141
{kind: Kind.Tmpl, fn: liftLocalRefs},
141142
{kind: Kind.Both, fn: generateNullishCoalesceExpressions},
143+
{kind: Kind.Both, fn: requiredParentheses},
142144
{kind: Kind.Both, fn: expandSafeReads},
143145
{kind: Kind.Both, fn: generateTemporaryVariables},
144146
{kind: Kind.Both, fn: optimizeVariables},
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import * as o from '../../../../output/output_ast';
10+
import * as ir from '../../ir';
11+
import type {CompilationJob} from '../compilation';
12+
13+
// TODO: create AST for parentheses when parsing, then we can remove the unnecessary ones instead of
14+
// adding them out of thin air. This should simplify the parsing and give us valid spans for the
15+
// parentheses.
16+
17+
/**
18+
* In some cases we need to add parentheses to expressions for them to be considered valid
19+
* JavaScript. This phase adds parentheses to cover such cases. Currently these cases are:
20+
*
21+
* 1. Unary operators in the base of an exponentiation expression. For example, `-2 ** 3` is not
22+
* valid JavaScript, but `(-2) ** 3` is.
23+
*/
24+
export function requiredParentheses(job: CompilationJob): void {
25+
for (const unit of job.units) {
26+
for (const op of unit.ops()) {
27+
ir.transformExpressionsInOp(
28+
op,
29+
(expr) => {
30+
if (
31+
expr instanceof o.BinaryOperatorExpr &&
32+
expr.operator === o.BinaryOperator.Exponentiation &&
33+
expr.lhs instanceof o.UnaryOperatorExpr
34+
) {
35+
expr.lhs = new o.ParenthesizedExpr(expr.lhs);
36+
}
37+
return expr;
38+
},
39+
ir.VisitorContextFlag.None,
40+
);
41+
}
42+
}
43+
}

packages/language-service/test/quick_info_spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,14 @@ describe('quick info', () => {
625625
const documentation = toText(quickInfo!.documentation);
626626
expect(documentation).toBe('This is the title of the `AppCmp` Component.');
627627
});
628+
629+
it('should work with parenthesized exponentiation expression', () => {
630+
expectQuickInfo({
631+
templateOverride: `{{ (-¦anyValue) ** 2 }}`,
632+
expectedSpanText: 'anyValue',
633+
expectedDisplayString: '(property) AppCmp.anyValue: any',
634+
});
635+
});
628636
});
629637

630638
describe('blocks', () => {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy