Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions gazelle/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "rules_python", version = "0.18.0")
bazel_dep(name = "rules_go", version = "0.59.0", repo_name = "io_bazel_rules_go")
bazel_dep(name = "gazelle", version = "0.47.0", repo_name = "bazel_gazelle")
bazel_dep(name = "rules_cc", version = "0.0.16")

local_path_override(
module_name = "rules_python",
Expand All @@ -23,7 +22,7 @@ use_repo(
"com_github_bmatcuk_doublestar_v4",
"com_github_emirpasic_gods",
"com_github_ghodss_yaml",
"com_github_smacker_go_tree_sitter",
"com_github_odvcencio_gotreesitter",
"com_github_stretchr_testify",
"in_gopkg_yaml_v2",
"org_golang_x_sync",
Expand Down
48 changes: 42 additions & 6 deletions gazelle/deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ def go_deps():
sum = "h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=",
version = "v4.9.1",
)
go_repository(
name = "com_github_creack_pty",
importpath = "github.com/creack/pty",
sum = "h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=",
version = "v1.1.9",
)
go_repository(
name = "com_github_davecgh_go_spew",
importpath = "github.com/davecgh/go-spew",
Expand Down Expand Up @@ -110,17 +116,41 @@ def go_deps():
sum = "h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=",
version = "v0.6.0",
)
go_repository(
name = "com_github_kr_pretty",
importpath = "github.com/kr/pretty",
sum = "h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=",
version = "v0.1.0",
)
go_repository(
name = "com_github_kr_text",
importpath = "github.com/kr/text",
sum = "h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=",
version = "v0.2.0",
)
go_repository(
name = "com_github_odvcencio_gotreesitter",
importpath = "github.com/odvcencio/gotreesitter",
sum = "h1:xM+yZ6G63pfT7nriiUvmddmeL5+PCZ5YdMOOOul+lmw=",
version = "v0.17.4",
)
go_repository(
name = "com_github_pmezard_go_difflib",
importpath = "github.com/pmezard/go-difflib",
sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=",
version = "v1.0.0",
)
go_repository(
name = "com_github_smacker_go_tree_sitter",
importpath = "github.com/smacker/go-tree-sitter",
sum = "h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4=",
version = "v0.0.0-20240827094217-dd81d9e9be82",
name = "com_github_segmentio_asm",
importpath = "github.com/segmentio/asm",
sum = "h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=",
version = "v1.1.3",
)
go_repository(
name = "com_github_segmentio_encoding",
importpath = "github.com/segmentio/encoding",
sum = "h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc=",
version = "v0.3.4",
)
go_repository(
name = "com_github_stretchr_objx",
Expand All @@ -134,11 +164,17 @@ def go_deps():
sum = "h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=",
version = "v1.9.0",
)
go_repository(
name = "dev_lsp_go_jsonrpc2",
importpath = "go.lsp.dev/jsonrpc2",
sum = "h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI=",
version = "v0.10.0",
)
go_repository(
name = "in_gopkg_check_v1",
importpath = "gopkg.in/check.v1",
sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=",
version = "v0.0.0-20161208181325-20d25e280405",
sum = "h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=",
version = "v1.0.0-20180628173108-788fd7840127",
)
go_repository(
name = "in_gopkg_yaml_v2",
Expand Down
3 changes: 2 additions & 1 deletion gazelle/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/bmatcuk/doublestar/v4 v4.9.1
github.com/emirpasic/gods v1.18.1
github.com/ghodss/yaml v1.0.0
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82
github.com/odvcencio/gotreesitter v0.17.4
github.com/stretchr/testify v1.9.0
golang.org/x/sync v0.11.0
gopkg.in/yaml.v2 v2.4.0
Expand All @@ -20,6 +20,7 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sys v0.30.0 // indirect
Expand Down
12 changes: 9 additions & 3 deletions gazelle/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ github.com/bazelbuild/rules_go v0.55.1 h1:cQYGcunY8myOB+0Ym6PGQRhc/milkRcNv0my3X
github.com/bazelbuild/rules_go v0.55.1/go.mod h1:T90Gpyq4HDFlsrvtQa2CBdHNJ2P4rAu/uUTmQbanzf0=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
Expand All @@ -14,10 +15,14 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/odvcencio/gotreesitter v0.17.4 h1:xM+yZ6G63pfT7nriiUvmddmeL5+PCZ5YdMOOOul+lmw=
github.com/odvcencio/gotreesitter v0.17.4/go.mod h1:MSmkQmznhGkdLcyQxiM813bi014e1Y1cpcDnm50meHs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
Expand All @@ -28,8 +33,9 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools/go/vcs v0.1.0-deprecated h1:cOIJqWBl99H1dH5LWizPa+0ImeeJq3t3cJjaeOWUAL4=
golang.org/x/tools/go/vcs v0.1.0-deprecated/go.mod h1:zUrvATBAvEI9535oC0yWYsLsHIV4Z7g63sNPVMtuBy8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
4 changes: 2 additions & 2 deletions gazelle/python/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ go_library(
"@com_github_emirpasic_gods//lists/singlylinkedlist",
"@com_github_emirpasic_gods//sets/treeset",
"@com_github_emirpasic_gods//utils",
"@com_github_smacker_go_tree_sitter//:go-tree-sitter",
"@com_github_smacker_go_tree_sitter//python",
"@com_github_odvcencio_gotreesitter//:gotreesitter",
"@com_github_odvcencio_gotreesitter//grammars",
"@org_golang_x_sync//errgroup",
],
)
Expand Down
58 changes: 29 additions & 29 deletions gazelle/python/file_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (
"path/filepath"
"strings"

sitter "github.com/smacker/go-tree-sitter"
"github.com/smacker/go-tree-sitter/python"
sitter "github.com/odvcencio/gotreesitter"
"github.com/odvcencio/gotreesitter/grammars"
)

const (
Expand All @@ -39,6 +39,8 @@ const (
sitterNodeTypeImportFromStatement = "import_from_statement"
)

var pythonLanguage = grammars.PythonLanguage()

type ParserOutput struct {
FileName string
Modules []Module
Expand All @@ -47,10 +49,10 @@ type ParserOutput struct {
}

type FileParser struct {
code []byte
relFilepath string
output ParserOutput
inTypeCheckingBlock bool
code []byte
relFilepath string
output ParserOutput
inTypeCheckingBlock bool
}

func NewFileParser() *FileParser {
Expand All @@ -61,10 +63,8 @@ func NewFileParser() *FileParser {
// 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())

tree, err := parser.ParseCtx(context.Background(), nil, code)
parser := sitter.NewParser(pythonLanguage)
tree, err := parser.Parse(code)
if err != nil {
return nil, err
}
Expand All @@ -89,10 +89,10 @@ func ParseCode(code []byte, path string) (*sitter.Node, error) {
// Example logs:
// gazelle: Parse error at {Row:1 Column:0}:
// def search_one_more_level[T]():
log.Printf("Parse error at %+v:\n%+v", child.StartPoint(), child.Content(code))
log.Printf("Parse error at %+v:\n%+v", child.StartPoint(), child.Text(code))
// Log the internal tree-sitter representation of what was parsed. Eg:
// gazelle: The above was parsed as: (ERROR (identifier) (call function: (list (identifier)) arguments: (argument_list)))
log.Printf("The above was parsed as: %v", child.String())
log.Printf("The above was parsed as: %v", child.SExpr(pythonLanguage))
}
}

Expand All @@ -107,16 +107,16 @@ func (p *FileParser) parseMain(ctx context.Context, node *sitter.Node) bool {
return false
}
child := node.Child(i)
if child.Type() == sitterNodeTypeIfStatement &&
child.Child(1).Type() == sitterNodeTypeComparisonOperator && child.Child(1).Child(1).Type() == "==" {
if child.Type(pythonLanguage) == sitterNodeTypeIfStatement &&
child.Child(1).Type(pythonLanguage) == sitterNodeTypeComparisonOperator && child.Child(1).Child(1).Type(pythonLanguage) == "==" {
Comment on lines +122 to +123

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The parser can return a tree with errors if the Python source is malformed. Accessing child.Child(1) and its children without checking ChildCount() can lead to a nil pointer dereference and panic. Since ParseCode explicitly allows returning a root node with errors, we should be defensive here.

Suggested change
if child.Type(pythonLanguage) == sitterNodeTypeIfStatement &&
child.Child(1).Type(pythonLanguage) == sitterNodeTypeComparisonOperator && child.Child(1).Child(1).Type(pythonLanguage) == "==" {
if child.Type(pythonLanguage) == sitterNodeTypeIfStatement && child.ChildCount() >= 2 &&
child.Child(1).Type(pythonLanguage) == sitterNodeTypeComparisonOperator && child.Child(1).ChildCount() >= 3 && child.Child(1).Child(1).Type(pythonLanguage) == "==" {

statement := child.Child(1)
a, b := statement.Child(0), statement.Child(2)
// convert "'__main__' == __name__" to "__name__ == '__main__'"
if b.Type() == sitterNodeTypeIdentifier {
if b.Type(pythonLanguage) == sitterNodeTypeIdentifier {
a, b = b, a
}
if a.Type() == sitterNodeTypeIdentifier && a.Content(p.code) == "__name__" &&
b.Type() == sitterNodeTypeString && string(p.code[b.StartByte()+1:b.EndByte()-1]) == "__main__" {
if a.Type(pythonLanguage) == sitterNodeTypeIdentifier && a.Text(p.code) == "__name__" &&
b.Type(pythonLanguage) == sitterNodeTypeString && string(p.code[b.StartByte()+1:b.EndByte()-1]) == "__main__" {
Comment on lines +130 to +131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When stripping quotes from a string node, we should ensure the node is at least 2 bytes long (for the quotes) to avoid a slice out-of-bounds panic on malformed or empty string nodes.

Suggested change
if a.Type(pythonLanguage) == sitterNodeTypeIdentifier && a.Text(p.code) == "__name__" &&
b.Type(pythonLanguage) == sitterNodeTypeString && string(p.code[b.StartByte()+1:b.EndByte()-1]) == "__main__" {
if a.Type(pythonLanguage) == sitterNodeTypeIdentifier && a.Text(p.code) == "__name__" &&
b.Type(pythonLanguage) == sitterNodeTypeString && b.EndByte()-b.StartByte() >= 2 && string(p.code[b.StartByte()+1:b.EndByte()-1]) == "__main__" {

return true
}
}
Expand All @@ -127,10 +127,10 @@ func (p *FileParser) parseMain(ctx context.Context, node *sitter.Node) bool {
// 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() {
switch node.Type(pythonLanguage) {
case sitterNodeTypeDottedName:
return Module{
Name: node.Content(code),
Name: node.Text(code),
LineNumber: node.StartPoint().Row + 1,
}, true
case sitterNodeTypeAliasedImport:
Expand Down Expand Up @@ -158,7 +158,7 @@ func cleanImportString(s string) string {
// 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 {
if node.Type(pythonLanguage) == sitterNodeTypeImportStatement {
for j := 1; j < int(node.ChildCount()); j++ {
m, ok := parseImportStatement(node.Child(j), p.code)
if !ok {
Expand All @@ -173,8 +173,8 @@ func (p *FileParser) parseImportStatements(node *sitter.Node) bool {
}
p.output.Modules = append(p.output.Modules, m)
}
} else if node.Type() == sitterNodeTypeImportFromStatement {
from := node.Child(1).Content(p.code)
} else if node.Type(pythonLanguage) == sitterNodeTypeImportFromStatement {
from := node.Child(1).Text(p.code)
Comment on lines +188 to +189

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For import_from_statement, we should verify that the node has enough children before accessing node.Child(1) to avoid a nil pointer dereference on malformed input.

Suggested change
} else if node.Type(pythonLanguage) == sitterNodeTypeImportFromStatement {
from := node.Child(1).Text(p.code)
} else if node.Type(pythonLanguage) == sitterNodeTypeImportFromStatement && node.ChildCount() >= 2 {
from := node.Child(1).Text(p.code)

from = cleanImportString(from)
// If the import is from the current package, we don't need to add it to the modules i.e. from . import Class1.
// If the import is from a different relative package i.e. from .package1 import foo, we need to add it to the modules.
Expand Down Expand Up @@ -202,8 +202,8 @@ func (p *FileParser) parseImportStatements(node *sitter.Node) bool {
// 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)))
if node.Type(pythonLanguage) == sitterNodeTypeComment {
p.output.Comments = append(p.output.Comments, Comment(node.Text(p.code)))
return true
}
return false
Expand All @@ -217,23 +217,23 @@ func (p *FileParser) SetCodeAndFile(code []byte, relPackagePath, filename string

// isTypeCheckingBlock returns true if the given node is an `if TYPE_CHECKING:` block.
func (p *FileParser) isTypeCheckingBlock(node *sitter.Node) bool {
if node.Type() != sitterNodeTypeIfStatement || node.ChildCount() < 2 {
if node.Type(pythonLanguage) != sitterNodeTypeIfStatement || node.ChildCount() < 2 {
return false
}

condition := node.Child(1)

// Handle `if TYPE_CHECKING:`
if condition.Type() == sitterNodeTypeIdentifier && condition.Content(p.code) == "TYPE_CHECKING" {
if condition.Type(pythonLanguage) == sitterNodeTypeIdentifier && condition.Text(p.code) == "TYPE_CHECKING" {
return true
}

// Handle `if typing.TYPE_CHECKING:`
if condition.Type() == "attribute" && condition.ChildCount() >= 3 {
if condition.Type(pythonLanguage) == "attribute" && condition.ChildCount() >= 3 {
object := condition.Child(0)
attr := condition.Child(2)
if object.Type() == sitterNodeTypeIdentifier && object.Content(p.code) == "typing" &&
attr.Type() == sitterNodeTypeIdentifier && attr.Content(p.code) == "TYPE_CHECKING" {
if object.Type(pythonLanguage) == sitterNodeTypeIdentifier && object.Text(p.code) == "typing" &&
attr.Type(pythonLanguage) == sitterNodeTypeIdentifier && attr.Text(p.code) == "TYPE_CHECKING" {
return true
}
}
Expand Down