diff --git a/parser/v2/ifexpressionparser.go b/parser/v2/ifexpressionparser.go index 7e51b786..aa0995af 100644 --- a/parser/v2/ifexpressionparser.go +++ b/parser/v2/ifexpressionparser.go @@ -54,13 +54,26 @@ func (ifExpressionParser) Parse(pi *parse.Input) (n Node, matched bool, err erro } // Read the optional 'Else' Nodes. - var elseNodes Nodes - if elseNodes, _, err = elseExpression.Parse(pi); err != nil { - // Populate the nodes anyway, so that the LSP can use them. - r.Else = elseNodes.Nodes + elseIdx := pi.Index() + var elseMatched bool + if _, elseMatched, err = elseKeyword.Parse(pi); err != nil { return r, true, err } - r.Else = elseNodes.Nodes + if elseMatched { + r.ElseRange = NewRange(pi.PositionAt(elseIdx), pi.Position()) + if _, _, err = parse.OptionalWhitespace.Parse(pi); err != nil { + return r, true, err + } + var elseNodes Nodes + np := newTemplateNodeParser(closeBraceWithOptionalPadding, "else expression closing brace") + if elseNodes, _, err = np.Parse(pi); err != nil { + r.Else = elseNodes.Nodes + return r, true, err + } + r.Else = elseNodes.Nodes + } else { + pi.Seek(elseIdx) + } // Read the required closing brace. if _, matched, err = closeBraceWithOptionalPadding.Parse(pi); err != nil || !matched { @@ -113,6 +126,15 @@ func (elseIfExpressionParser) Parse(pi *parse.Input) (r ElseIfExpression, matche return r, true, nil } +// elseKeyword matches "} else {" without consuming trailing whitespace, so the +// caller can record its precise source range before the body begins. +var elseKeyword = parse.All( + parse.Rune('}'), + parse.OptionalWhitespace, + parse.String("else"), + parse.OptionalWhitespace, + parse.Rune('{')) + var endElseParser = parse.All( parse.Rune('}'), parse.OptionalWhitespace, diff --git a/parser/v2/ifexpressionparser_test.go b/parser/v2/ifexpressionparser_test.go index 7c64c670..371104ff 100644 --- a/parser/v2/ifexpressionparser_test.go +++ b/parser/v2/ifexpressionparser_test.go @@ -180,6 +180,10 @@ func TestIfExpression(t *testing.T) { }, }, }, + ElseRange: Range{ + From: Position{Index: 18, Line: 2, Col: 0}, + To: Position{Index: 26, Line: 2, Col: 8}, + }, Range: Range{ From: Position{Index: 0, Line: 0, Col: 0}, To: Position{Index: 37, Line: 4, Col: 1}, @@ -404,12 +408,78 @@ func TestIfExpression(t *testing.T) { }, }, }, + ElseRange: Range{ + From: Position{Index: 17, Line: 2, Col: 0}, + To: Position{Index: 25, Line: 2, Col: 8}, + }, Range: Range{ From: Position{Index: 0, Line: 0, Col: 0}, To: Position{Index: 36, Line: 4, Col: 1}, }, }, }, + { + name: "if: else, keyword spans multiple lines", + input: `if p.A { + { "A" } +} +else { + { "B" } +}`, + expected: &IfExpression{ + Expression: Expression{ + Value: `p.A`, + Range: Range{ + From: Position{Index: 3, Line: 0, Col: 3}, + To: Position{Index: 6, Line: 0, Col: 6}, + }, + }, + Then: []Node{ + &Whitespace{Range: Range{ + From: Position{Index: 9, Line: 1, Col: 0}, + To: Position{Index: 10, Line: 1, Col: 1}, + }, Value: "\t"}, + &StringExpression{ + Expression: Expression{ + Value: `"A"`, + Range: Range{ + From: Position{Index: 12, Line: 1, Col: 3}, + To: Position{Index: 15, Line: 1, Col: 6}, + }, + }, + TrailingSpace: SpaceVertical, + Range: Range{ + From: Position{Index: 10, Line: 1, Col: 1}, + To: Position{Index: 18, Line: 2, Col: 0}, + }, + }, + }, + Else: []Node{ + &StringExpression{ + Expression: Expression{ + Value: `"B"`, + Range: Range{ + From: Position{Index: 30, Line: 4, Col: 3}, + To: Position{Index: 33, Line: 4, Col: 6}, + }, + }, + TrailingSpace: SpaceVertical, + Range: Range{ + From: Position{Index: 28, Line: 4, Col: 1}, + To: Position{Index: 36, Line: 5, Col: 0}, + }, + }, + }, + ElseRange: Range{ + From: Position{Index: 18, Line: 2, Col: 0}, + To: Position{Index: 26, Line: 3, Col: 6}, + }, + Range: Range{ + From: Position{Index: 0, Line: 0, Col: 0}, + To: Position{Index: 37, Line: 5, Col: 1}, + }, + }, + }, { name: "if: nested", input: `if p.A { @@ -921,6 +991,10 @@ func TestIfExpression(t *testing.T) { }, }, }, + ElseRange: Range{ + From: Position{Index: 68, Line: 6, Col: 0}, + To: Position{Index: 76, Line: 6, Col: 8}, + }, Range: Range{ From: Position{Index: 0, Line: 0, Col: 0}, To: Position{Index: 87, Line: 8, Col: 1}, @@ -1010,6 +1084,10 @@ func TestIfExpression(t *testing.T) { }, }, }, + ElseRange: Range{ + From: Position{Index: 33, Line: 2, Col: 1}, + To: Position{Index: 41, Line: 2, Col: 9}, + }, Range: Range{ From: Position{Index: 0, Line: 0, Col: 0}, To: Position{Index: 54, Line: 4, Col: 2}, diff --git a/parser/v2/raw.go b/parser/v2/raw.go index c18c7719..5a66f5b8 100644 --- a/parser/v2/raw.go +++ b/parser/v2/raw.go @@ -54,11 +54,13 @@ func (p rawElementParser) Parse(pi *parse.Input) (n Node, ok bool, err error) { // Once we've got an open tag, parse anything until the end tag as the tag contents. // It's going to be rendered out raw. + contentsStart := pi.Position() end := parse.All(parse.String("")) if e.Contents, ok, err = parse.StringUntil(end).Parse(pi); err != nil || !ok { err = parse.Error(fmt.Sprintf("<%s>: expected end tag not present", e.Name), pi.Position()) return } + e.ContentsRange = NewRange(contentsStart, pi.Position()) // Cut the end element. _, _, _ = end.Parse(pi) diff --git a/parser/v2/raw_test.go b/parser/v2/raw_test.go index dd0b9499..8bdb02de 100644 --- a/parser/v2/raw_test.go +++ b/parser/v2/raw_test.go @@ -44,6 +44,10 @@ func TestRawElementParser(t *testing.T) { }, }, Contents: "contents", + ContentsRange: Range{ + From: Position{Index: 23, Line: 0, Col: 23}, + To: Position{Index: 31, Line: 0, Col: 31}, + }, Range: Range{ From: Position{Index: 0, Line: 0, Col: 0}, To: Position{Index: 39, Line: 0, Col: 39}, @@ -76,6 +80,10 @@ func TestRawElementParser(t *testing.T) { }, }, Contents: ignoredContent, + ContentsRange: Range{ + From: Position{Index: 23, Line: 0, Col: 23}, + To: Position{Index: 44, Line: 3, Col: 1}, + }, Range: Range{ From: Position{Index: 0, Line: 0, Col: 0}, To: Position{Index: 52, Line: 3, Col: 9}, diff --git a/parser/v2/types.go b/parser/v2/types.go index 4c268930..aa633686 100644 --- a/parser/v2/types.go +++ b/parser/v2/types.go @@ -795,10 +795,11 @@ func writeStrings(w io.Writer, ss ...string) error { } type RawElement struct { - Name string - Attributes []Attribute - Contents string - Range Range + Name string + Attributes []Attribute + Contents string + ContentsRange Range + Range Range } func (e *RawElement) IsNode() bool { return true } @@ -1335,6 +1336,7 @@ type IfExpression struct { Then []Node ElseIfs []ElseIfExpression Else []Node + ElseRange Range // source position of the "} else {" keyword; zero if no else clause Range Range }