diff --git a/error_templates/java/already_defined_error.go b/error_templates/java/already_defined_error.go index 66a5152..87fddae 100644 --- a/error_templates/java/already_defined_error.go +++ b/error_templates/java/already_defined_error.go @@ -24,30 +24,21 @@ var AlreadyDefinedError = lib.ErrorTemplate{ pos := m.ErrorNode.StartPos // get the nearest class declaration first based on error location - lib.QueryNode(rootNode, strings.NewReader("(class_declaration) @class"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - pointA := c.Node.StartPoint() - pointB := c.Node.EndPoint() - if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - aCtx.NearestClass = node - return false - } + for q := rootNode.Query("(class_declaration) @class"); q.Next(); { + classNode := q.CurrentNode() + pointA := classNode.StartPoint() + pointB := classNode.EndPoint() + if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 { + aCtx.NearestClass = classNode + break } - return true - }) + } // get the nearest method declaration based on symbol signature - lib.QueryNode(aCtx.NearestClass, strings.NewReader(rawQuery), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - aCtx.NearestMethod = node - return false - } - return true - }) + for q := aCtx.NearestClass.Query(rawQuery); q.Next(); { + aCtx.NearestMethod = q.CurrentNode() + break + } m.Context = aCtx }, diff --git a/error_templates/java/arithmetic_exception.go b/error_templates/java/arithmetic_exception.go index d9cfc78..fb7358e 100644 --- a/error_templates/java/arithmetic_exception.go +++ b/error_templates/java/arithmetic_exception.go @@ -1,8 +1,6 @@ package java import ( - "strings" - lib "github.com/nedpals/errgoengine" ) @@ -38,15 +36,10 @@ var ArithmeticException = lib.ErrorTemplate{ } if len(query) != 0 { - lib.QueryNode(cd.MainError.Nearest, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(cd.MainError.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(cd.MainError.Nearest.Doc, c.Node) - err.Nearest = node - return false - } - return true - }) + for q := err.Nearest.Query(query); q.Next(); { + err.Nearest = q.CurrentNode() + break + } } err.Context = ctx diff --git a/error_templates/java/array_index_out_of_bounds_exception.go b/error_templates/java/array_index_out_of_bounds_exception.go index df44249..5e870ea 100644 --- a/error_templates/java/array_index_out_of_bounds_exception.go +++ b/error_templates/java/array_index_out_of_bounds_exception.go @@ -3,7 +3,6 @@ package java import ( "fmt" "strconv" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -12,15 +11,11 @@ var ArrayIndexOutOfBoundsException = lib.ErrorTemplate{ Name: "ArrayIndexOutOfBoundsException", Pattern: runtimeErrorPattern("java.lang.ArrayIndexOutOfBoundsException", `Index (?P\d+) out of bounds for length (?P\d+)`), OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { - lib.QueryNode(m.Nearest, strings.NewReader("(array_access index: (_) @index)"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - return false - } - return true - }) + for q := m.Nearest.Query(`(array_access index: (_) @index)`); q.Next(); { + node := q.CurrentNode() + m.Nearest = node + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add("This error occurs because the code is trying to access index %s that is beyond the bounds of the array which only has %s items.", cd.Variables["index"], cd.Variables["length"]) diff --git a/error_templates/java/array_required_type_error.go b/error_templates/java/array_required_type_error.go index 43aca6c..a9b5e3d 100644 --- a/error_templates/java/array_required_type_error.go +++ b/error_templates/java/array_required_type_error.go @@ -2,7 +2,6 @@ package java import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -12,16 +11,10 @@ var ArrayRequiredTypeError = lib.ErrorTemplate{ Pattern: comptimeErrorPattern(`array required, but (?P\S+) found`), StackTracePattern: comptimeStackTracePattern, OnAnalyzeErrorFn: func(cd *lib.ContextData, err *lib.MainError) { - query := strings.NewReader("(array_access array: (identifier) index: ((_) @index (#eq? @index \"0\")))") - lib.QueryNode(cd.MainError.Nearest, query, func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(cd.MainError.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(cd.MainError.Nearest.Doc, c.Node) - err.Nearest = node - return false - } - return true - }) + for q := cd.MainError.Nearest.Query("(array_access array: (identifier) index: ((_) @index (#eq? @index \"0\")))"); q.Next(); { + err.Nearest = q.CurrentNode() + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { parent := cd.MainError.Nearest.Parent() diff --git a/error_templates/java/bracket_mismatch_error.go b/error_templates/java/bracket_mismatch_error.go index 4f23ae0..6a73b20 100644 --- a/error_templates/java/bracket_mismatch_error.go +++ b/error_templates/java/bracket_mismatch_error.go @@ -3,7 +3,6 @@ package java import ( "fmt" "strconv" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -12,15 +11,11 @@ var BracketMismatchError = lib.ErrorTemplate{ Name: "ArrayIndexOutOfBoundsException", Pattern: comptimeErrorPattern(`'(?P\S+)' expected`), OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { - lib.QueryNode(m.Nearest, strings.NewReader("(array_access index: (_) @index)"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - return false - } - return true - }) + for q := m.Nearest.Query(`(array_access index: (_) @index)`); q.Next(); { + node := q.CurrentNode() + m.Nearest = node + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add("This error occurs because the code is trying to access index %s that is beyond the bounds of the array which only has %s items.", cd.Variables["index"], cd.Variables["length"]) diff --git a/error_templates/java/cannot_be_applied_error.go b/error_templates/java/cannot_be_applied_error.go index cf2c561..e7fa53f 100644 --- a/error_templates/java/cannot_be_applied_error.go +++ b/error_templates/java/cannot_be_applied_error.go @@ -85,20 +85,15 @@ var CannotBeAppliedError = lib.ErrorTemplate{ argumentNodeTypesToLook += nTypesStr } - rawQuery := fmt.Sprintf(`((method_invocation name: (identifier) @name arguments: (argument_list %s)) @call (#eq? @name "%s"))`, argumentNodeTypesToLook, cd.Variables["method"]) - - lib.QueryNode(m.Nearest, strings.NewReader(rawQuery), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Document, c.Node) - fmt.Println(node.Text()) - cCtx.callExprNode = node - argNode := node.ChildByFieldName("arguments").NamedChild(cCtx.invalidIdx) - m.Nearest = argNode - return false - } - return true - }) + for q := cd.MainError.Nearest.Query( + `((method_invocation name: (identifier) @name arguments: (argument_list %s)) @call (#eq? @name "%s"))`, + argumentNodeTypesToLook, cd.Variables["method"], + ); q.Next(); { + node := q.CurrentNode() + cCtx.callExprNode = node + m.Nearest = node.ChildByFieldName("arguments").NamedChild(cCtx.invalidIdx) + break + } m.Context = cCtx }, diff --git a/error_templates/java/illegal_expression_start_error.go b/error_templates/java/illegal_expression_start_error.go index 1a8d332..fff6671 100644 --- a/error_templates/java/illegal_expression_start_error.go +++ b/error_templates/java/illegal_expression_start_error.go @@ -1,8 +1,6 @@ package java import ( - "strings" - lib "github.com/nedpals/errgoengine" ) @@ -11,16 +9,12 @@ var IllegalExpressionStartError = lib.ErrorTemplate{ Pattern: comptimeErrorPattern(`illegal start of expression`), StackTracePattern: comptimeStackTracePattern, OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { - lib.QueryNode(m.Nearest, strings.NewReader("(ERROR) @error"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - // aCtx.NearestClass = node - return false - } - return true - }) + for q := m.Nearest.Query("(ERROR) @error"); q.Next(); { + node := q.CurrentNode() + m.Nearest = node + // aCtx.NearestClass = node + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add("This error occurs when the compiler encounters an expression that is not valid.") diff --git a/error_templates/java/invalid_method_declaration_error.go b/error_templates/java/invalid_method_declaration_error.go index 4f11702..07a56fd 100644 --- a/error_templates/java/invalid_method_declaration_error.go +++ b/error_templates/java/invalid_method_declaration_error.go @@ -2,7 +2,6 @@ package java import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -20,20 +19,16 @@ var InvalidMethodDeclarationError = lib.ErrorTemplate{ iCtx := invalidMethodDeclarationErrorCtx{} pos := m.ErrorNode.StartPos - lib.QueryNode(m.Document.RootNode(), strings.NewReader("(constructor_declaration) @method"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - pointA := c.Node.StartPoint() - pointB := c.Node.EndPoint() - if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - iCtx.declNode = node - m.Nearest = node.ChildByFieldName("name") - return false - } + for q := m.Document.RootNode().Query("(constructor_declaration) @method"); q.Next(); { + node := q.CurrentNode() + pointA := node.StartPoint() + pointB := node.EndPoint() + if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 { + iCtx.declNode = node + m.Nearest = node.ChildByFieldName("name") + break } - return true - }) + } iCtx.returnTypeToAdd = lib.UnwrapReturnType(cd.FindSymbol(m.Nearest.Text(), m.Nearest.StartPosition().Index)) m.Context = iCtx diff --git a/error_templates/java/missing_return_error.go b/error_templates/java/missing_return_error.go index 826870c..2b794ee 100644 --- a/error_templates/java/missing_return_error.go +++ b/error_templates/java/missing_return_error.go @@ -3,7 +3,6 @@ package java import ( "context" "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -21,19 +20,17 @@ var MissingReturnError = lib.ErrorTemplate{ mCtx := missingReturnErrorCtx{} rootNode := m.Document.RootNode() pos := m.ErrorNode.StartPos - lib.QueryNode(rootNode, strings.NewReader("(method_declaration) @method"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - pointA := c.Node.StartPoint() - pointB := c.Node.EndPoint() - if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - mCtx.NearestMethod = node - return false - } + + for q := rootNode.Query("(method_declaration) @method"); q.Next(); { + node := q.CurrentNode() + pointA := node.StartPoint() + pointB := node.EndPoint() + if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 { + mCtx.NearestMethod = node + break } - return true - }) + } + m.Context = mCtx }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { diff --git a/error_templates/java/negative_array_size_exception.go b/error_templates/java/negative_array_size_exception.go index 40a0926..ed18c40 100644 --- a/error_templates/java/negative_array_size_exception.go +++ b/error_templates/java/negative_array_size_exception.go @@ -1,8 +1,6 @@ package java import ( - "strings" - lib "github.com/nedpals/errgoengine" ) @@ -16,16 +14,13 @@ var NegativeArraySizeException = lib.ErrorTemplate{ OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { nCtx := negativeArraySizeExceptionCtx{} query := "(array_creation_expression dimensions: (dimensions_expr (unary_expression operand: (decimal_integer_literal)))) @array" - lib.QueryNode(m.Nearest, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - nCtx.ArrayExprNode = node - m.Nearest = node.ChildByFieldName("dimensions").NamedChild(0) - return false - } - return true - }) + for q := m.Nearest.Query(query); q.Next(); { + node := q.CurrentNode() + nCtx.ArrayExprNode = node + m.Nearest = node.ChildByFieldName("dimensions").NamedChild(0) + break + } + m.Context = nCtx }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { diff --git a/error_templates/java/non_static_method_access_error.go b/error_templates/java/non_static_method_access_error.go index bd0f992..aae32a4 100644 --- a/error_templates/java/non_static_method_access_error.go +++ b/error_templates/java/non_static_method_access_error.go @@ -2,7 +2,6 @@ package java import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -29,18 +28,14 @@ var NonStaticMethodAccessError = lib.ErrorTemplate{ } } - m.Context = nCtx + for q := m.Nearest.Query(`(method_invocation name: (identifier) @method arguments: (argument_list))`); q.Next(); { + node := q.CurrentNode() + m.Nearest = node + nCtx.method = node.Text() + break + } - lib.QueryNode(m.Nearest, strings.NewReader("(method_invocation name: (identifier) @method arguments: (argument_list))"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - nCtx.method = node.Text() - return false - } - return true - }) + m.Context = nCtx }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add("This error occurs when trying to access a non-static method from a static context. In Java, a non-static method belongs to an instance of the class and needs an object to be called upon.") diff --git a/error_templates/java/null_pointer_exception.go b/error_templates/java/null_pointer_exception.go index 3d17ac6..6d40f88 100644 --- a/error_templates/java/null_pointer_exception.go +++ b/error_templates/java/null_pointer_exception.go @@ -107,6 +107,12 @@ var NullPointerException = lib.ErrorTemplate{ gen.Add("Your program try to access or manipulate an object reference that is currently pointing to `null`, meaning it doesn't refer to any actual object in memory. This typically happens when you forget to initialize an object before using it, or when you try to access an object that hasn't been properly assigned a value. ") }, OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) { + gen.Add("Wrap with an if statement", func(s *lib.BugFixSuggestion) { + s.AddDescription("Check for the variable that is being used as `null`.") + }) + gen.Add("Initialize the variable", func(s *lib.BugFixSuggestion) { + s.AddDescription("An alternative fix is to initialize the `test` variable with a non-null value before calling the method.") + }) }, } diff --git a/error_templates/java/operator_cannot_be_applied_error.go b/error_templates/java/operator_cannot_be_applied_error.go index 25d0abe..eb795fd 100644 --- a/error_templates/java/operator_cannot_be_applied_error.go +++ b/error_templates/java/operator_cannot_be_applied_error.go @@ -2,7 +2,6 @@ package java import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -18,17 +17,13 @@ var OperatorCannotBeAppliedError = lib.ErrorTemplate{ OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { oCtx := opCannotBeAppliedCtx{} operator := cd.Variables["operator"] - query := fmt.Sprintf(`((binary_expression) @binary_expr (#eq @binary_expr "%s"))`, operator) - lib.QueryNode(m.Nearest, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - oCtx.Parent = node - m.Nearest = node.Child(1) - return false - } - return true - }) + for q := m.Nearest.Query(`((binary_expression) @binary_expr (#eq @binary_expr "%s"))`, operator); q.Next(); { + node := q.CurrentNode() + oCtx.Parent = node + m.Nearest = node.Child(1) + break + } + m.Context = oCtx }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { @@ -45,7 +40,6 @@ var OperatorCannotBeAppliedError = lib.ErrorTemplate{ right := ctx.Parent.ChildByFieldName("right") gen.Add(fmt.Sprintf("Use %s's compareTo method", cd.Variables["firstType"]), func(s *lib.BugFixSuggestion) { - s.AddStep( "Since you are comparing a %s and an %s, you need to use the `compareTo` method to compare their values.", cd.Variables["firstType"], diff --git a/error_templates/java/precision_loss_error.go b/error_templates/java/precision_loss_error.go index 3333dbc..f3f3dc0 100644 --- a/error_templates/java/precision_loss_error.go +++ b/error_templates/java/precision_loss_error.go @@ -2,7 +2,6 @@ package java import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -18,17 +17,13 @@ var PrecisionLossError = lib.ErrorTemplate{ OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { pCtx := precisionLossCtx{} targetType := cd.Variables["targetType"] - query := fmt.Sprintf(`((local_variable_declaration type: (_) @target-type) (#eq? @target-type "%s"))`, targetType) - lib.QueryNode(m.Nearest, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - pCtx.Parent = node.Parent() - m.Nearest = pCtx.Parent.ChildByFieldName("declarator").ChildByFieldName("value") - return false - } - return true - }) + for q := m.Nearest.Query(`((local_variable_declaration type: (_) @target-type) (#eq? @target-type "%s"))`, targetType); q.Next(); { + node := q.CurrentNode() + pCtx.Parent = node.Parent() + m.Nearest = pCtx.Parent.ChildByFieldName("declarator").ChildByFieldName("value") + break + } + m.Context = pCtx }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { diff --git a/error_templates/java/private_access_error.go b/error_templates/java/private_access_error.go index 8391826..57d5a4e 100644 --- a/error_templates/java/private_access_error.go +++ b/error_templates/java/private_access_error.go @@ -21,28 +21,18 @@ var PrivateAccessError = lib.ErrorTemplate{ rootNode := m.Nearest.Doc.RootNode() // locate the right node first - query := fmt.Sprintf(`((field_access (identifier) . (identifier) @field-name) @field (#eq? @field-name "%s"))`, cd.Variables["field"]) - lib.QueryNode(rootNode, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - return false - } - return true - }) + for q := m.Nearest.Query(`((field_access (identifier) . (identifier) @field-name) @field (#eq? @field-name "%s"))`, cd.Variables["field"]); q.Next(); { + node := q.CurrentNode() + m.Nearest = node + break + } // get class declaration node - classQuery := fmt.Sprintf(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s")) @class`, className) - lib.QueryNode(rootNode, strings.NewReader(classQuery), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - pCtx.ClassDeclarationNode = node - return false - } - return true - }) + for q := rootNode.Query(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s")) @class`, className); q.Next(); { + node := q.CurrentNode() + pCtx.ClassDeclarationNode = node + break + } m.Context = pCtx }, diff --git a/error_templates/java/public_class_filename_mismatch_error.go b/error_templates/java/public_class_filename_mismatch_error.go index c2fdd0d..4eb9657 100644 --- a/error_templates/java/public_class_filename_mismatch_error.go +++ b/error_templates/java/public_class_filename_mismatch_error.go @@ -1,7 +1,6 @@ package java import ( - "fmt" "path/filepath" "strings" @@ -29,18 +28,11 @@ var PublicClassFilenameMismatchError = lib.ErrorTemplate{ expectedClassFilename: className + ".java", // the expected filename to be renamed } - query := fmt.Sprintf(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s"))`, className) - rootNode := m.Nearest.Doc.RootNode() - - lib.QueryNode(rootNode, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - return false - } - return true - }) + for q := m.Nearest.Doc.RootNode().Query(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s"))`, className); q.Next(); { + node := q.CurrentNode() + m.Nearest = node + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add(`This error occurs because the name of the Java file does not match the name of the public class within it.`) diff --git a/error_templates/java/symbol_not_found_error.go b/error_templates/java/symbol_not_found_error.go index ffba8a0..9a7a0b6 100644 --- a/error_templates/java/symbol_not_found_error.go +++ b/error_templates/java/symbol_not_found_error.go @@ -34,32 +34,21 @@ var SymbolNotFoundError = lib.ErrorTemplate{ nodeTypeToFind = "type_identifier" } - query := fmt.Sprintf("((%s) @symbol (#eq? @symbol \"%s\"))", nodeTypeToFind, symbolName) - lib.QueryNode(m.Nearest, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - errorCtx.rootNode = m.Nearest - errorCtx.parentNode = node.Parent() - m.Nearest = node - return false - } - return true - }) + for q := m.Nearest.Query("((%s) @symbol (#eq? @symbol \"%s\"))", nodeTypeToFind, symbolName); q.Next(); { + node := q.CurrentNode() + errorCtx.rootNode = m.Nearest + errorCtx.parentNode = node.Parent() + m.Nearest = node + break + } // locate the location node - locationQuery := fmt.Sprintf(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s"))`, errorCtx.locationClass) - rootNode := lib.WrapNode(m.Nearest.Doc, m.Nearest.Doc.Tree.RootNode()) - - lib.QueryNode(rootNode, strings.NewReader(locationQuery), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node.Parent()) - errorCtx.locationNode = node - return false - } - return true - }) + rootNode := m.Nearest.Doc.RootNode() + for q := rootNode.Query(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s"))`, errorCtx.locationClass); q.Next(); { + node := q.CurrentNode().Parent() + errorCtx.locationNode = node + break + } m.Context = errorCtx }, diff --git a/error_templates/java/test_files/null_pointer_exception/test.txt b/error_templates/java/test_files/null_pointer_exception/test.txt index 13f7274..8cd9732 100644 --- a/error_templates/java/test_files/null_pointer_exception/test.txt +++ b/error_templates/java/test_files/null_pointer_exception/test.txt @@ -7,24 +7,28 @@ template: "Java.NullPointerException" --- # NullPointerException This error occurs because the program is trying to access a method or property of a null object, in this case, trying to invoke the `toUpperCase()` method on a `null` string. - +``` + String test = null; + System.out.println(test.toUpperCase()); + ^^^^ + } +} +``` ## Steps to fix -1. Check for the variable that is being used as `null` and causing the issue. +### 1. Wrap with an if statement +Check for the variable that is being used as `null`. ```diff String test = null; - System.out.println(test.toUpperCase()); + if (test != null) { + System.out.println(test.toUpperCase()); -+ } else { -+ System.out.println("The string is null."); + } ``` -2. An alternative fix is to initialize the `test` variable with a non-null value before calling the `toUpperCase()` method. +### 2. Initialize the variable +An alternative fix is to initialize the `test` variable with a non-null value before calling the method. ```diff - String test = null; + String test = "example"; // Assign a non-null value System.out.println(test.toUpperCase()); ``` - -Both proposed solutions help to avoid the `NullPointerException`. The first one checks if the variable is `null` before invoking the `toUpperCase()` method. The second one ensures the variable is initialized with a non-null value to prevent the error. diff --git a/error_templates/java/unclosed_character_literal_error.go b/error_templates/java/unclosed_character_literal_error.go index 70c485f..e816f4b 100644 --- a/error_templates/java/unclosed_character_literal_error.go +++ b/error_templates/java/unclosed_character_literal_error.go @@ -2,7 +2,6 @@ package java import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -24,14 +23,11 @@ var UnclosedCharacterLiteralError = lib.ErrorTemplate{ return } - lib.QueryNode(err.Nearest, strings.NewReader("(character_literal) @literal"), func(ctx lib.QueryNodeCtx) bool { - for _, c := range ctx.Match.Captures { - node := lib.WrapNode(err.Document, c.Node) - err.Nearest = node - return false - } - return true - }) + for q := err.Nearest.Query(`(character_literal) @literal`); q.Next(); { + node := q.CurrentNode() + err.Nearest = node + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add("This error occurs when there's an attempt to define a character literal with more than one character, or if the character literal is not closed properly.") diff --git a/error_templates/java/unclosed_string_literal_error.go b/error_templates/java/unclosed_string_literal_error.go index 3a88727..473f1aa 100644 --- a/error_templates/java/unclosed_string_literal_error.go +++ b/error_templates/java/unclosed_string_literal_error.go @@ -1,8 +1,6 @@ package java import ( - "strings" - lib "github.com/nedpals/errgoengine" ) @@ -15,16 +13,13 @@ var UnclosedStringLiteralError = lib.ErrorTemplate{ if !m.Nearest.IsError() { m.Nearest = m.Nearest.Parent() } - lib.QueryNode(m.Nearest, strings.NewReader("(ERROR) @error"), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - // aCtx.NearestClass = node - return false - } - return true - }) + + for q := m.Nearest.Query(`(ERROR) @error`); q.Next(); { + node := q.CurrentNode() + m.Nearest = node + // aCtx.NearestClass = node + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add("This error occurs when there is an unclosed string literal in the code.") diff --git a/error_templates/java/uninitialized_variable_error.go b/error_templates/java/uninitialized_variable_error.go index b6d3591..525f498 100644 --- a/error_templates/java/uninitialized_variable_error.go +++ b/error_templates/java/uninitialized_variable_error.go @@ -2,7 +2,6 @@ package java import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -18,22 +17,17 @@ var UninitializedVariableError = lib.ErrorTemplate{ StackTracePattern: comptimeStackTracePattern, OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { uCtx := uninitializedVariableErrCtx{} - query := strings.NewReader(fmt.Sprintf(`((identifier) @variable (#eq? @variable "%s"))`, cd.Variables["variable"])) - lib.QueryNode(cd.MainError.Nearest, query, func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(cd.MainError.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(cd.MainError.Nearest.Doc, c.Node) - m.Nearest = node - return false - } - return true - }) + q := m.Nearest.Query(`((identifier) @variable (#eq? @variable "%s"))`, cd.Variables["variable"]) + for q.Next() { + m.Nearest = q.CurrentNode() + break + } // get symbol and declaration node - rootTree := cd.MainError.Document.RootNode() - nearestTree := cd.InitOrGetSymbolTree(cd.MainDocumentPath()).GetNearestScopedTree(cd.MainError.Nearest.StartPosition().Index) - declaredVariableSym := nearestTree.GetSymbolByNode(cd.MainError.Nearest) - declNode := rootTree.NamedDescendantForPointRange(declaredVariableSym.Location()) + root := m.Document.RootNode() + nearestTree := cd.InitOrGetSymbolTree(cd.MainDocumentPath()).GetNearestScopedTree(m.Nearest.StartPosition().Index) + declaredVariableSym := nearestTree.GetSymbolByNode(m.Nearest) + declNode := root.NamedDescendantForPointRange(declaredVariableSym.Location()) uCtx.DeclarationSym = declaredVariableSym @@ -61,7 +55,6 @@ var UninitializedVariableError = lib.ErrorTemplate{ }) gen.Add("Assign a value before using", func(s *lib.BugFixSuggestion) { - fmt.Println(cd.MainError.Document.LineAt(ctx.DeclarationNode.StartPosition().Line)) spaces := cd.MainError.Document.LineAt(ctx.DeclarationNode.StartPosition().Line)[:ctx.DeclarationNode.StartPosition().Column] s.AddStep("Alternatively, you can assign a value to the variable before using it.").AddFix(lib.FixSuggestion{ diff --git a/error_templates/python/name_error.go b/error_templates/python/name_error.go index 94bd157..508e38a 100644 --- a/error_templates/python/name_error.go +++ b/error_templates/python/name_error.go @@ -2,7 +2,6 @@ package python import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -11,15 +10,10 @@ var NameError = lib.ErrorTemplate{ Name: "NameError", Pattern: `NameError: name '(?P\S+)' is not defined`, OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { - lib.QueryNode(m.Nearest, strings.NewReader(fmt.Sprintf(`((identifier) @name (#eq? @name "%s"))`, cd.Variables["variable"])), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - return false - } - return true - }) + for q := m.Nearest.Query(`((identifier) @name (#eq? @name "%s"))`, cd.Variables["variable"]); q.Next(); { + m.Nearest = q.CurrentNode() + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { gen.Add("This error occurs when trying to use a variable (`%s`) or name that has not been defined in the current scope.", cd.Variables["variable"]) diff --git a/error_templates/python/value_error.go b/error_templates/python/value_error.go index 6081dfb..fac0aec 100644 --- a/error_templates/python/value_error.go +++ b/error_templates/python/value_error.go @@ -33,17 +33,11 @@ var ValueError = lib.ErrorTemplate{ vCtx.kind = valueErrorKindUnknown } - if len(query) != 0 { - lib.QueryNode(m.Nearest, strings.NewReader(query), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - vCtx.callNode = node - m.Nearest = node.ChildByFieldName("arguments").FirstNamedChild() - return false - } - return true - }) + for q := m.Nearest.Query(query); q.Next(); { + node := q.CurrentNode() + vCtx.callNode = node + m.Nearest = node.ChildByFieldName("arguments").FirstNamedChild() + break } m.Context = vCtx diff --git a/error_templates/python/zero_division_error.go b/error_templates/python/zero_division_error.go index da0b824..4da032e 100644 --- a/error_templates/python/zero_division_error.go +++ b/error_templates/python/zero_division_error.go @@ -2,7 +2,6 @@ package python import ( "fmt" - "strings" lib "github.com/nedpals/errgoengine" ) @@ -11,15 +10,10 @@ var ZeroDivisionError = lib.ErrorTemplate{ Name: "ZeroDivisionError", Pattern: "ZeroDivisionError: division by zero", OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { - lib.QueryNode(m.Nearest, strings.NewReader(`(binary_operator right: (_) @right)`), func(ctx lib.QueryNodeCtx) bool { - match := ctx.Cursor.FilterPredicates(ctx.Match, []byte(m.Nearest.Doc.Contents)) - for _, c := range match.Captures { - node := lib.WrapNode(m.Nearest.Doc, c.Node) - m.Nearest = node - return false - } - return true - }) + for q := m.Nearest.Query("(binary_operator right: (_) @right)"); q.Next(); { + m.Nearest = q.CurrentNode() + break + } }, OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { // TODO: diff --git a/node.go b/node.go index a779fe4..91759f6 100644 --- a/node.go +++ b/node.go @@ -1,7 +1,8 @@ package errgoengine import ( - "io" + "fmt" + "strings" sitter "github.com/smacker/go-tree-sitter" ) @@ -13,6 +14,10 @@ type SyntaxNode struct { text string } +func (n SyntaxNode) Debug() { + fmt.Println("[SyntaxNode.DEBUG]", n, n.Text()) +} + func (n SyntaxNode) Text() string { if !n.isTextCached && !n.IsNull() { n.isTextCached = true @@ -95,6 +100,10 @@ func (n SyntaxNode) RawNode() *sitter.Node { return n.Node } +func (n SyntaxNode) Query(q string, d ...any) *QueryNodeCursor { + return queryNode2(n, fmt.Sprintf(q, d...)) +} + func WrapNode(doc *Document, n *sitter.Node) SyntaxNode { return SyntaxNode{ isTextCached: false, @@ -124,31 +133,133 @@ func nearestNodeFromPos(cursor *sitter.TreeCursor, pos Position) *sitter.Node { } type QueryNodeCtx struct { - Match *sitter.QueryMatch Query *sitter.Query Cursor *sitter.QueryCursor } -func QueryNode(rootNode SyntaxNode, queryR io.Reader, callback func(QueryNodeCtx) bool) { - query, err := io.ReadAll(queryR) - if err != nil { - panic(err) +type QueryNodeCursor struct { + ctx QueryNodeCtx + doc *Document + cursor *sitter.TreeCursor + matchCursor *QueryMatchIterator + rawQuery string + hasPredicate bool +} + +func (c *QueryNodeCursor) Next() bool { + if c.matchCursor == nil || c.matchCursor.ReachedEnd() { + if !c.NextMatch() { + c.cursor.Close() + return false + } + } + return c.matchCursor.Next() +} + +func (c *QueryNodeCursor) NextMatch() bool { + // use for loop to avoid stack overflow + for { + m, ok := c.ctx.Cursor.NextMatch() + if !ok { + c.matchCursor = nil + return false + } + + // to avoid overhead of calling FilterPredicates if there are no predicates + if c.hasPredicate { + match := c.ctx.Cursor.FilterPredicates(m, []byte(c.doc.Contents)) + fmt.Println(c.rawQuery, match, m) + m = match + } + + // if there are no captures, skip to the next match + if len(m.Captures) == 0 { + continue + } + + if c.matchCursor == nil { + c.matchCursor = &QueryMatchIterator{-1, m} + } else { + // reuse the same match cursor + c.matchCursor.match = m + c.matchCursor.idx = -1 + } + return true + } +} + +func (c *QueryNodeCursor) Match() *QueryMatchIterator { + return c.matchCursor +} + +func (c *QueryNodeCursor) CurrentNode() SyntaxNode { + return WrapNode(c.doc, c.matchCursor.Current().Node) +} + +func (c *QueryNodeCursor) Query() *sitter.Query { + return c.ctx.Query +} + +func (c *QueryNodeCursor) Len() int { + if c.matchCursor == nil { + if !c.NextMatch() { + return 0 + } + } + return len(c.matchCursor.Captures()) +} + +type QueryMatchIterator struct { + idx int + match *sitter.QueryMatch +} + +func (it *QueryMatchIterator) Next() bool { + if it.idx+1 >= len(it.match.Captures) { + return false } - q, err := sitter.NewQuery(query, rootNode.Doc.Language.SitterLanguage) + it.idx++ + return true +} + +func (it *QueryMatchIterator) Current() sitter.QueryCapture { + return it.match.Captures[it.idx] +} + +func (it *QueryMatchIterator) Captures() []sitter.QueryCapture { + return it.match.Captures +} + +func (it *QueryMatchIterator) ReachedEnd() bool { + return it.idx+1 >= len(it.match.Captures) +} + +func queryNode2(node SyntaxNode, queryR string) *QueryNodeCursor { + q, err := sitter.NewQuery([]byte(queryR), node.Doc.Language.SitterLanguage) if err != nil { panic(err) } queryCursor := sitter.NewQueryCursor() - defer queryCursor.Close() + queryCursor.Exec(q, node.Node) - queryCursor.Exec(q, rootNode.Node) + // check if queryR has predicates. usually predicates are used to filter + // out matches that are not needed and the syntax for predicates starts + // with "(#" (eg. (#eq? @name "int"), (#match? @name "int")) + hasPredicate := false + if strings.Contains(queryR, "(#") { + hasPredicate = true + } - for i := 0; ; i++ { - m, ok := queryCursor.NextMatch() - if !ok || !callback(QueryNodeCtx{m, q, queryCursor}) { - break - } + cursor := &QueryNodeCursor{ + ctx: QueryNodeCtx{q, queryCursor}, + hasPredicate: hasPredicate, + doc: node.Doc, + cursor: sitter.NewTreeCursor(node.Node), + rawQuery: queryR, } + + cursor.NextMatch() + return cursor } diff --git a/symbol_analyzer.go b/symbol_analyzer.go index 7cf6cb4..44e8ba4 100644 --- a/symbol_analyzer.go +++ b/symbol_analyzer.go @@ -345,19 +345,17 @@ func (an *SymbolAnalyzer) captureAndAnalyze(parent *SymbolTree, rootNode SyntaxN panic("Parent is null") } - QueryNode(rootNode, strings.NewReader(symbolCaptures), func(ctx QueryNodeCtx) bool { - if len(ctx.Match.Captures) <= 1 || ctx.Match.Captures == nil { - return true + for q := rootNode.Query(symbolCaptures); q.NextMatch(); { + if q.Len() <= 1 { + continue } - it := &captureIterator{doc: an.doc, captures: ctx.Match.Captures} + it := &captureIterator{doc: an.doc, captures: q.Match().Captures()} it.Reset() nearest := parent.GetNearestScopedTree(int(it.Get(0).Node.StartByte())) - an.analyzeUnknown(nearest, ctx.Query, it) - - return true - }) + an.analyzeUnknown(nearest, q.Query(), it) + } } func (an *SymbolAnalyzer) Analyze(doc *Document) {