Skip to content

Commit

Permalink
feat: support for symbol not found outside class
Browse files Browse the repository at this point in the history
  • Loading branch information
nedpals committed Feb 4, 2024
1 parent a8896ef commit f4253b2
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 19 deletions.
124 changes: 105 additions & 19 deletions error_templates/java/symbol_not_found_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,32 @@ import (
)

type symbolNotFoundErrorCtx struct {
symbolType string
symbolName string
locationClass string
locationNode lib.SyntaxNode
rootNode lib.SyntaxNode
parentNode lib.SyntaxNode
symbolType string
symbolName string
locationClass string
locationVariable string
locationVarType string
variableType lib.Symbol // only if the locationVariable is present
classLocation lib.Location
locationNode lib.SyntaxNode
rootNode lib.SyntaxNode
parentNode lib.SyntaxNode
}

var SymbolNotFoundError = lib.ErrorTemplate{
Name: "SymbolNotFoundError",
Pattern: comptimeErrorPattern("cannot find symbol", `symbol:\s+(?P<symbolType>variable|method|class) (?P<symbolName>\S+)\s+location\:\s+class (?P<locationClass>\S+)`),
Pattern: comptimeErrorPattern("cannot find symbol", `symbol:\s+(?P<symbolType>variable|method|class) (?P<symbolName>\S+)\s+location\:\s+(?:(?:class (?P<locationClass>\S+))|(?:variable (?P<locationVariable>\S+) of type (?P<locationVarType>\S+)))`),
StackTracePattern: comptimeStackTracePattern,
OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
symbolName := cd.Variables["symbolName"]
errorCtx := symbolNotFoundErrorCtx{
symbolType: cd.Variables["symbolType"],
locationClass: cd.Variables["locationClass"],
symbolName: symbolName,
rootNode: m.Nearest,
symbolType: cd.Variables["symbolType"],
locationClass: cd.Variables["locationClass"],
locationVariable: cd.Variables["locationVariable"],
locationVarType: cd.Variables["locationVarType"],
symbolName: symbolName,
variableType: lib.UnresolvedSymbol,
rootNode: m.Nearest,
}

nodeTypeToFind := "identifier"
Expand All @@ -43,11 +50,73 @@ var SymbolNotFoundError = lib.ErrorTemplate{
}

// locate the location node
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
if len(errorCtx.locationClass) > 0 {
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
}
} else if len(errorCtx.locationVariable) > 0 && len(errorCtx.locationVarType) > 0 {
rootNode := m.Nearest.Doc.RootNode()
for q := rootNode.Query(`
(local_variable_declaration
type: (_) @var-type
declarator: (variable_declarator
name: (identifier) @var-name
(#eq? @var-name "%s"))
(#eq? @var-type "%s"))`,
errorCtx.locationVariable,
errorCtx.locationVarType); q.Next(); q.Next() {
node := q.CurrentNode().Parent()
errorCtx.locationNode = node
break
}
} else if len(errorCtx.locationVariable) > 0 {
rootNode := m.Nearest.Doc.RootNode()
for q := rootNode.Query(`(variable_declarator name: (identifier) @var-name (#eq? @var-name "%s"))`, errorCtx.locationVariable); q.Next(); {
node := q.CurrentNode().Parent()
errorCtx.locationNode = node
break
}

// get type of the variable
declNode := errorCtx.locationNode.Parent() // assumes that this is local_variable_declaration
typeNode := declNode.ChildByFieldName("type")

errorCtx.locationVarType = typeNode.Text()
}

if len(errorCtx.locationVarType) != 0 {
errorCtx.locationClass = errorCtx.locationVarType
foundVariableType := cd.FindSymbol(errorCtx.locationVarType, -1)
if foundVariableType != nil {
errorCtx.variableType = foundVariableType

// cast to top level symbol
if topLevelSym, ok := foundVariableType.(*lib.TopLevelSymbol); ok {
errorCtx.classLocation = topLevelSym.Location()
}
} else {
errorCtx.variableType = lib.UnresolvedSymbol
}
}

