cue/parser: record position of deprecated feature

Change-Id: I8f1dbac1dd325324977a5e08ca94c64866f1a8d6
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3875
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/parser/interface.go b/cue/parser/interface.go
index ce09af3..302eb38 100644
--- a/cue/parser/interface.go
+++ b/cue/parser/interface.go
@@ -17,6 +17,8 @@
 package parser
 
 import (
+	"fmt"
+
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/ast/astutil"
 	"cuelang.org/go/cue/errors"
@@ -87,6 +89,16 @@
 	return -1000 + 100*minor + patch
 }
 
+// DeprecationError is a sentinel error to indicate that an error is
+// related to an unsupported old CUE syntax.
+type DeprecationError struct {
+	Version int
+}
+
+func (e *DeprecationError) Error() string {
+	return fmt.Sprintf("try running `cue fmt` on the file to upgrade.")
+}
+
 // Latest specifies the latest version of the parser, effectively setting
 // the strictest implementation.
 const Latest = latest
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 296b19e..4744ceb 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -275,7 +275,7 @@
 	// Scan the comment for '\n' chars and adjust endline accordingly.
 	endline = p.file.Line(p.pos)
 	if p.lit[1] == '*' {
-		p.assertV0(0, 10, "block quotes")
+		p.assertV0(p.pos, 0, 10, "block quotes")
 
 		// don't use range here - no need to decode Unicode code points
 		for i := 0; i < len(p.lit); i++ {
@@ -368,9 +368,12 @@
 	}
 }
 
-func (p *parser) assertV0(minor, patch int, name string) {
-	if p.version != 0 && p.version > version0(minor, patch) {
-		p.errf(p.pos, "%s deprecated as of v0.%d.%d", name, minor, patch+1)
+func (p *parser) assertV0(pos token.Pos, minor, patch int, name string) {
+	v := version0(minor, patch)
+	if p.version != 0 && p.version > v {
+		p.errors = errors.Append(p.errors,
+			errors.Wrapf(&DeprecationError{v}, pos,
+				"%s deprecated as of v0.%d.%d", name, minor, patch+1))
 	}
 }
 
@@ -826,7 +829,7 @@
 
 		switch p.tok {
 		case token.IDENT, token.STRING, token.LSS, token.INTERPOLATION, token.LBRACK:
-			p.assertV0(0, 12, "space-separated labels")
+			p.assertV0(p.pos, 0, 12, "space-separated labels")
 			field := &ast.Field{}
 			m.Value = &ast.StructLit{Elts: []ast.Decl{field}}
 			m = field
@@ -909,6 +912,7 @@
 		fallthrough
 
 	case token.FOR, token.IF:
+		p.assertV0(p.pos, 0, 10, "old-style comprehensions")
 		if !allowComprehension {
 			p.errf(p.pos, "comprehension not allowed for this field")
 		}
@@ -1070,7 +1074,7 @@
 			p.next()
 		}
 
-		p.assertV0(0, 12, "template labels")
+		p.assertV0(p.pos, 0, 12, "template labels")
 		label = &ast.TemplateLabel{Langle: pos, Ident: ident, Rangle: gtr}
 		c.closeNode(p, label)
 		ok = true