Repository: CloudyKit/jet (go)

Issue:
import calls can trigger unintelligible error messages
If you place a `import` call after some html in your template:
```
<html>
  <head>
     {{import "something/somewhere.jet"}}
```

You get an error like: `unexpected token <import> on operand` which is very tricky to relate to the actual problem.

Also it doesn't seem to be documented that `{{import}}` has to go at the top of the file.

Pull request description:
return useful error on late import/extends statements
fixes #134

Patch:
diff --git a/parse.go b/parse.go
index 4be44aa..a00b761 100644
--- a/parse.go
+++ b/parse.go
@@ -143,7 +143,12 @@ func (t *Template) expectOneOf(expected1, expected2 itemType, context string) it
 
 // unexpected complains about the token and terminates processing.
 func (t *Template) unexpected(token item, context string) {
-	t.errorf("unexpected %s in %s", token, context)
+	switch token.typ {
+	case itemImport, itemExtends:
+		t.errorf("parsing %s: unexpected '%s' statement ('%s' statements must be at the beginning of the template)", context, token.val, token.val)
+	default:
+		t.errorf("parsing %s: unexpected token '%s'", context, token.val)
+	}
 }
 
 // recover is the handler that turns panics into returns from the top level of Parse.
@@ -565,16 +570,15 @@ func (t *Template) unaryExpression(context string) (Expression, item) {
 		expr, endToken := t.comparativeExpression(context)
 		return t.newNotExpr(expr.Position(), t.lex.lineNumber(), expr), endToken
 	case itemMinus, itemAdd:
-		return t.newAdditiveExpr(next.pos, t.lex.lineNumber(), nil, t.operand(), next), t.nextNonSpace()
+		return t.newAdditiveExpr(next.pos, t.lex.lineNumber(), nil, t.operand("additive expression"), next), t.nextNonSpace()
 	default:
 		t.backup()
 	}
-	operand := t.operand()
+	operand := t.operand("expression")
 	return operand, t.nextNonSpace()
 }
 
 func (t *Template) assignmentOrExpression(context string) (operand Expression) {
-
 	t.peekNonSpace()
 	line := t.lex.lineNumber()
 	var right, left []Expression
@@ -734,10 +738,10 @@ func (t *Template) command(baseExpr Expression) *CommandNode {
 // An operand is a space-separated component of a command,
 // a term possibly followed by field accesses.
 // A nil return means the next item is not an operand.
-func (t *Template) operand() Expression {
+func (t *Template) operand(context string) Expression {
 	node := t.term()
 	if node == nil {
-		t.errorf("unexpected token %s on operand", t.next())
+		t.unexpected(t.next(), context)
 	}
 RESET:
 	if t.peek().typ == itemField {


Test patch:
diff --git a/lex_test.go b/lex_test.go
index d41ea8c..f3f4176 100644
--- a/lex_test.go
+++ b/lex_test.go
@@ -32,7 +32,7 @@ func lexerTestCaseCustomLexer(t *testing.T, lexer *lexer, input string, items ..
 	}
 	item := lexer.nextItem()
 	if item.typ != itemEOF {
-		t.Errorf("Unexpected token %s EOF is expected", item)
+		t.Errorf("Unexpected token %s, expected EOF", item)
 	}
 }
 
diff --git a/parse_test.go b/parse_test.go
index fb83ad4..0f780d8 100644
--- a/parse_test.go
+++ b/parse_test.go
@@ -50,6 +50,21 @@ func (t ParserTestCase) ExpectPrint(input, output string) {
 	t.ExpectPrintName("", input, output)
 }
 
+func (t ParserTestCase) ExpectError(name, input, errorMessage string) {
+	set := parseSet
+	if t.set != nil {
+		set = t.set
+	}
+	_, err := set.parse(name, input)
+	if err == nil {
+		t.Errorf("expected %q but got no error", errorMessage)
+		return
+	}
+	if err.Error() != errorMessage {
+		t.Errorf("expected %q but got %q", errorMessage, err.Error())
+	}
+}
+
 func (t ParserTestCase) TestPrintFile(file string) {
 	content, err := ioutil.ReadFile(path.Join("./testData", file))
 	if err != nil {
@@ -70,6 +85,12 @@ func TestParseTemplateAndImport(t *testing.T) {
 	p.TestPrintFile("imports.jet")
 }
 
+func TestUsefulErrorOnLateImportOrExtends(t *testing.T) {
+	p := ParserTestCase{T: t}
+	p.ExpectError("late_import.jet", `<html><head>{{import "./foo.jet"}}</head></html>`, "template: late_import.jet:1: parsing expression: unexpected 'import' statement ('import' statements must be at the beginning of the template)")
+	p.ExpectError("late_extends.jet", `<html><head>{{extends "./foo.jet"}}</head></html>`, "template: late_extends.jet:1: parsing expression: unexpected 'extends' statement ('extends' statements must be at the beginning of the template)")
+}
+
 func TestParseTemplateControl(t *testing.T) {
 	p := ParserTestCase{T: t}
 	p.TestPrintFile("if.jet")