Repository: angular/angular
File: packages/compiler/src/expression_parser/parser.ts (TypeScript)

Code before:
  private parsePrimary(): AST {
    const start = this.inputIndex;
    if (this.consumeOptionalCharacter(chars.$LPAREN)) {
      this.rparensExpected++;
      const result = this.parsePipe();
      if (!this.consumeOptionalCharacter(chars.$RPAREN)) {
        this.error('Missing closing parentheses');
        // Calling into `error` above will attempt to recover up until the next closing paren.
        // If that's the case, consume it so we can partially recover the expression.
        this.consumeOptionalCharacter(chars.$RPAREN);
      }
      this.rparensExpected--;
      return new ParenthesizedExpression(this.span(start), this.sourceSpan(start), result);
    } else if (this.next.isKeywordNull()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), null);
    } else if (this.next.isKeywordUndefined()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), void 0);
    } else if (this.next.isKeywordTrue()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), true);
    } else if (this.next.isKeywordFalse()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), false);
    } else if (this.next.isKeywordIn() && this.index > 0) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), 'in');
    } else if (this.next.isKeywordThis()) {
      this.advance();
      return new ThisReceiver(this.span(start), this.sourceSpan(start));
    } else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
      this.rbracketsExpected++;
      const elements = this.parseExpressionList(chars.$RBRACKET);
      this.rbracketsExpected--;
      this.expectCharacter(chars.$RBRACKET);
      return new LiteralArray(this.span(start), this.sourceSpan(start), elements);
    } else if (this.next.isCharacter(chars.$LBRACE)) {
      return this.parseLiteralMap();
    } else if (this.next.isIdentifier()) {
      return this.parseAccessMember(
        new ImplicitReceiver(this.span(start), this.sourceSpan(start)),
        start,
        false,
      );
    } else if (this.next.isNumber()) {
      const value = this.next.toNumber();
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), value);
    } else if (this.next.isTemplateLiteralEnd()) {
      return this.parseNoInterpolationTemplateLiteral();

Review comment (suggestion):
I don't follow the rationale here, nor for the original code; why would `in` be tokenized as keyword, yet interpreted as string... string literals should be tokenized as-is, so keyword tokens shouldn't be parsed to string literals.

Making this conditional on token index makes this even weirder. Concretely, I don't expect multi-statement bindings in outputs to be handled correctly:

```html
<a (click)="in; in"></a>
```

Code after:
        result = this.parseKeyedReadOrWrite(result, start, false);
      } else if (this.consumeOptionalCharacter(chars.$LPAREN)) {
        result = this.parseCall(result, start, false);
      } else if (this.consumeOptionalOperator('!')) {
        result = new NonNullAssert(this.span(start), this.sourceSpan(start), result);
      } else if (this.next.isTemplateLiteralEnd()) {
        result = this.parseNoInterpolationTaggedTemplateLiteral(result, start);
      } else if (this.next.isTemplateLiteralPart()) {
        result = this.parseTaggedTemplateLiteral(result, start);
      } else {
        return result;
      }
    }
  }

  private parsePrimary(): AST {
    const start = this.inputIndex;
    if (this.isArrowFunction()) {
      return this.parseArrowFunction(start);
    } else if (this.consumeOptionalCharacter(chars.$LPAREN)) {
      this.rparensExpected++;
      const result = this.parsePipe();
      if (!this.consumeOptionalCharacter(chars.$RPAREN)) {
        this.error('Missing closing parentheses');
        // Calling into `error` above will attempt to recover up until the next closing paren.
        // If that's the case, consume it so we can partially recover the expression.
        this.consumeOptionalCharacter(chars.$RPAREN);
      }
      this.rparensExpected--;
      return new ParenthesizedExpression(this.span(start), this.sourceSpan(start), result);
    } else if (this.next.isKeywordNull()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), null);
    } else if (this.next.isKeywordUndefined()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), undefined);
    } else if (this.next.isKeywordTrue()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), true);
    } else if (this.next.isKeywordFalse()) {
      this.advance();
      return new LiteralPrimitive(this.span(start), this.sourceSpan(start), false);
    } else if (this.next.isKeywordThis()) {
      this.advance();
      return new ThisReceiver(this.span(start), this.sourceSpan(start));
    } else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
      return this.parseLiteralArray(start);
    } else if (this.next.isCharacter(chars.$LBRACE)) {
      return this.parseLiteralMap();
    } else if (this.next.isIdentifier()) {
      return this.parseAccessMember(