Repository: ysugimoto/falco
Pull request #94 (2022-08-17 11:05:23)

Allow function expressions in error statements
Hey there, I'm currently working on getting the Financial Times' Fastly VCL working with Falco, and have been fixing any false positives I've found. So I might make a few PRs to upstream the fixes over the next few days if that's okay! Thanks for this great resource.

The first change is allowing function calls in `error` statements. We're currently using a `table` to track all the custom error codes we return so that we can minimise duplication and bugs when handling them in `error` subroutines. For example, we might declare an error like
```vcl
error table.lookup_integer(synthetic_errors, "gtg", 601) "Report health on cdn";
```
to (verbosely) enforce the same error code across subroutines (though the non-optional fallback argument doesn't feel great...)

Currently, Falco only expects identifiers or numbers as the first argument to `error`, but functions are also accepted by Fastly. I've added additional parsing logic to allow for this. The linter will also check to make sure that the function returns an integer.

ysugimoto: @davinci26 I've already approved and also looks fine. I'll leave it to you 👍 

Patch:
diff --git a/linter/linter.go b/linter/linter.go
index 7793abc..9a16485 100644
--- a/linter/linter.go
+++ b/linter/linter.go
@@ -1070,6 +1070,11 @@ func (l *Linter) lintErrorStatement(stmt *ast.ErrorStatement, ctx *context.Conte
 		if code != types.IntegerType {
 			l.Error(InvalidType(t.GetMeta(), t.Value, types.IntegerType, code))
 		}
+	case *ast.FunctionCallExpression:
+		code := l.lint(t, ctx)
+		if code != types.IntegerType {
+			l.Error(InvalidType(t.GetMeta(), "error code", types.IntegerType, code))
+		}
 	case *ast.Integer:
 		if t.Value > 699 {
 			l.Error(ErrorCodeRange(t.GetMeta(), t.Value).Match(ERROR_STATEMENT_CODE))
diff --git a/parser/statement_parser.go b/parser/statement_parser.go
index d1b7c2e..1c17bfb 100644
--- a/parser/statement_parser.go
+++ b/parser/statement_parser.go
@@ -316,7 +316,13 @@ func (p *Parser) parseErrorStatement() (*ast.ErrorStatement, error) {
 		stmt.Code, err = p.parseInteger()
 	case token.IDENT:
 		p.nextToken()
-		stmt.Code = p.parseIdent()
+		if p.peekTokenIs(token.LEFT_PAREN) {
+			i := p.parseIdent()
+			p.nextToken()
+			stmt.Code, err = p.parseFunctionCallExpression(i)
+		} else {
+			stmt.Code = p.parseIdent()
+		}
 	default:
 		err = UnexpectedToken(p.peekToken)
 	}


Test patch:
diff --git a/linter/linter_test.go b/linter/linter_test.go
index 78f6a73..94ae67e 100644
--- a/linter/linter_test.go
+++ b/linter/linter_test.go
@@ -622,6 +622,24 @@ sub foo {
 sub foo {
 	error 602;
 }
+`
+		assertError(t, input)
+	})
+
+	t.Run("pass with function", func(t *testing.T) {
+		input := `
+sub foo {
+	error std.atoi("10");
+}
+`
+		assertNoError(t, input)
+	})
+
+	t.Run("invalid function return type", func(t *testing.T) {
+		input := `
+sub foo {
+	error std.strrev("error");
+}
 `
 		assertError(t, input)
 	})
diff --git a/parser/statement_parser_test.go b/parser/statement_parser_test.go
index d44625b..9dca717 100644
--- a/parser/statement_parser_test.go
+++ b/parser/statement_parser_test.go
@@ -1093,6 +1093,63 @@ sub vcl_recv {
 		}
 		assert(t, vcl, expect)
 	})
+
+	t.Run("with function argument", func(t *testing.T) {
+		input := `// Subroutine
+sub vcl_recv {
+	// Leading comment
+	error table.lookup_integer(errors, "foo", 600) "bar"; // Trailing comment
+}`
+		expect := &ast.VCL{
+			Statements: []ast.Statement{
+				&ast.SubroutineDeclaration{
+					Meta: ast.New(T, 0, comments("// Subroutine")),
+					Name: &ast.Ident{
+						Meta:  ast.New(T, 0),
+						Value: "vcl_recv",
+					},
+					Block: &ast.BlockStatement{
+						Meta: ast.New(T, 1),
+						Statements: []ast.Statement{
+							&ast.ErrorStatement{
+								Meta: ast.New(T, 1, comments("// Leading comment"), comments("// Trailing comment")),
+								Code: &ast.FunctionCallExpression{
+									Meta: ast.New(T, 1),
+									Function: &ast.Ident{
+										Meta:  ast.New(T, 1),
+										Value: "table.lookup_integer",
+									},
+									Arguments: []ast.Expression{
+										&ast.Ident{
+											Meta:  ast.New(T, 1),
+											Value: "errors",
+										},
+										&ast.String{
+											Meta:  ast.New(T, 1),
+											Value: "foo",
+										},
+										&ast.Integer{
+											Meta:  ast.New(T, 1),
+											Value: 600,
+										},
+									},
+								},
+								Argument: &ast.String{
+									Meta:  ast.New(T, 1),
+									Value: "bar",
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		vcl, err := New(lexer.NewFromString(input)).ParseVCL()
+		if err != nil {
+			t.Errorf("%+v", err)
+		}
+		assert(t, vcl, expect)
+	})
 }
 
 func TestLogStatement(t *testing.T) {