if !errorCtx.locationNode.IsNull() && errorCtx.locationNode.Type() != "class_declaration" {
if errorCtx.classLocation.DocumentPath != "" {
// go to the class declaration of that specific class
doc := cd.Store.Documents[errorCtx.classLocation.DocumentPath]
foundNode := doc.RootNode().NamedDescendantForPointRange(errorCtx.classLocation)

if !foundNode.IsNull() {
errorCtx.locationNode = foundNode
}
} else {
// go up to the class declaration
for !errorCtx.locationNode.IsNull() && errorCtx.locationNode.Type() != "class_declaration" {
errorCtx.locationNode = errorCtx.locationNode.Parent()
}
}
}

m.Context = errorCtx
Expand Down Expand Up @@ -80,6 +149,11 @@ var SymbolNotFoundError = lib.ErrorTemplate{
})
})
case "method":
// change the doc to use for defining the missing method
if ctx.classLocation.DocumentPath != "" {
gen.Document = cd.Store.Documents[ctx.classLocation.DocumentPath]
}

gen.Add("Define the missing method.", func(s *lib.BugFixSuggestion) {
bodyNode := ctx.locationNode.ChildByFieldName("body")
lastMethodNode := bodyNode.LastNamedChild()
Expand All @@ -91,7 +165,12 @@ var SymbolNotFoundError = lib.ErrorTemplate{
}

// TODO: smartly infer the method signature for the missing method
s.AddStep("Add the missing method `%s` to the `%s` class", methodName, ctx.locationClass).
prefix := "Add"
if ctx.classLocation.DocumentPath != "" {
prefix = fmt.Sprintf("In `%s`, add", ctx.classLocation.DocumentPath)
}

s.AddStep("%s the missing method `%s` to the `%s` class", prefix, methodName, ctx.locationClass).
AddFix(lib.FixSuggestion{
NewText: fmt.Sprintf("\n\n\tprivate static void %s(%s) {\n\t\t// Add code here\n\t}\n", methodName, strings.Join(parameters, ", ")),
StartPosition: lastMethodNode.EndPosition().Add(lib.Position{Column: 1}), // add 1 column so that the parenthesis won't be replaced
Expand All @@ -118,7 +197,14 @@ var SymbolNotFoundError = lib.ErrorTemplate{
}

func parseMethodSignature(symbolName string) (methodName string, parameterTypes []string) {
methodName = symbolName[:strings.Index(symbolName, "(")]
parameterTypes = strings.Split(symbolName[strings.Index(symbolName, "(")+1:strings.Index(symbolName, ")")], ",")
openingPar := strings.Index(symbolName, "(")
closingPar := strings.Index(symbolName, ")")

methodName = symbolName[:openingPar]
if openingPar+1 == closingPar {
return
}

parameterTypes = strings.Split(symbolName[openingPar+1:closingPar], ",")
return methodName, parameterTypes
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class Test {
public static void main(String[] args) {
Triangle tri = new Triangle();
tri.getArea();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public class Triangle {
public static int getSides() {
return 3;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: "MissingMethodImported"
template: "Java.SymbolNotFoundError"
---
Test.java:4: error: cannot find symbol
tri.getArea();
^
symbol: method getArea()
location: variable tri of type Triangle
1 error
===
template: "Java.SymbolNotFoundError"
---
# SymbolNotFoundError
The error indicates that the compiler cannot find the method `getArea()` in the `Triangle` class.
```
Triangle tri = new Triangle();
tri.getArea();
^^^^^^^^^^^^^^
}
}
```
## Steps to fix
### Define the missing method.
In `Triangle.java`, add the missing method `getArea` to the `Triangle` class.
```diff
public static int getSides() {
return 3;
}
+
+ private static void getArea() {
+ // Add code here
+ }
}

```

0 comments on commit f4253b2

Please sign in to comment.