From 4a7c9984c0bf5453b26071377f5b1ffe3dfaf30a Mon Sep 17 00:00:00 2001 From: Ned Palacios Date: Wed, 13 Dec 2023 16:39:21 +0800 Subject: [PATCH] feat: add support for Java.AlreadyDefinedError template --- error_templates/java/already_defined_error.go | 114 ++++++++++++++++++ error_templates/java/java.go | 1 + .../already_defined_error/Main.java | 8 ++ .../test_files/already_defined_error/test.txt | 42 +++++++ 4 files changed, 165 insertions(+) create mode 100644 error_templates/java/already_defined_error.go create mode 100644 error_templates/java/test_files/already_defined_error/Main.java create mode 100644 error_templates/java/test_files/already_defined_error/test.txt diff --git a/error_templates/java/already_defined_error.go b/error_templates/java/already_defined_error.go new file mode 100644 index 0000000..3f2e52d --- /dev/null +++ b/error_templates/java/already_defined_error.go @@ -0,0 +1,114 @@ +package java + +import ( + "fmt" + "regexp" + "strings" + + lib "github.com/nedpals/errgoengine" +) + +type alreadyDefinedErrorCtx struct { + NearestClass lib.SyntaxNode + NearestMethod lib.SyntaxNode +} + +var AlreadyDefinedError = lib.ErrorTemplate{ + Name: "AlreadyDefinedError", + Pattern: comptimeErrorPattern(`variable (?P\S+) is already defined in method (?P.+)`), + StackTracePattern: comptimeStackTracePattern, + OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { + aCtx := alreadyDefinedErrorCtx{} + rootNode := lib.WrapNode(m.Document, m.Document.Tree.RootNode()) + rawQuery := parseSymbolSignature(cd.Variables["symbolSignature"]) + 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 + } + } + 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 + }) + + m.Context = aCtx + }, + OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { + gen.Add("This error occurs when you try to declare a variable with a name that is already in use within the same scope.") + }, + OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) { + gen.Add("Remove redeclaration", func(s *lib.BugFixSuggestion) { + s.AddStep("To resolve the already defined error, remove the attempt to redeclare the variable '%s'.", cd.Variables["variable"]). + AddFix(lib.FixSuggestion{ + NewText: "", + StartPosition: lib.Position{Line: cd.MainError.Nearest.StartPosition().Line, Column: 0}, + EndPosition: cd.MainError.Nearest.EndPosition(), + Description: fmt.Sprintf("Since '%s' is already declared earlier in the method, you don't need to declare it again.", cd.Variables["variable"]), + }) + }) + + gen.Add("Assign a new value", func(s *lib.BugFixSuggestion) { + dupeVarType := cd.MainError.Nearest.ChildByFieldName("type") + dupeVarDeclarator := cd.MainError.Nearest.ChildByFieldName("declarator") + + s.AddStep("If you intended to change the value of '%s', you can simply assign a new value to the existing variable.", cd.Variables["variable"]). + AddFix(lib.FixSuggestion{ + NewText: "", + StartPosition: dupeVarType.StartPosition(), + EndPosition: dupeVarDeclarator.StartPosition(), + Description: fmt.Sprintf("This way, you update the value of '%s' without redeclaring it.", cd.Variables["variable"]), + }) + }) + }, +} + +var symbolSigRegex = regexp.MustCompile(`^(?m)(\S+)\((.+)\)$`) + +// converts the signature into a tree-sitter query +func parseSymbolSignature(str string) string { + sb := &strings.Builder{} + methodName := "" + paramTypes := []string{} + + for _, submatches := range symbolSigRegex.FindAllStringSubmatch(str, -1) { + for i, matchedContent := range submatches { + switch i { + case 1: + methodName = matchedContent + case 2: + paramTypes = strings.Split(matchedContent, ",") + } + } + } + + sb.WriteByte('(') + sb.WriteString("(method_declaration name: (identifier) @method-name parameters: (formal_parameters") + for i := range paramTypes { + sb.WriteString(fmt.Sprintf(" (formal_parameter type: (_) @param-%d-type)", i)) + } + sb.WriteString(")) @method") + sb.WriteString(" (#eq? @method-name \"" + methodName + "\")") + for i, expType := range paramTypes { + sb.WriteString(fmt.Sprintf(" (#eq? @param-%d-type \"%s\")", i, expType)) + } + sb.WriteByte(')') + return sb.String() +} diff --git a/error_templates/java/java.go b/error_templates/java/java.go index 908fa13..ff37c15 100644 --- a/error_templates/java/java.go +++ b/error_templates/java/java.go @@ -27,6 +27,7 @@ func LoadErrorTemplates(errorTemplates *lib.ErrorTemplates) { errorTemplates.MustAdd(java.Language, NotAStatementError) errorTemplates.MustAdd(java.Language, IncompatibleTypesError) errorTemplates.MustAdd(java.Language, UninitializedVariableError) + errorTemplates.MustAdd(java.Language, AlreadyDefinedError) } func runtimeErrorPattern(errorName string, pattern string) string { diff --git a/error_templates/java/test_files/already_defined_error/Main.java b/error_templates/java/test_files/already_defined_error/Main.java new file mode 100644 index 0000000..9d8ea17 --- /dev/null +++ b/error_templates/java/test_files/already_defined_error/Main.java @@ -0,0 +1,8 @@ +public class Main { + public static void main(String[] args) { + int x = 5; + // Attempting to redeclare the variable 'x' + int x = 10; + System.out.println(x); + } +} diff --git a/error_templates/java/test_files/already_defined_error/test.txt b/error_templates/java/test_files/already_defined_error/test.txt new file mode 100644 index 0000000..39f7c3c --- /dev/null +++ b/error_templates/java/test_files/already_defined_error/test.txt @@ -0,0 +1,42 @@ +template: "Java.AlreadyDefinedError" +--- +Main.java:5: error: variable x is already defined in method main(String[]) + int x = 10; + ^ +1 error +=== +template: "Java.AlreadyDefinedError" +--- +# AlreadyDefinedError +This error occurs when you try to declare a variable with a name that is already in use within the same scope. +``` + // Attempting to redeclare the variable 'x' + int x = 10; + ^^^^^^^^^^^ + System.out.println(x); + } +``` +## Steps to fix +### 1. Remove redeclaration +To resolve the already defined error, remove the attempt to redeclare the variable 'x'. +```diff + public static void main(String[] args) { + int x = 5; + // Attempting to redeclare the variable 'x' +- int x = 10; + System.out.println(x); + } +``` +Since 'x' is already declared earlier in the method, you don't need to declare it again. + +### 2. Assign a new value +If you intended to change the value of 'x', you can simply assign a new value to the existing variable. +```diff + int x = 5; + // Attempting to redeclare the variable 'x' +- int x = 10; ++ x = 10; + System.out.println(x); + } +``` +This way, you update the value of 'x' without redeclaring it.