Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ describe('type check blocks', () => {
expect(tcb('{{a * b ** c + d}}')).toContain(
'(((((this).a)) * ((((this).b)) ** (((this).c)))) + (((this).d)))',
);
expect(tcb('{{a ** b ** c}}')).toContain('((((this).a)) ** ((((this).b)) ** (((this).c))))');
});

it('should handle attribute values for directive inputs', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-
{{ typeof foo?.bar === 'string' }}
{{ typeof foo?.bar | identity }}
{{ void 'test' }}
{{ (-1) ** 3 }}
`, isInline: true, dependencies: [{ kind: "pipe", type: IdentityPipe, name: "identity" }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
type: Component,
Expand All @@ -111,6 +112,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
{{ typeof foo?.bar === 'string' }}
{{ typeof foo?.bar | identity }}
{{ void 'test' }}
{{ (-1) ** 3 }}
`,
imports: [IdentityPipe],
}]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class IdentityPipe {
{{ typeof foo?.bar === 'string' }}
{{ typeof foo?.bar | identity }}
{{ void 'test' }}
{{ (-1) ** 3 }}
`,
imports: [IdentityPipe],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ template: function MyApp_Template(rf, $ctx$) {
$i0$.ɵɵtext(0);
i0.ɵɵpipe(1, "identity");
} if (rf & 2) {
i0.ɵɵtextInterpolate8(" ",
i0.ɵɵtextInterpolateV([" ",
1 + 2, " ",
1 % 2 + 3 / 4 * 5 ** 6, " ",
+1, " ",
typeof i0.ɵɵpureFunction0(10, _c0) === "object", " ",
!(typeof i0.ɵɵpureFunction0(11, _c0) === "object"), " ",
typeof i0.ɵɵpureFunction0(11, _c0) === "object", " ",
!(typeof i0.ɵɵpureFunction0(12, _c0) === "object"), " ",
typeof (ctx.foo == null ? null : ctx.foo.bar) === "string", " ",
i0.ɵɵpipeBind1(1, 8, typeof (ctx.foo == null ? null : ctx.foo.bar)), " ",
void "test", " "
);
i0.ɵɵpipeBind1(1, 9, typeof (ctx.foo == null ? null : ctx.foo.bar)), " ",
void "test", " ",
(-1) ** 3, " "
]);
}
}

2 changes: 1 addition & 1 deletion packages/compiler/src/expression_parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@ class _ParseAST {
);
}
this.advance();
const right = this.parsePrefix();
const right = this.parseExponentiation();
result = new Binary(this.span(start), this.sourceSpan(start), '**', result, right);
}
return result;
Expand Down
14 changes: 8 additions & 6 deletions packages/compiler/src/output/output_ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1687,12 +1687,6 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any): any {
return ast;
}
visitTypeofExpr(ast: TypeofExpr, context: any): any {
return this.visitExpression(ast, context);
}
visitVoidExpr(ast: VoidExpr, context: any) {
return this.visitExpression(ast, context);
}
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
return this.visitExpression(ast, context);
}
Expand Down Expand Up @@ -1770,6 +1764,14 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor
ast.expr.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitTypeofExpr(ast: TypeofExpr, context: any): any {
ast.expr.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitVoidExpr(ast: VoidExpr, context: any) {
ast.expr.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
ast.lhs.visitExpression(this, context);
ast.rhs.visitExpression(this, context);
Expand Down
14 changes: 8 additions & 6 deletions packages/compiler/src/template/pipeline/src/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import {
import {deleteAnyCasts} from './phases/any_cast';
import {applyI18nExpressions} from './phases/apply_i18n_expressions';
import {assignI18nSlotDependencies} from './phases/assign_i18n_slot_dependencies';
import {attachSourceLocations} from './phases/attach_source_locations';
import {extractAttributes} from './phases/attribute_extraction';
import {specializeBindings} from './phases/binding_specialization';
import {chain} from './phases/chaining';
import {collapseSingletonInterpolations} from './phases/collapse_singleton_interpolations';
import {generateConditionalExpressions} from './phases/conditionals';
import {collectElementConsts} from './phases/const_collection';
import {convertI18nBindings} from './phases/convert_i18n_bindings';
import {resolveDeferDepsFns} from './phases/resolve_defer_deps_fns';
import {createI18nContexts} from './phases/create_i18n_contexts';
import {deduplicateTextBindings} from './phases/deduplicate_text_bindings';
import {configureDeferInstructions} from './phases/defer_configs';
Expand All @@ -38,6 +38,7 @@ import {collapseEmptyInstructions} from './phases/empty_elements';
import {expandSafeReads} from './phases/expand_safe_reads';
import {extractI18nMessages} from './phases/extract_i18n_messages';
import {generateAdvance} from './phases/generate_advance';
import {generateLocalLetReferences} from './phases/generate_local_let_references';
import {generateProjectionDefs} from './phases/generate_projection_def';
import {generateVariables} from './phases/generate_variables';
import {collectConstExpressions} from './phases/has_const_expression_collection';
Expand All @@ -62,27 +63,27 @@ import {generatePureLiteralStructures} from './phases/pure_literal_structures';
import {reify} from './phases/reify';
import {removeEmptyBindings} from './phases/remove_empty_bindings';
import {removeI18nContexts} from './phases/remove_i18n_contexts';
import {removeIllegalLetReferences} from './phases/remove_illegal_let_references';
import {removeUnusedI18nAttributesOps} from './phases/remove_unused_i18n_attrs';
import {requiredParentheses} from './phases/required_parentheses';
import {resolveContexts} from './phases/resolve_contexts';
import {resolveDeferDepsFns} from './phases/resolve_defer_deps_fns';
import {resolveDollarEvent} from './phases/resolve_dollar_event';
import {resolveI18nElementPlaceholders} from './phases/resolve_i18n_element_placeholders';
import {resolveI18nExpressionPlaceholders} from './phases/resolve_i18n_expression_placeholders';
import {resolveNames} from './phases/resolve_names';
import {resolveSanitizers} from './phases/resolve_sanitizers';
import {transformTwoWayBindingSet} from './phases/transform_two_way_binding_set';
import {saveAndRestoreView} from './phases/save_restore_view';
import {allocateSlots} from './phases/slot_allocation';
import {optimizeStoreLet} from './phases/store_let_optimization';
import {specializeStyleBindings} from './phases/style_binding_specialization';
import {generateTemporaryVariables} from './phases/temporary_variables';
import {optimizeTrackFns} from './phases/track_fn_optimization';
import {generateTrackVariables} from './phases/track_variables';
import {transformTwoWayBindingSet} from './phases/transform_two_way_binding_set';
import {countVariables} from './phases/var_counting';
import {optimizeVariables} from './phases/variable_optimization';
import {wrapI18nIcus} from './phases/wrap_icus';
import {optimizeStoreLet} from './phases/store_let_optimization';
import {removeIllegalLetReferences} from './phases/remove_illegal_let_references';
import {generateLocalLetReferences} from './phases/generate_local_let_references';
import {attachSourceLocations} from './phases/attach_source_locations';

type Phase =
| {
Expand Down Expand Up @@ -139,6 +140,7 @@ const phases: Phase[] = [
{kind: Kind.Both, fn: resolveSanitizers},
{kind: Kind.Tmpl, fn: liftLocalRefs},
{kind: Kind.Both, fn: generateNullishCoalesceExpressions},
{kind: Kind.Both, fn: requiredParentheses},
{kind: Kind.Both, fn: expandSafeReads},
{kind: Kind.Both, fn: generateTemporaryVariables},
{kind: Kind.Both, fn: optimizeVariables},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import * as o from '../../../../output/output_ast';
import * as ir from '../../ir';
import type {CompilationJob} from '../compilation';

// TODO: create AST for parentheses when parsing, then we can remove the unnecessary ones instead of
// adding them out of thin air. This should simplify the parsing and give us valid spans for the
// parentheses.

/**
* In some cases we need to add parentheses to expressions for them to be considered valid
* JavaScript. This phase adds parentheses to cover such cases. Currently these cases are:
*
* 1. Unary operators in the base of an exponentiation expression. For example, `-2 ** 3` is not
* valid JavaScript, but `(-2) ** 3` is.
*/
export function requiredParentheses(job: CompilationJob): void {
for (const unit of job.units) {
for (const op of unit.ops()) {
ir.transformExpressionsInOp(
op,
(expr) => {
if (
expr instanceof o.BinaryOperatorExpr &&
expr.operator === o.BinaryOperator.Exponentiation &&
expr.lhs instanceof o.UnaryOperatorExpr
) {
expr.lhs = new o.ParenthesizedExpr(expr.lhs);
}
return expr;
},
ir.VisitorContextFlag.None,
);
}
}
}
10 changes: 10 additions & 0 deletions packages/core/test/acceptance/integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2787,6 +2787,16 @@ describe('acceptance integration tests', () => {
expect(fixture.nativeElement.textContent).toEqual('1.03 | 0.97');
});

it('should have right-to-left associativity for exponentiation', () => {
@Component({
template: '{{2 ** 2 ** 3}}',
})
class TestComponent {}
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toEqual('256');
});

describe('tView.firstUpdatePass', () => {
function isFirstUpdatePass() {
const lView = getLView();
Expand Down
8 changes: 8 additions & 0 deletions packages/language-service/test/quick_info_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,14 @@ describe('quick info', () => {
const documentation = toText(quickInfo!.documentation);
expect(documentation).toBe('This is the title of the `AppCmp` Component.');
});

it('should work with parenthesized exponentiation expression', () => {
expectQuickInfo({
templateOverride: `{{ (-¦anyValue) ** 2 }}`,
expectedSpanText: 'anyValue',
expectedDisplayString: '(property) AppCmp.anyValue: any',
});
});
});

describe('blocks', () => {
Expand Down