diff --git a/internal/cmd/generate.go b/internal/cmd/generate.go index dbc62dcc47..ec6417e105 100644 --- a/internal/cmd/generate.go +++ b/internal/cmd/generate.go @@ -14,6 +14,7 @@ import ( "github.com/kyleconroy/sqlc/internal/config" "github.com/kyleconroy/sqlc/internal/dinosql" "github.com/kyleconroy/sqlc/internal/dinosql/kotlin" + "github.com/kyleconroy/sqlc/internal/multierr" "github.com/kyleconroy/sqlc/internal/mysql" ) @@ -32,7 +33,7 @@ The only supported version is "1". const errMessageNoPackages = `No packages are configured` -func printFileErr(stderr io.Writer, dir string, fileErr dinosql.FileErr) { +func printFileErr(stderr io.Writer, dir string, fileErr *multierr.FileError) { filename := strings.TrimPrefix(fileErr.Filename, dir+"/") fmt.Fprintf(stderr, "%s:%d:%d: %s\n", filename, fileErr.Line, fileErr.Column, fileErr.Err) } @@ -179,8 +180,8 @@ func parse(name, dir string, sql config.SQL, combo config.CombinedSettings, pars q, err := mysql.GeneratePkg(name, sql.Schema, sql.Queries, combo) if err != nil { fmt.Fprintf(stderr, "# package %s\n", name) - if parserErr, ok := err.(*dinosql.ParserErr); ok { - for _, fileErr := range parserErr.Errs { + if parserErr, ok := err.(*multierr.Error); ok { + for _, fileErr := range parserErr.Errs() { printFileErr(stderr, dir, fileErr) } } else { @@ -194,8 +195,8 @@ func parse(name, dir string, sql config.SQL, combo config.CombinedSettings, pars c, err := dinosql.ParseCatalog(sql.Schema) if err != nil { fmt.Fprintf(stderr, "# package %s\n", name) - if parserErr, ok := err.(*dinosql.ParserErr); ok { - for _, fileErr := range parserErr.Errs { + if parserErr, ok := err.(*multierr.Error); ok { + for _, fileErr := range parserErr.Errs() { printFileErr(stderr, dir, fileErr) } } else { @@ -207,8 +208,8 @@ func parse(name, dir string, sql config.SQL, combo config.CombinedSettings, pars q, err := dinosql.ParseQueries(c, sql.Queries, parserOpts) if err != nil { fmt.Fprintf(stderr, "# package %s\n", name) - if parserErr, ok := err.(*dinosql.ParserErr); ok { - for _, fileErr := range parserErr.Errs { + if parserErr, ok := err.(*multierr.Error); ok { + for _, fileErr := range parserErr.Errs() { printFileErr(stderr, dir, fileErr) } } else { diff --git a/internal/dinosql/parser.go b/internal/dinosql/parser.go index 9663aae31f..687e3b5e36 100644 --- a/internal/dinosql/parser.go +++ b/internal/dinosql/parser.go @@ -13,6 +13,7 @@ import ( "github.com/kyleconroy/sqlc/internal/catalog" "github.com/kyleconroy/sqlc/internal/migrations" + "github.com/kyleconroy/sqlc/internal/multierr" core "github.com/kyleconroy/sqlc/internal/pg" "github.com/kyleconroy/sqlc/internal/postgres" "github.com/kyleconroy/sqlc/internal/postgresql/ast" @@ -27,46 +28,13 @@ func keepSpew() { spew.Dump("hello world") } -type FileErr struct { - Filename string - Line int - Column int - Err error -} - -type ParserErr struct { - Errs []FileErr -} - -func (e *ParserErr) Add(filename, source string, loc int, err error) { - line := 1 - column := 1 - if lerr, ok := err.(core.Error); ok { - if lerr.Location != 0 { - loc = lerr.Location - } - } - if source != "" && loc != 0 { - line, column = lineno(source, loc) - } - e.Errs = append(e.Errs, FileErr{filename, line, column, err}) -} - -func NewParserErr() *ParserErr { - return &ParserErr{} -} - -func (e *ParserErr) Error() string { - return fmt.Sprintf("multiple errors: %d errors", len(e.Errs)) -} - func ParseCatalog(schemas []string) (core.Catalog, error) { files, err := sqlpath.Glob(schemas) if err != nil { return core.Catalog{}, err } - merr := NewParserErr() + merr := multierr.New() c := core.NewCatalog() for _, filename := range files { blob, err := ioutil.ReadFile(filename) @@ -96,7 +64,7 @@ func ParseCatalog(schemas []string) (core.Catalog, error) { // catalog so that other queries can not read from it. delete(c.Schemas, "pg_temp") - if len(merr.Errs) > 0 { + if len(merr.Errs()) > 0 { return c, merr } return c, nil @@ -163,7 +131,7 @@ type ParserOpts struct { } func ParseQueries(c core.Catalog, queriesPaths []string, opts ParserOpts) (*Result, error) { - merr := NewParserErr() + merr := multierr.New() var q []*Query set := map[string]struct{}{} @@ -205,7 +173,7 @@ func ParseQueries(c core.Catalog, queriesPaths []string, opts ParserOpts) (*Resu } } } - if len(merr.Errs) > 0 { + if len(merr.Errs()) > 0 { return nil, merr } if len(q) == 0 { @@ -227,36 +195,6 @@ func location(node nodes.Node) int { return 0 } -func lineno(source string, head int) (int, int) { - // Calculate the true line and column number for a query, ignoring spaces - var comment bool - var loc, line, col int - for i, char := range source { - loc += 1 - col += 1 - // TODO: Check bounds - if char == '-' && source[i+1] == '-' { - comment = true - } - if char == '\n' { - comment = false - line += 1 - col = 0 - } - if loc <= head { - continue - } - if unicode.IsSpace(char) { - continue - } - if comment { - continue - } - break - } - return line + 1, col -} - func pluckQuery(source string, n nodes.RawStmt) (string, error) { head := n.StmtLocation tail := n.StmtLocation + n.StmtLen diff --git a/internal/dinosql/parser_test.go b/internal/dinosql/parser_test.go index cc59f349f8..c700c0c475 100644 --- a/internal/dinosql/parser_test.go +++ b/internal/dinosql/parser_test.go @@ -6,6 +6,7 @@ import ( "path" "testing" + "github.com/kyleconroy/sqlc/internal/source" "github.com/kyleconroy/sqlc/internal/sql/sqlpath" "github.com/google/go-cmp/cmp" @@ -81,7 +82,7 @@ func TestLineColumn(t *testing.T) { {tree.Statements[5], 10, 12}, } { raw := test.node.(nodes.RawStmt) - line, column := lineno(lineColumn, raw.StmtLocation) + line, column := source.LineNumber(lineColumn, raw.StmtLocation) if line != test.line { t.Errorf("expected stmt %d to be on line %d, not %d", i, test.line, line) } diff --git a/internal/multierr/error.go b/internal/multierr/error.go new file mode 100644 index 0000000000..aabce10556 --- /dev/null +++ b/internal/multierr/error.go @@ -0,0 +1,49 @@ +package multierr + +import ( + "fmt" + + "github.com/kyleconroy/sqlc/internal/pg" + "github.com/kyleconroy/sqlc/internal/source" +) + +type FileError struct { + Filename string + Line int + Column int + Err error +} + +func (e *FileError) Unwrap() error { + return e.Err +} + +type Error struct { + errs []*FileError +} + +func (e *Error) Add(filename, in string, loc int, err error) { + line := 1 + column := 1 + if lerr, ok := err.(pg.Error); ok { + if lerr.Location != 0 { + loc = lerr.Location + } + } + if in != "" && loc != 0 { + line, column = source.LineNumber(in, loc) + } + e.errs = append(e.errs, &FileError{filename, line, column, err}) +} + +func (e *Error) Errs() []*FileError { + return e.errs +} + +func (e *Error) Error() string { + return fmt.Sprintf("multiple errors: %d errors", len(e.errs)) +} + +func New() *Error { + return &Error{} +} diff --git a/internal/mysql/parse.go b/internal/mysql/parse.go index cdcddb4ab3..5b795234ea 100644 --- a/internal/mysql/parse.go +++ b/internal/mysql/parse.go @@ -12,6 +12,7 @@ import ( "github.com/kyleconroy/sqlc/internal/config" "github.com/kyleconroy/sqlc/internal/dinosql" "github.com/kyleconroy/sqlc/internal/migrations" + "github.com/kyleconroy/sqlc/internal/multierr" "github.com/kyleconroy/sqlc/internal/sql/sqlpath" ) @@ -38,8 +39,7 @@ func parsePath(sqlPath []string, generator PackageGenerator) (*Result, error) { return nil, err } - parseErrors := dinosql.ParserErr{} - + parseErrors := multierr.New() parsedQueries := []*Query{} for _, filename := range files { blob, err := ioutil.ReadFile(filename) @@ -86,8 +86,8 @@ func parsePath(sqlPath []string, generator PackageGenerator) (*Result, error) { } } - if len(parseErrors.Errs) > 0 { - return nil, &parseErrors + if len(parseErrors.Errs()) > 0 { + return nil, parseErrors } return &Result{ diff --git a/internal/source/code.go b/internal/source/code.go new file mode 100644 index 0000000000..65d340a67d --- /dev/null +++ b/internal/source/code.go @@ -0,0 +1,33 @@ +package source + +import "unicode" + +func LineNumber(source string, head int) (int, int) { + // Calculate the true line and column number for a query, ignoring spaces + var comment bool + var loc, line, col int + for i, char := range source { + loc += 1 + col += 1 + // TODO: Check bounds + if char == '-' && source[i+1] == '-' { + comment = true + } + if char == '\n' { + comment = false + line += 1 + col = 0 + } + if loc <= head { + continue + } + if unicode.IsSpace(char) { + continue + } + if comment { + continue + } + break + } + return line + 1, col +}