Skip to content

Commit

Permalink
Merge branch 'master' of github.com:nedpals/errgoengine
Browse files Browse the repository at this point in the history
  • Loading branch information
nedpals committed Mar 20, 2024
2 parents 90b082f + 5f2d310 commit 2166995
Show file tree
Hide file tree
Showing 25 changed files with 1,157 additions and 239 deletions.
95 changes: 90 additions & 5 deletions errgoengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type ErrgoEngine struct {
ErrorTemplates ErrorTemplates
FS *MultiReadFileFS
OutputGen *OutputGenerator
IsTesting bool
}

func New() *ErrgoEngine {
Expand Down Expand Up @@ -56,6 +57,7 @@ func (e *ErrgoEngine) Analyze(workingPath, msg string) (*CompiledErrorTemplate,
contextData := NewContextData(e.SharedStore, workingPath)
contextData.Analyzer = template.Language.AnalyzerFactory(contextData)
contextData.AddVariable("message", msg)
contextData.FS = e.FS

// extract variables from the error message
contextData.AddVariables(template.ExtractVariables(msg))
Expand Down Expand Up @@ -125,19 +127,64 @@ func (e *ErrgoEngine) Translate(template *CompiledErrorTemplate, contextData *Co
template.OnGenBugFixFn(contextData, fixGen)
}

output := e.OutputGen.Generate(contextData, expGen, fixGen)
if e.IsTesting {
// add a code snippet that points to the error
e.OutputGen.GenAfterExplain = func(gen *OutputGenerator) {
err := contextData.MainError
if err == nil {
return
}

doc := err.Document
if doc == nil || err.Nearest.IsNull() {
return
}

startLineNr := err.Nearest.StartPosition().Line
startLines := doc.LinesAt(max(startLineNr-1, 0), startLineNr)
endLines := doc.LinesAt(min(startLineNr+1, doc.TotalLines()), min(startLineNr+2, doc.TotalLines()))
arrowLength := int(err.Nearest.EndByte() - err.Nearest.StartByte())
if arrowLength == 0 {
arrowLength = 1
}

startArrowPos := err.Nearest.StartPosition().Column
gen.Writeln("```")
gen.WriteLines(startLines...)

for i := 0; i < startArrowPos; i++ {
if startLines[len(startLines)-1][i] == '\t' {
gen.Builder.WriteString(" ")
} else {
gen.Builder.WriteByte(' ')
}
}

for i := 0; i < arrowLength; i++ {
gen.Builder.WriteByte('^')
}

gen.Break()
gen.WriteLines(endLines...)
gen.Writeln("```")
}
}

output := e.OutputGen.Generate(expGen, fixGen)
defer e.OutputGen.Reset()

return expGen.Builder.String(), output
}

func ParseFromStackTrace(contextData *ContextData, defaultLanguage *Language, files fs.ReadFileFS) error {
func ParseFiles(contextData *ContextData, defaultLanguage *Language, files fs.ReadFileFS, fileNames []string) error {
if files == nil {
return fmt.Errorf("files is nil")
}

parser := sitter.NewParser()
analyzer := &SymbolAnalyzer{ContextData: contextData}

for _, node := range contextData.TraceStack {
path := node.DocumentPath

for _, path := range fileNames {
contents, err := files.ReadFile(path)
if err != nil {
// return err
Expand All @@ -153,6 +200,11 @@ func ParseFromStackTrace(contextData *ContextData, defaultLanguage *Language, fi
// check if document already exists
existingDoc, docExists := contextData.Documents[path]

if docExists && existingDoc.BytesContentEquals(contents) {
// do not parse if content is the same
continue
}

// check matched languages
selectedLanguage := defaultLanguage
if docExists {
Expand Down Expand Up @@ -183,3 +235,36 @@ func ParseFromStackTrace(contextData *ContextData, defaultLanguage *Language, fi

return nil
}

func ParseFromStackTrace(contextData *ContextData, defaultLanguage *Language, files fs.ReadFileFS) error {
filesToParse := []string{}

for _, node := range contextData.TraceStack {
path := node.DocumentPath
if path == "" {
continue
}

// check if file is already parsed
if _, ok := contextData.Documents[path]; ok {
continue
}

// check if file is already in the list
found := false
for _, f := range filesToParse {
if f == path {
found = true
break
}
}

if found {
continue
}

filesToParse = append(filesToParse, path)
}

return ParseFiles(contextData, defaultLanguage, files, filesToParse)
}
8 changes: 3 additions & 5 deletions error_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import (
"strings"
)

type GenAnalyzeErrorFn func(cd *ContextData, m *MainError)

type ErrorTemplate struct {
Name string
Pattern string
StackTracePattern string
OnAnalyzeErrorFn GenAnalyzeErrorFn
OnGenExplainFn GenExplainFn
OnGenBugFixFn GenBugFixFn
OnAnalyzeErrorFn func(cd *ContextData, m *MainError)
OnGenExplainFn func(cd *ContextData, gen *ExplainGenerator)
OnGenBugFixFn func(cd *ContextData, gen *BugFixGenerator)
}

func CustomErrorPattern(pattern string) string {
Expand Down
126 changes: 111 additions & 15 deletions error_templates/java/identifier_expected_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,119 @@ package java

import (
"context"
"strings"

lib "github.com/nedpals/errgoengine"
"github.com/nedpals/errgoengine/utils/levenshtein"
"github.com/nedpals/errgoengine/utils/slice"
sitter "github.com/smacker/go-tree-sitter"
)

type identifiedExpectedReasonKind int

const (
identifierExpectedReasonUnknown identifiedExpectedReasonKind = 0
identifierExpectedReasonClassInterfaceEnum identifiedExpectedReasonKind = iota
identifierExpectedReasonTypo identifiedExpectedReasonKind = iota
)

type identifierExpectedFixKind int

const (
identifierExpectedFixUnknown identifierExpectedFixKind = 0
identifierExpectedFixWrapFunction identifierExpectedFixKind = iota
identifierExpectedCorrectTypo identifierExpectedFixKind = iota
)

type identifierExpectedErrorCtx struct {
fixKind identifierExpectedFixKind
reasonKind identifiedExpectedReasonKind
typoWord string // for identifierExpectedCorrectTypo. the word that is a typo
wordForTypo string // for identifierExpectedCorrectTypo. the closest word to replace the typo
fixKind identifierExpectedFixKind
}

var IdentifierExpectedError = lib.ErrorTemplate{
Name: "IdentifierExpectedError",
Pattern: comptimeErrorPattern(`<identifier> expected`),
Pattern: comptimeErrorPattern(`(?P<reason>\<identifier\>|class, interface, or enum) expected`),
StackTracePattern: comptimeStackTracePattern,
OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
iCtx := identifierExpectedErrorCtx{}

// identify the reason
switch cd.Variables["reason"] {
case "class, interface, or enum":
iCtx.reasonKind = identifierExpectedReasonClassInterfaceEnum
default:
iCtx.reasonKind = identifierExpectedReasonUnknown
}

// TODO: check if node is parsable
if tree, err := sitter.ParseCtx(
if iCtx.reasonKind == identifierExpectedReasonClassInterfaceEnum {
accessTokens := []string{"public"}
statementTokens := []string{"class", "interface", "enum"}

// use levenstein distance to check if the word is a typo
tokens := append(accessTokens, statementTokens...)

// get the nearest word
nearestWord := ""
wordToReplace := ""

// get the contents of that line
line := m.Document.LineAt(m.Nearest.StartPosition().Line)
lineTokens := strings.Split(line, " ")

// position
nearestCol := 0

for _, token := range tokens {
for ltIdx, lineToken := range lineTokens {
distance := levenshtein.ComputeDistance(token, lineToken)
if distance >= 1 && distance <= 3 {
wordToReplace = lineToken
nearestWord = token

// compute the position of the word
for i := 0; i < ltIdx; i++ {
nearestCol += len(lineTokens[i]) + 1
}

// add 1 to nearestCol to get the portion of the word
nearestCol++
break
}
}
}

if nearestWord != "" {
iCtx.wordForTypo = nearestWord
iCtx.typoWord = wordToReplace
iCtx.fixKind = identifierExpectedCorrectTypo

targetPos := lib.Position{
Line: m.Nearest.StartPosition().Line,
Column: nearestCol,
}

// get the nearest node of the word from the position
initialNearest := m.Document.RootNode().NamedDescendantForPointRange(lib.Location{
StartPos: targetPos,
EndPos: targetPos,
})

rawNearestNode := nearestNodeFromPos2(initialNearest.TreeCursor(), targetPos)
if rawNearestNode != nil {
m.Nearest = lib.WrapNode(m.Document, rawNearestNode)
} else {
m.Nearest = initialNearest
}

// if nearestword is not a statement token, then it's a typo
if !slice.ContainsString(statementTokens, nearestWord) {
iCtx.reasonKind = identifierExpectedReasonTypo
}
}
} else if tree, err := sitter.ParseCtx(
context.Background(),
[]byte(m.Nearest.Text()),
m.Document.Language.SitterLanguage,
Expand All @@ -37,18 +125,17 @@ var IdentifierExpectedError = lib.ErrorTemplate{
m.Context = iCtx
},
OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
gen.Add("This error occurs when an identifier is expected, but an expression is found in a location where a statement or declaration is expected.")

// ctx := cd.MainError.Context.(IdentifierExpectedErrorCtx)

// switch ctx.kind {
// case cannotBeAppliedMismatchedArgCount:
// gen.Add("This error occurs when there is an attempt to apply a method with an incorrect number of arguments.")
// case cannotBeAppliedMismatchedArgType:
// gen.Add("This error occurs when there is an attempt to apply a method with arguments that do not match the method signature.")
// default:
// gen.Add("unable to determine.")
// }
iCtx := cd.MainError.Context.(identifierExpectedErrorCtx)

switch iCtx.reasonKind {
case identifierExpectedReasonClassInterfaceEnum:
gen.Add("This error occurs when there's a typo or the keyword `class`, `interface`, or `enum` is missing.")
case identifierExpectedReasonTypo:
gen.Add("This error indicates there's a typo or misspelled word in your code.")
default:
gen.Add("This error occurs when an identifier is expected, but an expression is found in a location where a statement or declaration is expected.")
}

},
OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
ctx := cd.MainError.Context.(identifierExpectedErrorCtx)
Expand All @@ -75,6 +162,15 @@ var IdentifierExpectedError = lib.ErrorTemplate{
EndPosition: cd.MainError.Nearest.EndPosition(),
})
})
case identifierExpectedCorrectTypo:
gen.Add("Correct the typo", func(s *lib.BugFixSuggestion) {
s.AddStep("Change `%s` to `%s`.", ctx.typoWord, ctx.wordForTypo).
AddFix(lib.FixSuggestion{
NewText: ctx.wordForTypo,
StartPosition: cd.MainError.Nearest.StartPosition(),
EndPosition: cd.MainError.Nearest.EndPosition(),
})
})
}
},
}
13 changes: 6 additions & 7 deletions error_templates/java/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,19 +184,18 @@ func getSpaceBoundaryIndiv(line string, idx int, defaultDirection spaceComputeDi
}

if defaultDirection == spaceComputeDirectionLeft {
// if idx is...
// 1. greater than 0
// 2. not a space character (if stopOnSpace is false)
// 3. or a space character (if stopOnSpace is true)
// then return the current index
if idx-1 >= 0 && ((!stopOnSpace && !isSpace(line, idx-1)) ||
(stopOnSpace && isSpace(line, idx-1))) {
return idx
}

// if idx is less than 0, return the current index
if idx-1 < 0 {
// check if the current index is not a space
if !isSpace(line, idx) {
// go to the reverse direction to get the nearest space
newIdx := getSpaceBoundaryIndiv(line, idx, spaceComputeDirectionRight)
return newIdx
}

return idx
}

Expand Down
Loading

0 comments on commit 2166995

Please sign in to comment.