diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a7d992cb..dd1c2ff67e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,8 @@ Unreleased changes template. {#v0-0-0-added} ### Added -* Nothing added. +* (gazelle): Parser failures will now be logged to the terminal. Additional + details can be logged by setting `GAZELLE_VERBOSE=1`. {#v0-0-0-removed} ### Removed diff --git a/gazelle/python/file_parser.go b/gazelle/python/file_parser.go index a2b22c2b8f..9101621639 100644 --- a/gazelle/python/file_parser.go +++ b/gazelle/python/file_parser.go @@ -17,6 +17,7 @@ package python import ( "context" "fmt" + "log" "os" "path/filepath" "strings" @@ -55,7 +56,10 @@ func NewFileParser() *FileParser { return &FileParser{} } -func ParseCode(code []byte) (*sitter.Node, error) { +// ParseCode instantiates a new tree-sitter Parser and parses the python code, returning +// the tree-sitter RootNode. +// It prints a warning if parsing fails. +func ParseCode(code []byte, path string) (*sitter.Node, error) { parser := sitter.NewParser() parser.SetLanguage(python.GetLanguage()) @@ -64,9 +68,27 @@ func ParseCode(code []byte) (*sitter.Node, error) { return nil, err } - return tree.RootNode(), nil + root := tree.RootNode() + if root.HasError() { + log.Printf("WARNING: failed to parse %q. The resulting BUILD target may be incorrect.", path) + + verbose, envExists := os.LookupEnv("GAZELLE_VERBOSE") + if envExists && verbose == "1" { + for i := 0; i < int(root.ChildCount()); i++ { + child := root.Child(i) + if child.IsError() { + log.Printf("Parse error at %+v:\n%+v", child.StartPoint(), child.Content(code)) + log.Printf("The above was parsed as: %v", child.String()) + } + } + } + } + + return root, nil } +// parseMain returns true if the python file has an `if __name__ == "__main__":` block, +// which is a common idiom for python scripts/binaries. func (p *FileParser) parseMain(ctx context.Context, node *sitter.Node) bool { for i := 0; i < int(node.ChildCount()); i++ { if err := ctx.Err(); err != nil { @@ -94,6 +116,8 @@ func (p *FileParser) parseMain(ctx context.Context, node *sitter.Node) bool { return false } +// parseImportStatement parses a node for an import statement, returning a `module` and a boolean +// representing if the parse was OK or not. func parseImportStatement(node *sitter.Node, code []byte) (module, bool) { switch node.Type() { case sitterNodeTypeDottedName: @@ -112,6 +136,9 @@ func parseImportStatement(node *sitter.Node, code []byte) (module, bool) { return module{}, false } +// parseImportStatements parses a node for import statements, returning true if the node is +// an import statement. It updates FileParser.output.Modules with the `module` that the +// import represents. func (p *FileParser) parseImportStatements(node *sitter.Node) bool { if node.Type() == sitterNodeTypeImportStatement { for j := 1; j < int(node.ChildCount()); j++ { @@ -146,6 +173,8 @@ func (p *FileParser) parseImportStatements(node *sitter.Node) bool { return true } +// parseComments parses a node for comments, returning true if the node is a comment. +// It updates FileParser.output.Comments with the parsed comment. func (p *FileParser) parseComments(node *sitter.Node) bool { if node.Type() == sitterNodeTypeComment { p.output.Comments = append(p.output.Comments, comment(node.Content(p.code))) @@ -180,7 +209,7 @@ func (p *FileParser) parse(ctx context.Context, node *sitter.Node) { } func (p *FileParser) Parse(ctx context.Context) (*ParserOutput, error) { - rootNode, err := ParseCode(p.code) + rootNode, err := ParseCode(p.code, p.relFilepath) if err != nil { return nil, err }