diff --git a/error_templates/java/java.go b/error_templates/java/java.go index ff37c15..bdff2e2 100644 --- a/error_templates/java/java.go +++ b/error_templates/java/java.go @@ -24,10 +24,12 @@ func LoadErrorTemplates(errorTemplates *lib.ErrorTemplates) { errorTemplates.MustAdd(java.Language, UnclosedCharacterLiteralError) errorTemplates.MustAdd(java.Language, OperatorCannotBeAppliedError) errorTemplates.MustAdd(java.Language, PrecisionLossError) + errorTemplates.MustAdd(java.Language, MissingReturnError) errorTemplates.MustAdd(java.Language, NotAStatementError) errorTemplates.MustAdd(java.Language, IncompatibleTypesError) errorTemplates.MustAdd(java.Language, UninitializedVariableError) errorTemplates.MustAdd(java.Language, AlreadyDefinedError) + errorTemplates.MustAdd(java.Language, PrivateAccessError) } func runtimeErrorPattern(errorName string, pattern string) string { diff --git a/error_templates/java/missing_return_error.go b/error_templates/java/missing_return_error.go new file mode 100644 index 0000000..6e88188 --- /dev/null +++ b/error_templates/java/missing_return_error.go @@ -0,0 +1,79 @@ +package java + +import ( + "fmt" + "strings" + + lib "github.com/nedpals/errgoengine" +) + +type missingReturnErrorCtx struct { + NearestMethod lib.SyntaxNode +} + +var MissingReturnError = lib.ErrorTemplate{ + Name: "MissingReturnError", + Pattern: comptimeErrorPattern(`missing return statement`), + StackTracePattern: comptimeStackTracePattern, + OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { + // get nearest method declaration + mCtx := missingReturnErrorCtx{} + rootNode := lib.WrapNode(m.Document, m.Document.Tree.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 + } + } + return true + }) + fmt.Println(mCtx.NearestMethod.Text()) + m.Context = mCtx + }, + OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { + gen.Add("This error occurs when a method is declared to return a value, but there is no return statement within the method.") + }, + OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) { + ctx := cd.MainError.Context.(missingReturnErrorCtx) + + // TODO + gen.Add("Provide a return statement", func(s *lib.BugFixSuggestion) { + bodyNode := ctx.NearestMethod.ChildByFieldName("body") + lastStartPosInBlock := bodyNode.EndPosition() + lastEndPosInBlock := bodyNode.EndPosition() + if bodyNode.NamedChildCount() > 0 { + lastStartPosInBlock = bodyNode.LastNamedChild().StartPosition() + lastEndPosInBlock = bodyNode.LastNamedChild().EndPosition() + } + + s.AddStep( + "Since the `%s` method is declared to return an `%s`, you need to provide a return statement with the result", + ctx.NearestMethod.ChildByFieldName("name").Text(), + ctx.NearestMethod.ChildByFieldName("type").Text(), + ).AddFix(lib.FixSuggestion{ + NewText: "\n" + cd.MainError.Document.LineAt(lastStartPosInBlock.Line)[:lastStartPosInBlock.Column] + fmt.Sprintf("return %s;", ctx.NearestMethod.ChildByFieldName("type").Text()), + StartPosition: lastEndPosInBlock, + EndPosition: lastEndPosInBlock, + Description: "This ensures that the method returns the sum of the two input numbers.", + }) + }) + + gen.Add("Set the method return type to void", func(s *lib.BugFixSuggestion) { + s.AddStep( + "If you don't intend to return a value from the `%s` method, you can change its return type to `void`.", + ctx.NearestMethod.ChildByFieldName("name").Text(), + ).AddFix(lib.FixSuggestion{ + NewText: "void", + StartPosition: ctx.NearestMethod.ChildByFieldName("type").StartPosition(), + EndPosition: ctx.NearestMethod.ChildByFieldName("type").EndPosition(), + Description: "This is appropriate if you're using the method for side effects rather than returning a value.", + }) + }) + }, +} diff --git a/error_templates/java/private_access_error.go b/error_templates/java/private_access_error.go new file mode 100644 index 0000000..8bd792f --- /dev/null +++ b/error_templates/java/private_access_error.go @@ -0,0 +1,68 @@ +package java + +import ( + "fmt" + "strings" + + lib "github.com/nedpals/errgoengine" +) + +type privateAccessErrorCtx struct { + ClassDeclarationNode lib.SyntaxNode +} + +var PrivateAccessError = lib.ErrorTemplate{ + Name: "PrivateAccessError", + Pattern: comptimeErrorPattern(`(?P\S+) has private access in (?P\S+)`), + StackTracePattern: comptimeStackTracePattern, + OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { + pCtx := privateAccessErrorCtx{} + className := cd.Variables["class"] + rootNode := lib.WrapNode(m.Nearest.Doc, m.Nearest.Doc.Tree.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 + }) + + // get class declaration node + classQuery := fmt.Sprintf(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s"))`, 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 + }) + + m.Context = pCtx + }, + OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { + gen.Add("This error occurs when you try to access a private variable from another class, which is not allowed.") + }, + OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) { + // ctx := cd.MainError.Context.(privateAccessErrorCtx) + fmt.Println(cd.MainError.Nearest.String()) + fmt.Println(cd.Analyzer.AnalyzeNode(cd.MainError.Nearest)) + + gen.Add("Use a public accessor method", func(s *lib.BugFixSuggestion) { + // methodCreatorSb := &strings.Builder{} + + // get return type of the private field + + // methodCreatorSb.WriteString("public ") + + // s.AddStep("To access a private variable from another class, create a public accessor method in `%s`", cd.Variables["class"]). + // AddFix() + }) + }, +} diff --git a/error_templates/java/test_files/missing_return_error/MissingReturn.java b/error_templates/java/test_files/missing_return_error/MissingReturn.java new file mode 100644 index 0000000..86f8633 --- /dev/null +++ b/error_templates/java/test_files/missing_return_error/MissingReturn.java @@ -0,0 +1,11 @@ +public class MissingReturn { + public int addNumbers(int a, int b) { + // Missing return statement + } + + public static void main(String[] args) { + MissingReturn calculator = new MissingReturn(); + int result = calculator.addNumbers(5, 7); + System.out.println("Result: " + result); + } +} diff --git a/error_templates/java/test_files/missing_return_error/test.txt b/error_templates/java/test_files/missing_return_error/test.txt new file mode 100644 index 0000000..0ee24f2 --- /dev/null +++ b/error_templates/java/test_files/missing_return_error/test.txt @@ -0,0 +1,41 @@ +template: "Java.MissingReturnError" +--- +MissingReturn.java:4: error: missing return statement + } + ^ +1 error +=== +template: "Java.MissingReturnError" +--- +# MissingReturnError +This error occurs when a method is declared to return a value, but there is no return statement within the method. +``` + // Missing return statement + } + ^ + + public static void main(String[] args) { +``` +## Steps to fix +### 1. Provide a return statement +Since the `addNumbers` method is declared to return an `int`, you need to provide a return statement with the result. +```diff +public class MissingReturn { + public int addNumbers(int a, int b) { +- // Missing return statement ++ // Missing return statement ++ return a; + } +``` +This ensures that the method returns the sum of the two input numbers. + +### 2. Set the method return type to void +If you don't intend to return a value from the `addNumbers` method, you can change its return type to `void`. +```diff +public class MissingReturn { +- public int addNumbers(int a, int b) { ++ public void addNumbers(int a, int b) { + // Missing return statement + } +``` +This is appropriate if you're using the method for side effects rather than returning a value. diff --git a/error_templates/java/test_files/private_access_error/Main.java b/error_templates/java/test_files/private_access_error/Main.java new file mode 100644 index 0000000..9060f72 --- /dev/null +++ b/error_templates/java/test_files/private_access_error/Main.java @@ -0,0 +1,12 @@ +public class Main { + public static void main(String[] args) { + AnotherClass anotherClass = new AnotherClass(); + // Attempting to access a private variable from another class + int value = anotherClass.privateVariable; + System.out.println(value); + } +} + +class AnotherClass { + private int privateVariable = 10; +} diff --git a/error_templates/java/test_files/private_access_error/test.txt b/error_templates/java/test_files/private_access_error/test.txt new file mode 100644 index 0000000..0ef2cd1 --- /dev/null +++ b/error_templates/java/test_files/private_access_error/test.txt @@ -0,0 +1,39 @@ +template: "Java.PrivateAccessError" +--- +Main.java:5: error: privateVariable has private access in AnotherClass + int value = anotherClass.privateVariable; + ^ +1 error +=== +template: "Java.PrivateAccessError" +--- +# AlreadyDefinedError +This error occurs when you try to access a private variable from another class, which is not allowed. + +## Steps to fix +### 1. Use a public accessor method +1. To access a private variable from another class, create a public accessor method in `AnotherClass`. +```diff ++ public int getPrivateVariable() { ++ return privateVariable; ++ } +``` +2. Then, use this method to get the value in the `Main` class. +```diff +- int value = anotherClass.privateVariable; ++ int value = anotherClass.getPrivateVariable(); +``` +This way, you respect encapsulation by using a method to access the private variable. + +### 2. Make the variable public (not recommended) +1. If you must access the variable directly, you can make it public, but this is generally not recommended for maintaining encapsulation. +```diff +- private int privateVariable = 10; ++ public int privateVariable = 10; +``` +2. Access it directly in the `Main` class. +```diff +- int value = anotherClass.privateVariable; ++ int value = anotherClass.privateVariable; +``` +Choose the fix that aligns with your design principles. Using an accessor method is a better practice for encapsulation. diff --git a/languages/java/language.go b/languages/java/language.go index f648b0e..4118345 100644 --- a/languages/java/language.go +++ b/languages/java/language.go @@ -74,6 +74,12 @@ func (an *javaAnalyzer) AnalyzeNode(n lib.SyntaxNode) lib.Symbol { sym := an.FindSymbol(n.Text(), int(n.StartByte())) if sym == nil { + if n.Type() == "type_identifier" { + an.ContextData. + + // mark type as unresolved + return lib.UnresolvedSymbol + } return BuiltinTypes.NullSymbol } diff --git a/symbols.go b/symbols.go index 621d5b1..cff41a2 100644 --- a/symbols.go +++ b/symbols.go @@ -48,6 +48,7 @@ func NewSymbolKindFromString(str string) SymbolKind { const ( SymbolKindUnknown SymbolKind = 0 + SymbolKindUnresolved SymbolKind = iota SymbolKindBuiltin SymbolKind = iota SymbolKindClass SymbolKind = iota SymbolKindFunction SymbolKind = iota @@ -219,6 +220,22 @@ func (sym ImportSymbol) Location() Location { } } +type unresolvedSymbol struct{} + +func (sym unresolvedSymbol) Name() string { + return "unresolved" +} + +func (sym unresolvedSymbol) Kind() SymbolKind { + return SymbolKindUnresolved +} + +func (sym unresolvedSymbol) Location() Location { + return Location{} +} + +var UnresolvedSymbol Symbol = unresolvedSymbol{} + // TODO: // func (sym ImportSymbol) Children() *SymbolTree { // // TODO: diff --git a/tests/java/.vscode/settings.json b/tests/java/.vscode/settings.json index 4110c1c..f524615 100644 --- a/tests/java/.vscode/settings.json +++ b/tests/java/.vscode/settings.json @@ -1,3 +1,4 @@ { - "bugbuddy.path": "/Users/nedpals/Documents/coding/bugbuddy-proto/server/cmd/bugbuddy" + "bugbuddy.path": "/Users/nedpals/Documents/coding/bugbuddy-proto/server/cmd/bugbuddy", + "java.debug.settings.onBuildFailureProceed": true }