this repo has no description
0
fork

Configure Feed

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

internal/cuetxtar: require commas between pos= specs

Commas between position specs in pos=[...] are now required.
Previously they were optional separators (whitespace worked too);
now pos=[0:5, 1:13] is the only accepted form. This simplifies
parsePosSpecs and write-back uses ", " consistently.

Update TestParsePosSpecs: space-only multi-spec input is now an error.
Update all txtar pos= annotations to use comma-separated form.

We also adjust the error message in case of extra positions
to inform the user that this can be okay, but that we need
to verify this.

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I3aaa3d47dad01fd3e32b5daa96e0f9620ff5fb0e
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1234942
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>

+52 -53
+4 -10
cue/testdata/basicrewrite/001_regexp.txtar
··· 8 8 b1: "a" 9 9 b2: =~"[a-z]{3}" @test(eq, "foo") 10 10 b2: "foo" 11 - b3: =~"[a-z]{4}" @test(err, code=eval, contains="out of bound", pos=[0:5 1:5]) 11 + b3: =~"[a-z]{4}" @test(err, code=eval, contains="out of bound", pos=[0:5, 1:5]) 12 12 b3: "foo" 13 13 b4: !~"[a-z]{4}" @test(eq, "foo") 14 14 b4: "foo" ··· 19 19 s4: =~"[a-z]" & !="b" @test(eq, =~"[a-z]" & !="b") // TODO: allow different order 20 20 21 21 e1: "foo" =~ 1 @test(err, code=eval, 22 - contains="cannot use 1", pos=[ 23 - 0:5 24 - 0:14 25 - ]) 22 + contains="cannot use 1", pos=[0:5, 0:14]) 26 23 e2: "foo" !~ true @test(err, code=eval, 27 - contains="cannot use true", pos=[ 28 - 0:5 29 - 0:14 30 - ]) 24 + contains="cannot use true", pos=[0:5, 0:14]) 31 25 e3: !="a" & <5 @test(err, code=eval, 32 - contains="conflicting values", pos=[0:5 0:13]) 26 + contains="conflicting values", pos=[0:5, 0:13]) 33 27 -- out/compile -- 34 28 --- in.cue 35 29 {
+1 -1
cue/testdata/basicrewrite/004_booleans.txtar
··· 3 3 t: !false 4 4 f: false @test(eq, false) 5 5 f: !t 6 - e: true @test(err, code=eval, pos=[0:4 1:4], contains="conflicting values") 6 + e: true @test(err, code=eval, pos=[0:4, 1:4], contains="conflicting values") 7 7 e: !true 8 8 -- out/errors.txt -- 9 9 [eval] e: conflicting values false and true:
+1 -1
cue/testdata/basicrewrite/005_boolean_arithmetic.txtar
··· 4 4 c: false == true @test(eq, false) 5 5 d: false != true @test(eq, true) 6 6 e: true & true @test(eq, true) 7 - f: true & false @test(err, code=eval, pos=[0:4 0:11]) 7 + f: true & false @test(err, code=eval, pos=[0:4, 0:11]) 8 8 -- out/errors.txt -- 9 9 [eval] f: conflicting values false and true: 10 10 ./in.cue:6:4
+1 -1
cue/testdata/basicrewrite/006_basic_type.txtar
··· 3 3 b: number & 1 @test(eq, 1) 4 4 c: 1.0 @test(eq, 1.0) 5 5 c: float 6 - d: int & float @test(err, code=eval, pos=[0:4 0:10]) 6 + d: int & float @test(err, code=eval, pos=[0:4, 0:10]) 7 7 e: "4" & string @test(eq, "4") 8 8 f: true @test(eq, true) 9 9 f: bool
+4 -4
cue/testdata/basicrewrite/010_lists.txtar
··· 2 2 list: [1, 2, 3] @test(eq, [1, 2, 3]) 3 3 index: [1, 2, 3][1] @test(eq, 2) 4 4 unify: [1, 2, 3] & [_, 2, 3] @test(eq, [1, 2, 3]) 5 - e: [] & 4 @test(err, code=eval, pos=[0:4 0:9], contains="conflicting values", args=[list, int]) 5 + e: [] & 4 @test(err, code=eval, pos=[0:4, 0:9], contains="conflicting values", args=[list, int]) 6 6 7 7 // No "contains" as message may vary by implementation. 8 8 e2: [3]["d"] @test(err, code=eval, pos=[0:9]) 9 - e3: [3][-1] @test(err, code=eval, pos=[0:5 0:9], contains="invalid index", args=[-1]) 10 - e4: [1, 2, ...>=4 & <=5] & [1, 2, 4, 8] @test(err, code=eval, pos=[0:21 0:38], contains="invalid value", args=[8, <=5]) 11 - e5: [1, 2, 4, 8] & [1, 2, ...>=4 & <=5] @test(err, code=eval, pos=[0:36 0:15], contains="invalid value", args=[8, <=5]) 9 + e3: [3][-1] @test(err, code=eval, pos=[0:5, 0:9], contains="invalid index", args=[-1]) 10 + e4: [1, 2, ...>=4 & <=5] & [1, 2, 4, 8] @test(err, code=eval, pos=[0:21, 0:38], contains="invalid value", args=[8, <=5]) 11 + e5: [1, 2, 4, 8] & [1, 2, ...>=4 & <=5] @test(err, code=eval, pos=[0:36, 0:15], contains="invalid value", args=[8, <=5]) 12 12 -- out/errors.txt -- 13 13 [eval] e: conflicting values [] and 4 (mismatched types list and int): 14 14 ./in.cue:4:4
+1 -1
cue/testdata/basicrewrite/013_obj_unify.txtar
··· 6 6 o4: {a: 1, b: 2} & {b: 2} 7 7 o4: {a: 1} & {a: 1, b: 2} @test(eq, {a: 1, b: 2}) 8 8 9 - e: 1 @test(err, code=eval, pos=[0:4 1:4], contains="conflicting values") 9 + e: 1 @test(err, code=eval, pos=[0:4, 1:4], contains="conflicting values") 10 10 e: {a: 3} 11 11 -- out/errors.txt -- 12 12 [eval] e: conflicting values 1 and {a:3} (mismatched types int and struct):
+1 -1
cue/testdata/comprehensions/else_on_error.txtar
··· 12 12 13 13 // Error in if condition - else should NOT trigger 14 14 ifConditionError: { 15 - @test(err, code=eval, pos=[1:6 1:14], contains="invalid operands") 15 + @test(err, code=eval, pos=[1:6, 1:14], contains="invalid operands") 16 16 if ("not" + 1) { a: 1 } else { b: 2 } 17 17 } 18 18
+2 -2
cue/testdata/comprehensions/lists.txtar
··· 1 1 -- in.cue -- 2 - a: [{a: 1}, {b: 2 & 3}] @test(err, code=eval, pos=[0:17 0:21], contains="conflicting values") 2 + a: [{a: 1}, {b: 2 & 3}] @test(err, code=eval, pos=[0:17, 0:21], contains="conflicting values") 3 3 4 - b: [for x in a {x}] @test(err, code=eval, pos=[-2:17 -2:21], contains="conflicting values") 4 + b: [for x in a {x}] @test(err, code=eval, pos=[-2:17, -2:21], contains="conflicting values") 5 5 -- out/errors.txt -- 6 6 [eval] a.1.b: conflicting values 3 and 2: 7 7 ./in.cue:1:17
+2 -2
cue/testdata/eval/issue3330.txtar
··· 21 21 #empty: {} 22 22 x: null | { n: 3 } 23 23 x: #empty & { n: 3 } @test(err, code=eval, contains="empty disjunction", 24 - suberr=(pos=[-1:5 0:5 0:14], contains="conflicting values", args=[struct, null]), 25 - suberr=(pos=[-1:13 0:15], contains="not allowed")) 24 + suberr=(pos=[-1:5, 0:5, 0:14], contains="conflicting values", args=[struct, null]), 25 + suberr=(pos=[-1:13, 0:15], contains="not allowed")) 26 26 out: len(x) @test(err, code=eval) 27 27 } 28 28
+1 -1
cue/testdata/eval/resolve_basic.txtar
··· 3 3 b: a + 1 @test(eq, 2) 4 4 d: { 5 5 x: _ 6 - y: b + x @test(err, code=incomplete, constains="non-concrete value", pos=[0:5 -1:5]) 6 + y: b + x @test(err, code=incomplete, constains="non-concrete value", pos=[0:5, -1:5]) 7 7 } 8 8 e: d & { 9 9 x: 5
+1 -1
cue/testdata/eval/selectors.txtar
··· 3 3 b: a + 1 @test(eq, 2) 4 4 d: { 5 5 x: _ 6 - y: b + x @test(err, code=incomplete, pos=[0:5 -1:5], contains="non-concrete value") 6 + y: b + x @test(err, code=incomplete, pos=[0:5, -1:5], contains="non-concrete value") 7 7 } 8 8 e: d & { 9 9 x: 5
+1 -1
cue/testdata/fulleval/005_conditional_field.txtar
··· 21 21 if a > 1 { 22 22 a: 3 23 23 } 24 - @test(err, code=(cycle|incomplete), pos=[2:5 1:5 2:2]) 24 + @test(err, code=(cycle|incomplete), pos=[2:5, 1:5, 2:2]) 25 25 } 26 26 -- out/errors.txt -- 27 27 [incomplete] d: non-concrete value int in operand to >:
+1 -1
cue/testdata/fulleval/020_complex_interaction_of_groundness.txtar
··· 8 8 res: [ {d: "b", s: "ab"} ] 9 9 a: b: c: { 10 10 d: string 11 - s: _|_ @test(err, code=incomplete, pos=[4:23 4:34 5:13]) 11 + s: _|_ @test(err, code=incomplete, pos=[4:23, 4:34, 5:13]) 12 12 } 13 13 }) 14 14 -- out/compile --
+2 -2
cue/testdata/fulleval/021_complex_groundness_2.txtar
··· 11 11 res: { 12 12 d: string 13 13 s: _|_ @test(err, code=incomplete, 14 - pos=[5:25 3:34 5:14 6:23 7:13]) 14 + pos=[5:25, 3:34, 5:14, 6:23, 7:13]) 15 15 } 16 16 }) 17 17 18 18 a: b: c: {d: string, s: "a" + d} @test(eq, { 19 19 d: string 20 20 s: _|_ @test(err, code=incomplete, 21 - pos=[5:25 5:14 6:23 7:13]) 21 + pos=[5:25, 5:14, 6:23, 7:13]) 22 22 }) 23 23 a: b: [C=string]: {d: string, s: "a" + d} 24 24 a: b: c: d: string
+1 -1
cue/testdata/fulleval/051_detectIncompleteYAML.txtar
··· 12 12 use: _vars.something 13 13 } 14 14 baz: yaml.Marshal(_vars.something) @test(err, code=incomplete, pos=[0:11]) 15 - foobar: yaml.Marshal(#foo) @test(err, code=incomplete, pos=[0:11 -6:21 -3:9]) 15 + foobar: yaml.Marshal(#foo) @test(err, code=incomplete, pos=[0:11, -6:21, -3:9]) 16 16 } 17 17 } 18 18 Val: #Spec & {
+3 -3
doc/specs/inline-test-attributes/spec.md
··· 227 227 - `at=<path>` — navigate to the given CUE path (relative to the annotated field) before checking the error. Useful when the erroneous sub-field cannot be directly annotated (e.g. it is inside a comprehension or a pattern constraint). `pos=` is not compatible with `any`; `at=` and `any` may not be combined. 228 228 - `args=[v1, v2, ...]` — the values returned by the error's `Msg()` method must include all listed strings (matched via `fmt.Sprint`, order-independent, subset check). See `Requirement: err directive — args= sub-option` for details. 229 229 - `suberr=(...)` — matches one sub-error of a multi-error value (e.g. a failed disjunction). Multiple `suberr=` entries match sub-errors order-independently. The body accepts the same sub-options as `@test(err, ...)`. See `Requirement: err directive — suberr=(...)` for details. 230 - - `pos=[spec ...]` — asserts the exact set of source positions reported by the error (as returned by `cuelang.org/go/cue/errors.Positions`). Matching is **order-independent**: the order of specs need not match the order of actual positions. Commas between specs are optional. Each spec takes one of two forms: 230 + - `pos=[spec, ...]` — asserts the set of source positions reported by the error (as returned by `cuelang.org/go/cue/errors.Positions`). Matching is **order-independent**: the order of specs need not match the order of actual positions. **Commas between specs are required.** Each spec takes one of two forms: 231 231 - `deltaLine:col` — position in the **same file** as the `@test` attribute, expressed as a signed line offset from an *anchor line* and a 1-indexed column. The anchor depends on where the `@test` attribute appears: 232 232 - **Field attribute** (`field: value @test(...)`): anchor is the field's line in the stripped output (`deltaLine=0` = same line as the field). 233 233 - **Struct-level decl attribute** (inside `{ @test(...) ... }`): anchor is the line of the opening `{` of the enclosing struct (`deltaLine=1` = first line inside the struct body). This keeps the assertion stable when the `@test` line itself is stripped, and gives the natural reading "N lines into the struct". ··· 261 261 262 262 #### Scenario: pos= matches error positions on field attribute 263 263 - **WHEN** `@test(err, pos=[0:5, 0:14])` is a **field attribute** and the error reports exactly two positions on the same line as the field (column 5 and column 14) 264 - - **THEN** the test passes (commas between specs are optional; order of specs need not match order of actual positions) 264 + - **THEN** the test passes (order of specs need not match order of actual positions) 265 265 266 266 #### Scenario: pos= matches error positions on struct decl attribute 267 267 - **WHEN** `d: { @test(err, pos=[1:5, 2:2]) ... }` is declared and the error reports positions at the 1st and 2nd lines inside `d`'s `{` at the given columns ··· 273 273 274 274 #### Scenario: pos= empty placeholder fills on CUE_UPDATE 275 275 - **WHEN** `@test(err, pos=[])` is declared and `CUE_UPDATE=1` is set 276 - - **THEN** the runner fills in the actual positions, writing e.g. `pos=[0:5 0:14]` to the source file 276 + - **THEN** the runner fills in the actual positions, writing e.g. `pos=[0:5, 0:14]` to the source file 277 277 278 278 #### Scenario: pos= cross-file absolute position 279 279 - **WHEN** `@test(err, pos=[fixture.cue:3:5])` is declared and the error position is at absolute line 3, column 5 in `fixture.cue`
+2 -2
internal/cuetxtar/astcmp.go
··· 591 591 for _, p := range positions { 592 592 got = append(got, fmt.Sprintf("%d:%d", p.Line(), p.Column())) 593 593 } 594 - return pathErr(path, "@test(err, pos=...): got %d position(s) %v, want %d", 595 - len(positions), got, len(ea.pos)) 594 + msg := formatPosCountMismatch("@test(err, pos=...)", len(positions), len(ea.pos)) 595 + return pathErr(path, "%s %v", msg, got) 596 596 } 597 597 // Order-independent matching: each expected position must match 598 598 // exactly one actual position.
+15 -6
internal/cuetxtar/inline_err.go
··· 188 188 } 189 189 190 190 // parsePosSpecs parses a pos= value into a slice of posSpec. 191 - // The value must be enclosed in square brackets; elements are whitespace-separated. 191 + // The value must be enclosed in square brackets; elements are comma-separated. 192 192 // Two element forms are supported: 193 193 // 194 194 // - deltaLine:col — relative position on the same file (one colon). ··· 203 203 } 204 204 s = inner 205 205 var specs []posSpec 206 - for _, p := range strings.Fields(s) { 207 - // TODO: make these required. 208 - p = strings.TrimRight(p, ",") // commas are optional separators 206 + for _, p := range strings.Split(s, ",") { 207 + p = strings.TrimSpace(p) 209 208 if p == "" { 210 209 continue 211 210 } ··· 552 551 for i, p := range u.positions { 553 552 parts[i] = r.formatPosSpec(p, pa) 554 553 } 555 - newPosStr := strings.Join(parts, " ") 554 + newPosStr := strings.Join(parts, ", ") 556 555 newAttrText = replaceSuberrPos(newAttrText, u.expIdx, newPosStr) 557 556 } 558 557 r.pendingPosWrites = append(r.pendingPosWrites, posWrite{ ··· 677 676 return got.Line() == baseLine+exp.deltaLine && got.Column() == exp.col 678 677 } 679 678 679 + // formatPosCountMismatch returns a consistent mismatch message for @test(err, 680 + // pos=...) assertions. When got has extra positions, it explains that this can 681 + // be acceptable after validating relevance. 682 + func formatPosCountMismatch(directive string, got, want int) string { 683 + if got > want { 684 + return fmt.Sprintf("%s: got %d position(s), want %d; extra positions are often acceptable, and after confirming they are relevant to this error you can add them to pos=[...]", directive, got, want) 685 + } 686 + return fmt.Sprintf("%s: got %d position(s), want %d", directive, got, want) 687 + } 688 + 680 689 // reportPosMismatch reports a position mismatch between actual positions and 681 690 // expected specs. directive is included verbatim in each error message. 682 691 // If counts differ, only the count error is reported. Otherwise each ··· 684 693 func (r *inlineRunner) reportPosMismatch(t testing.TB, path cue.Path, directive string, positions []token.Pos, specs []posSpec, baseLine int) { 685 694 t.Helper() 686 695 if len(positions) != len(specs) { 687 - t.Errorf("path %s: %s: got %d position(s), want %d", path, directive, len(positions), len(specs)) 696 + t.Errorf("path %s: %s", path, formatPosCountMismatch(directive, len(positions), len(specs))) 688 697 for _, p := range positions { 689 698 t.Logf(" actual: %d:%d", p.Line(), p.Column()) 690 699 }
+8 -12
internal/cuetxtar/inline_test.go
··· 134 134 want: []posSpec{{deltaLine: 0, col: 5}}, 135 135 }, 136 136 { 137 - name: "multiple relative with whitespace", 138 - input: "[0:5 1:13 -2:3]", 137 + name: "multiple relative comma-separated", 138 + input: "[0:5, 1:13, -2:3]", 139 139 want: []posSpec{ 140 140 {deltaLine: 0, col: 5}, 141 141 {deltaLine: 1, col: 13}, ··· 143 143 }, 144 144 }, 145 145 { 146 + name: "multiple relative whitespace-only is rejected", 147 + input: "[0:5 1:13 -2:3]", 148 + wantErr: true, 149 + }, 150 + { 146 151 name: "absolute form", 147 152 input: "[fixture.cue:3:5]", 148 153 want: []posSpec{{fileName: "fixture.cue", absLine: 3, col: 5}}, 149 154 }, 150 155 { 151 156 name: "mixed relative and absolute", 152 - input: "[0:5 fixture.cue:1:13]", 157 + input: "[0:5, fixture.cue:1:13]", 153 158 want: []posSpec{ 154 159 {deltaLine: 0, col: 5}, 155 160 {fileName: "fixture.cue", absLine: 1, col: 13}, ··· 174 179 name: "non-integer col", 175 180 input: "[0:x]", 176 181 wantErr: true, 177 - }, 178 - { 179 - name: "comma-separated (commas ignored)", 180 - input: "[0:5, 1:13, -2:3]", 181 - want: []posSpec{ 182 - {deltaLine: 0, col: 5}, 183 - {deltaLine: 1, col: 13}, 184 - {deltaLine: -2, col: 3}, 185 - }, 186 182 }, 187 183 { 188 184 name: "trailing comma only",