diff --git a/cue/testdata/export/issue2119.txtar b/cue/testdata/export/issue2119.txtar index 4879d953f..5783c9a2f 100644 --- a/cue/testdata/export/issue2119.txtar +++ b/cue/testdata/export/issue2119.txtar @@ -10,7 +10,7 @@ simplified: { [R+""][0] } out: yaml.Marshal(x) - @test(leq, {x: {d: ".*"}, out: string}) + @test(eq, {x: {d: ".*"}, out: "d: .*\n"}) } original: { @@ -32,8 +32,27 @@ original: { } } out: yaml.Marshal(rule) - // TODO(inline): support multi-line strings. - @test(leq, {out: string}) + @test(eq, { + #testableRegexp: { + string + #test: { + in: string + out: _|_ @test(err, code=incomplete, contains="original.#testableRegexp.#test.out: non-concrete value string in operand to +", pos=[21:29, 16:21, 17:3]) + } + } + rule: { + #patterns: { + bar: { + "(.*)" + #test: {in: "testcontent", out: "testcontent"} + } + } + } + out: """ + {} + + """ + }) } -- out/errors.txt -- [incomplete] original.#testableRegexp.#test.out: non-concrete value string in operand to +: diff --git a/internal/cuetxtar/inline_attr.go b/internal/cuetxtar/inline_attr.go index 9f3117779..6975b0815 100644 --- a/internal/cuetxtar/inline_attr.go +++ b/internal/cuetxtar/inline_attr.go @@ -589,8 +589,12 @@ func isInlineMode(archive *txtar.Archive) bool { if !strings.HasSuffix(f.Name, ".cue") { continue } - af, err := parser.ParseFile(f.Name, f.Data) - if err != nil { + // Use error-recovery parsing so that @test directives are detected + // even when the file has parse errors. Without this, a parse error + // causes us to skip the file entirely, resulting in a misleading + // "archive has no @test directives" report. + af, _ := parser.ParseFile(f.Name, f.Data, parser.AllErrors) + if af == nil { continue } if declsHaveTestAttrs(af.Decls) { diff --git a/internal/cuetxtar/inline_format.go b/internal/cuetxtar/inline_format.go index da86c0910..683600ae6 100644 --- a/internal/cuetxtar/inline_format.go +++ b/internal/cuetxtar/inline_format.go @@ -72,6 +72,17 @@ func (r *inlineRunner) eqFillAttrStr(exprStr, atStr string, pa parsedTestAttr) s } else { indent := r.attrLineIndent(pa) exprStr = strings.ReplaceAll(exprStr, "\n", "\n"+indent) + // Strip trailing whitespace from blank lines to avoid + // "non-matching whitespace" parse errors in multiline strings: + // a blank line that is all-whitespace must have zero characters + // (not indentation spaces) to satisfy CUE's multiline string rules. + lines := strings.Split(exprStr, "\n") + for i, line := range lines { + if strings.TrimSpace(line) == "" { + lines[i] = "" + } + } + exprStr = strings.Join(lines, "\n") } } if atStr != "" { diff --git a/internal/cuetxtar/inline_test.go b/internal/cuetxtar/inline_test.go index dbe831fc6..b2e66add9 100644 --- a/internal/cuetxtar/inline_test.go +++ b/internal/cuetxtar/inline_test.go @@ -19,6 +19,8 @@ import ( "strings" "testing" + "golang.org/x/tools/txtar" + "cuelang.org/go/cue" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/cuecontext" @@ -863,3 +865,21 @@ func TestParseErrArgs(t *testing.T) { } }) } + +// TestIsInlineModeParsedError verifies that isInlineMode returns true for an +// archive whose CUE file has a parse error in a @test attribute body. Before +// the AllErrors fix, isInlineMode skipped files with parse errors and returned +// false, producing a misleading "archive has no @test directives" error. +func TestIsInlineModeParsedError(t *testing.T) { + // A @test attribute whose argument is a multiline string with whitespace + // mismatch: the blank line has 1 tab but the closing """ has 2 tabs. + // parser.ParseFile fails on this input, but the partial AST still + // contains the @test attribute. + src := "x: 1 @test(eq, \"\"\"\n\t\thello\n\t\n\t\t\"\"\")" + ar := &txtar.Archive{ + Files: []txtar.File{{Name: "test.cue", Data: []byte(src)}}, + } + if !isInlineMode(ar) { + t.Error("isInlineMode returned false for archive with @test in parse-error file; want true") + } +} diff --git a/internal/cuetxtar/testdata/inline/eq_fill_multiline_blank.txtar b/internal/cuetxtar/testdata/inline/eq_fill_multiline_blank.txtar new file mode 100644 index 000000000..6c8303723 --- /dev/null +++ b/internal/cuetxtar/testdata/inline/eq_fill_multiline_blank.txtar @@ -0,0 +1,23 @@ +# Tests that CUE_UPDATE=1 generates valid (parseable) @test(eq, ...) output when +# the filled value contains a string with an embedded blank line. The generator +# re-indents the multiline expression; blank lines must remain completely empty +# (no trailing whitespace) so CUE's multiline-string whitespace rule is satisfied. +-- in/test.cue -- +outer: { + x: { + s: "line1\n\nline2" + } @test(eq) +} +-- out/update/test.cue -- +outer: { + x: { + s: "line1\n\nline2" + } @test(eq, {s: """ + line1 + + line2 + """}) +} +-- out/status.txt -- +update: output passes run +force: identical to update