this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

all: allow newlines around interpolations in single-line strings

The parser now accepts newlines between the \( and ) of a string
interpolation inside single-line string literals, just as it already
does inside multi-line string literals. This makes constructs like

x: "\(
expr)"

parse successfully.

The formatter then normalizes the surrounding whitespace according to
the kind of string literal:

* In single-line string literals, any newlines inside an interpolation
expression are collapsed, so the result stays on one line.
This aligns user code with how Swift interpolations work as well.

* In multi-line string literals, the newlines around each
interpolation expression are balanced so that they are either both
present or both absent; if only one of them is present, the
formatter inserts the other.

Fixes #309.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: Ifa40499f2e51f3ed99065d8aa2f28711487bec9c
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1228254
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>

+156 -7
+72
cue/format/node.go
··· 694 694 f.before(nil) 695 695 // Reindent multiline interpolation string fragments directly. 696 696 search, replace := f.interpolationReindent(x) 697 + // For multi-line string literals, enforce that the newlines 698 + // surrounding each interpolation expression are either both 699 + // present or both absent; if only one is present, force the 700 + // other. For single-line string literals, collapse all newlines 701 + // inside interpolation expressions so the result stays on one line. 702 + forceNewline := interpolationNormalize(x) 697 703 for i, el := range x.Elts { 704 + if i < len(forceNewline) && forceNewline[i] { 705 + f.print(newline, nooverride) 706 + } 698 707 // Only reindent even-indexed elements, which are the string 699 708 // fragments; odd-indexed elements are expressions like "bar" 700 709 // in \("bar") which may also be BasicLits. ··· 835 844 default: 836 845 panic(fmt.Sprintf("unimplemented type %T", x)) 837 846 } 847 + } 848 + 849 + // interpolationNormalize normalizes the whitespace surrounding each 850 + // \(...) interpolation expression in x. 851 + // 852 + // For multi-line string literals, it returns a slice of the same length 853 + // as x.Elts indicating which elements should be preceded by an inserted 854 + // newline so that the leading and trailing newlines around each 855 + // interpolation expression are either both present or both absent. 856 + // 857 + // For single-line string literals, it mutates positions within each 858 + // interpolation expression and its closing fragment so that no 859 + // position-driven newlines are emitted, collapsing the interpolation 860 + // to a single line. 861 + func interpolationNormalize(x *ast.Interpolation) []bool { 862 + if len(x.Elts) < 3 { 863 + return nil 864 + } 865 + first, ok := x.Elts[0].(*ast.BasicLit) 866 + if !ok { 867 + return nil 868 + } 869 + last, ok := x.Elts[len(x.Elts)-1].(*ast.BasicLit) 870 + if !ok { 871 + return nil 872 + } 873 + qi, _, _, _ := literal.ParseQuotes(first.Value, last.Value) 874 + if !qi.IsMulti() { 875 + // Remove newlines from all elements except the first. 876 + for _, e := range x.Elts[1:] { 877 + ast.Walk(e, func(n ast.Node) bool { 878 + if n != nil && n.Pos().RelPos() >= token.Newline { 879 + ast.SetRelPos(n, token.NoRelPos) 880 + } 881 + return true 882 + }, nil) 883 + } 884 + return nil 885 + } 886 + var force []bool 887 + for i := 1; i+1 < len(x.Elts); i += 2 { 888 + prev, expr, next := x.Elts[i-1], x.Elts[i], x.Elts[i+1] 889 + // Skip if any relevant position is missing, as can happen for 890 + // AST nodes synthesized without positions. 891 + if !expr.Pos().IsValid() || !prev.End().IsValid() || 892 + !next.Pos().IsValid() || !expr.End().IsValid() { 893 + continue 894 + } 895 + hasLeading := expr.Pos().Line() > prev.End().Line() 896 + hasTrailing := next.Pos().Line() > expr.End().Line() 897 + if hasLeading == hasTrailing { 898 + continue 899 + } 900 + if force == nil { 901 + force = make([]bool, len(x.Elts)) 902 + } 903 + if hasLeading { 904 + force[i+1] = true 905 + } else { 906 + force[i] = true 907 + } 908 + } 909 + return force 838 910 } 839 911 840 912 // interpolationReindent returns a search/replace pair to reindent multiline
+75
cue/format/testdata/interpolation.txtar
··· 1 + 2 + -- interpolation.input -- 3 + package p 4 + 5 + // Single-line strings: any newlines inside interpolations collapse to one line. 6 + singleInline: "\(1)" 7 + singleLeading: "\( 8 + 1)" 9 + singleTrailing: "\(1 10 + )" 11 + singleInner: "\(1 + 12 + 2)" 13 + singleBoth: "\( 14 + 1 15 + )" 16 + singleTwo: "\(1) is \( 17 + 2)" 18 + 19 + // Multi-line strings: surrounding newlines around each interpolation are 20 + // balanced so they are either both present or both absent. 21 + multiBoth: """ 22 + \( 23 + 1 24 + ) 25 + """ 26 + multiLeading: """ 27 + \( 28 + 1) 29 + """ 30 + multiTrailing: """ 31 + \(1 32 + ) 33 + """ 34 + multiNeither: """ 35 + \(1) 36 + """ 37 + multiInner: """ 38 + \(1 + 39 + 2) 40 + """ 41 + -- out/format/interpolation.golden -- 42 + package p 43 + 44 + // Single-line strings: any newlines inside interpolations collapse to one line. 45 + singleInline: "\(1)" 46 + singleLeading: "\(1)" 47 + singleTrailing: "\(1)" 48 + singleInner: "\(1+2)" 49 + singleBoth: "\(1)" 50 + singleTwo: "\(1) is \(2)" 51 + 52 + // Multi-line strings: surrounding newlines around each interpolation are 53 + // balanced so they are either both present or both absent. 54 + multiBoth: """ 55 + \( 56 + 1 57 + ) 58 + """ 59 + multiLeading: """ 60 + \( 61 + 1 62 + ) 63 + """ 64 + multiTrailing: """ 65 + \( 66 + 1 67 + ) 68 + """ 69 + multiNeither: """ 70 + \(1) 71 + """ 72 + multiInner: """ 73 + \(1+ 74 + 2) 75 + """
+6
cue/parser/parser.go
··· 1780 1780 exprs = append(exprs, p.parseRHS()) 1781 1781 1782 1782 cc = p.openComments() 1783 + // If a comma was inserted, consume it to find the closing parenthesis. 1784 + // Newlines inside string interpolations are allowed; the formatter 1785 + // normalizes leading/trailing newlines to be both present or both absent. 1786 + if p.tok == token.COMMA && p.lit == "\n" { 1787 + p.next() 1788 + } 1783 1789 if p.tok != token.RPAREN { 1784 1790 p.errorExpected(p.pos, "')' for string interpolation") 1785 1791 }
+3 -7
cue/parser/parser_test.go
··· 909 909 desc: "interpolation with trailing whitespace and newline", 910 910 in: `"\(1 911 911 )"`, 912 - // TODO: this should be consistent with the two test cases above. 913 - out: `"\(1 )" 914 - expected ')' for string interpolation, found newline`, 912 + out: `"\(1)"`, 915 913 }, 916 914 { 917 915 desc: "interpolation with trailing comma", ··· 945 943 \(1 946 944 ) 947 945 """`, 948 - // TODO: this should be consistent with the two test cases above. 949 946 out: `""" 950 - \(1 ) 951 - """ 952 - expected ')' for string interpolation, found newline`, 947 + \(1) 948 + """`, 953 949 }, 954 950 { 955 951 desc: "multi-line interpolation with trailing comma",