From 548cbc1764b3d4a3b98dcccb227825aa8456f877 Mon Sep 17 00:00:00 2001 From: "notzippy@gmail.com" Date: Sat, 25 Apr 2020 15:32:29 -0700 Subject: [PATCH] Upatede Error type to SourceError Added processor object to code Verified compile errors appearing Signed-off-by: notzippy@gmail.com --- harness/build.go | 6 +- harness/harness.go | 16 +-- parser/reflect.go | 2 +- parser/utils.go | 18 ---- parser2/read.go | 252 +++++++++++++++++++++++++++++++++---------- tests/testrunner.go | 2 +- utils/build_error.go | 59 ++++++++++ utils/error.go | 12 +-- utils/file.go | 2 +- watcher/watcher.go | 12 +-- 10 files changed, 282 insertions(+), 99 deletions(-) delete mode 100644 parser/utils.go diff --git a/harness/build.go b/harness/build.go index b8bc7f9..51adb64 100644 --- a/harness/build.go +++ b/harness/build.go @@ -388,7 +388,7 @@ func containsValue(m map[string]string, val string) bool { // Parse the output of the "go build" command. // Return a detailed Error. -func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error { +func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceError { errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`). FindSubmatch(output) if errorMatch == nil { @@ -396,7 +396,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error { if errorMatch == nil { utils.Logger.Error("Failed to parse build errors", "error", string(output)) - return &utils.Error{ + return &utils.SourceError{ SourceType: "Go code", Title: "Go Compilation Error", Description: "See console for build error.", @@ -429,7 +429,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error { absFilename = findInPaths(relFilename) line, _ = strconv.Atoi(string(errorMatch[2])) description = string(errorMatch[4]) - compileError = &utils.Error{ + compileError = &utils.SourceError{ SourceType: "Go code", Title: "Go Compilation Error", Path: relFilename, diff --git a/harness/harness.go b/harness/harness.go index 546016e..bdf8002 100644 --- a/harness/harness.go +++ b/harness/harness.go @@ -89,12 +89,12 @@ func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err erro fmt.Fprintf(iw, "An error ocurred %s", err.Error()) return } - var revelError *utils.Error + var revelError *utils.SourceError switch e := err.(type) { - case *utils.Error: + case *utils.SourceError: revelError = e case error: - revelError = &utils.Error{ + revelError = &utils.SourceError{ Title: "Server Error", Description: e.Error(), } @@ -199,7 +199,7 @@ func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode str // Refresh method rebuilds the Revel application and run it on the given port. // called by the watcher -func (h *Harness) Refresh() (err *utils.Error) { +func (h *Harness) Refresh() (err *utils.SourceError) { // Allow only one thread to rebuild the process // If multiple requests to rebuild are queued only the last one is executed on // So before a build is started we wait for a second to determine if @@ -217,10 +217,10 @@ func (h *Harness) Refresh() (err *utils.Error) { h.app, newErr = Build(h.config, h.paths) if newErr != nil { utils.Logger.Error("Build detected an error", "error", newErr) - if castErr, ok := newErr.(*utils.Error); ok { + if castErr, ok := newErr.(*utils.SourceError); ok { return castErr } - err = &utils.Error{ + err = &utils.SourceError{ Title: "App failed to start up", Description: err.Error(), } @@ -231,10 +231,10 @@ func (h *Harness) Refresh() (err *utils.Error) { h.app.Port = h.port if err2 := h.app.Cmd(h.runMode).Start(h.config); err2 != nil { utils.Logger.Error("Could not start application", "error", err2) - if err,k :=err2.(*utils.Error);k { + if err,k :=err2.(*utils.SourceError);k { return err } - return &utils.Error{ + return &utils.SourceError{ Title: "App failed to start up", Description: err2.Error(), } diff --git a/parser/reflect.go b/parser/reflect.go index b4390f2..212e14e 100644 --- a/parser/reflect.go +++ b/parser/reflect.go @@ -85,7 +85,7 @@ func (pc *processContainer) processPath(path string, info os.FileInfo, err error if err != nil { if errList, ok := err.(scanner.ErrorList); ok { var pos = errList[0].Pos - newError := &utils.Error{ + newError := &utils.SourceError{ SourceType: ".go source", Title: "Go Compilation Error", Path: pos.Filename, diff --git a/parser/utils.go b/parser/utils.go deleted file mode 100644 index 8239d15..0000000 --- a/parser/utils.go +++ /dev/null @@ -1,18 +0,0 @@ -package parser - -import ( - //"golang.org/x/tools/go/packages" - //"github.com/revel/cmd/utils" -) -//import "golang.org/x/tools/go/packages" -// -//func GetPackage(appPath, importPath string) { -// config := &packages.Config{ -// Mode: packages.NeedName | packages.NeedFiles, -// Dir:appPath, -// } -// -// pkgs, err := packages.Load(config, []string{importPath}) -// utils.Logger.Info("Loaded packegs ", "len results", len(pkgs), "error",err) -// -//} diff --git a/parser2/read.go b/parser2/read.go index 3e1e738..f1ed059 100644 --- a/parser2/read.go +++ b/parser2/read.go @@ -1,76 +1,218 @@ package parser2 import ( - //"go/ast" - //"go/token" + "go/ast" + "go/token" "github.com/revel/cmd/model" "golang.org/x/tools/go/packages" "github.com/revel/cmd/utils" "errors" + "fmt" + "strings" + "github.com/revel/cmd/logger" + +) +type ( + SourceProcessor struct { + revelContainer *model.RevelContainer + log logger.MultiLogger + packageList []*packages.Package + importMap map[string]string + } ) func ProcessSource(revelContainer *model.RevelContainer) (sourceInfo *model.SourceInfo, compileError error) { utils.Logger.Info("ProcessSource") - // Combine packages for modules and app and revel - allPackages := []string{revelContainer.ImportPath+"/app/controllers/...",model.RevelImportPath} - for _,module := range revelContainer.ModulePathMap { - allPackages = append(allPackages,module.ImportPath+"/app/controllers/...") - } - - config := &packages.Config{ - Mode: packages.NeedName | packages.NeedFiles | packages.LoadTypes | packages.NeedTypes | packages.NeedSyntax , //| packages.NeedImports | - // packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo, - //packages.LoadSyntax | packages.NeedDeps, - Dir:revelContainer.AppPath, - } - utils.Logger.Info("Before ","apppath", config.Dir,"paths",allPackages) - pkgs, err := packages.Load(config, allPackages...) - utils.Logger.Info("***Loaded packegs ", "len results", len(pkgs), "error",err) - // Lets see if we can output all the path names - //packages.Visit(pkgs,func(p *packages.Package) bool{ - // println("Got pre",p.ID) - // return true - //}, func(p *packages.Package) { - //}) - counter := 0 - for _, p := range pkgs { - utils.Logger.Info("Errores","error",p.Errors, "id",p.ID) - //for _,g := range p.GoFiles { - // println("File", g) - //} - //for _, t:= range p.Syntax { - // utils.Logger.Info("File","name",t.Name) - //} - println("package typoe fouhnd ",p.Types.Name()) - //imports := map[string]string{} - - for _,s := range p.Syntax { - println("File ",s.Name.Name ) - //for _, decl := range s.Decls { - // if decl.Tok == token.IMPORT { - // } - } - } - //p.Fset.Iterate(func(file *token.File) bool{ - // - // // utils.Logger.Info("Output","Found file", p.ID," AND NAME ", f.Name()) - // // For each declaration in the source file... - // //for _, decl := range file.Decls { - // // addImports(imports, decl, pkgPath) - // //} - // counter ++ - // return true - //}) + processor := NewSourceProcessor(revelContainer) + sourceInfo, compileError = processor.parse() + fmt.Printf("From parsers \n%v\n%v\n",sourceInfo,compileError) + //// Combine packages for modules and app and revel + //allPackages := []string{revelContainer.ImportPath+"/app/controllers/...",model.RevelImportPath} + //for _,module := range revelContainer.ModulePathMap { + // allPackages = append(allPackages,module.ImportPath+"/app/controllers/...") //} - -compileError = errors.New("Incompleted") - println("*******************", counter) + //allPackages = []string{revelContainer.ImportPath+"/app/controllers/..."} + // + //config := &packages.Config{ + // // ode: packages.NeedSyntax | packages.NeedCompiledGoFiles, + // Mode: packages.NeedTypes | packages.NeedSyntax , + // //Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | + // // packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | + // // packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | + // // packages.NeedTypesSizes, + // + // //Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | + // // packages.NeedCompiledGoFiles | packages.NeedTypesSizes | + // // packages.NeedSyntax | packages.NeedCompiledGoFiles , + // //Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles | + // // packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // | + // // packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo, + // //packages.LoadSyntax | packages.NeedDeps, + // Dir:revelContainer.AppPath, + //} + //utils.Logger.Info("Before ","apppath", config.Dir,"paths",allPackages) + //pkgs, err := packages.Load(config, allPackages...) + //utils.Logger.Info("***Loaded packegs ", "len results", len(pkgs), "error",err) + //// Lets see if we can output all the path names + ////packages.Visit(pkgs,func(p *packages.Package) bool{ + //// println("Got pre",p.ID) + //// return true + ////}, func(p *packages.Package) { + ////}) + //counter := 0 + //for _, p := range pkgs { + // utils.Logger.Info("Errores","error",p.Errors, "id",p.ID) + // //for _,g := range p.GoFiles { + // // println("File", g) + // //} + // //for _, t:= range p.Syntax { + // // utils.Logger.Info("File","name",t.Name) + // //} + // //println("package typoe fouhnd ",p.Types.Name()) + // //imports := map[string]string{} + // + // for _,s := range p.Syntax { + // println("File ",s.Name.Name ) + // for _, decl := range s.Decls { + // genDecl, ok := decl.(*ast.GenDecl) + // if !ok { + // continue + // } + // + // if genDecl.Tok == token.IMPORT { + // for _, spec := range genDecl.Specs { + // importSpec := spec.(*ast.ImportSpec) + // fmt.Printf("*** import specification %#v\n", importSpec) + // var pkgAlias string + // if importSpec.Name != nil { + // pkgAlias = importSpec.Name.Name + // if pkgAlias == "_" { + // continue + // } + // } + // quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\"" + // fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes + // if pkgAlias == "" { + // pkgAlias = fullPath + // if index:=strings.LastIndex(pkgAlias,"/");index>0 { + // pkgAlias = pkgAlias[index+1:] + // } + // } + // //imports[pkgAlias] = fullPath + // println("Package ", pkgAlias, "fullpath", fullPath) + // } + // } + // } + // } + // } + // //p.Fset.Iterate(func(file *token.File) bool{ + // // + // // // utils.Logger.Info("Output","Found file", p.ID," AND NAME ", f.Name()) + // // // For each declaration in the source file... + // // //for _, decl := range file.Decls { + // // // addImports(imports, decl, pkgPath) + // // //} + // // counter ++ + // // return true + // //}) + ////} +if false { + compileError = errors.New("Incompleted") utils.Logger.Panic("Not implemented") +} return } +func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor { + return &SourceProcessor{revelContainer:revelContainer, log:utils.Logger.New("parser","SourceProcessor")} +} +func (s *SourceProcessor) parse() (sourceInfo *model.SourceInfo, compileError error) { + if compileError=s.addPackages();compileError!=nil { + return + } + if compileError = s.addImportMap();compileError!=nil { + return + } + return +} +func (s *SourceProcessor) addPackages() (err error) { + allPackages := []string{s.revelContainer.ImportPath+"/app/controllers/...",model.RevelImportPath} + for _,module := range s.revelContainer.ModulePathMap { + allPackages = append(allPackages,module.ImportPath+"/app/controllers/...") + } + allPackages = []string{s.revelContainer.ImportPath+"/app/controllers/..."} + + config := &packages.Config{ + // ode: packages.NeedSyntax | packages.NeedCompiledGoFiles, + Mode: packages.NeedTypes | packages.NeedSyntax , + //Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | + // packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | + // packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | + // packages.NeedTypesSizes, + + //Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | + // packages.NeedCompiledGoFiles | packages.NeedTypesSizes | + // packages.NeedSyntax | packages.NeedCompiledGoFiles , + //Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles | + // packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // | + // packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo, + //packages.LoadSyntax | packages.NeedDeps, + Dir:s.revelContainer.AppPath, + } + s.packageList, err = packages.Load(config, allPackages...) + s.log.Info("***Loaded packegs ", "len results", len(s.packageList), "error",err) + return +} +func (s *SourceProcessor) addImportMap() (err error) { + s.importMap = map[string]string{} + for _, p := range s.packageList { + if len(p.Errors)>0 { + // Generate a compile error + for _,e:=range p.Errors { + err = utils.NewCompileError("","",e) + } + + } + utils.Logger.Info("Errores","error",p.Errors, "id",p.ID) + + for _,tree := range p.Syntax { + println("File ",tree.Name.Name ) + for _, decl := range tree.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + + if genDecl.Tok == token.IMPORT { + for _, spec := range genDecl.Specs { + importSpec := spec.(*ast.ImportSpec) + fmt.Printf("*** import specification %#v\n", importSpec) + var pkgAlias string + if importSpec.Name != nil { + pkgAlias = importSpec.Name.Name + if pkgAlias == "_" { + continue + } + } + quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\"" + fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes + if pkgAlias == "" { + pkgAlias = fullPath + if index:=strings.LastIndex(pkgAlias,"/");index>0 { + pkgAlias = pkgAlias[index+1:] + } + } + s.importMap[pkgAlias] = fullPath + println("Package ", pkgAlias, "fullpath", fullPath) + } + } + } + } + } + return +} // Add imports to the map from the source dir //func addImports(imports map[string]string, decl ast.Decl, srcDir string) { diff --git a/tests/testrunner.go b/tests/testrunner.go index 6117873..a2fb9bd 100644 --- a/tests/testrunner.go +++ b/tests/testrunner.go @@ -107,7 +107,7 @@ func describeSuite(testSuite interface{}) TestSuiteDesc { } // errorSummary gets an error and returns its summary in human readable format. -func errorSummary(err *utils.Error) (message string) { +func errorSummary(err *utils.SourceError) (message string) { expectedPrefix := "(expected)" actualPrefix := "(actual)" errDesc := err.Description diff --git a/utils/build_error.go b/utils/build_error.go index 6412bbb..8655a96 100644 --- a/utils/build_error.go +++ b/utils/build_error.go @@ -3,6 +3,8 @@ package utils import ( "fmt" "github.com/revel/cmd/logger" + "strconv" + "regexp" ) type ( @@ -43,3 +45,60 @@ func NewBuildIfError(err error, message string, args ...interface{}) (b error) { func (b *BuildError) Error() string { return fmt.Sprint(b.Message, b.Args) } + +// Parse the output of the "go build" command. +// Return a detailed Error. +func NewCompileError(importPath, errorLink string, error error) *SourceError { + // Get the stack from the error + + errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`). + FindSubmatch([]byte(error.Error())) + if errorMatch == nil { + errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch([]byte(error.Error())) + + if errorMatch == nil { + Logger.Error("Failed to parse build errors", "error", error) + return &SourceError{ + SourceType: "Go code", + Title: "Go Compilation Error", + Description: "See console for build error.", + } + } + + errorMatch = append(errorMatch, errorMatch[3]) + + Logger.Error("Build errors", "errors", error) + } + + + // Read the source for the offending file. + var ( + relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go" + absFilename = relFilename + line, _ = strconv.Atoi(string(errorMatch[2])) + description = string(errorMatch[4]) + compileError = &SourceError{ + SourceType: "Go code", + Title: "Go Compilation Error", + Path: relFilename, + Description: description, + Line: line, + } + ) + + // errorLink := paths.Config.StringDefault("error.link", "") + + if errorLink != "" { + compileError.SetLink(errorLink) + } + + fileStr, err := ReadLines(absFilename) + if err != nil { + compileError.MetaError = absFilename + ": " + err.Error() + Logger.Info("Unable to readlines "+compileError.MetaError, "error", err) + return compileError + } + + compileError.SourceLines = fileStr + return compileError +} \ No newline at end of file diff --git a/utils/error.go b/utils/error.go index e03e1f3..1146d72 100644 --- a/utils/error.go +++ b/utils/error.go @@ -8,7 +8,7 @@ import ( // The error is a wrapper for the type ( - Error struct { + SourceError struct { SourceType string // The type of source that failed to build. Title, Path, Description string // Description of the error, as presented to the user. Line, Column int // Where the error was encountered. @@ -24,8 +24,8 @@ type ( } ) // Return a new error object -func NewError(source, title,path,description string) *Error { - return &Error { +func NewError(source, title,path,description string) *SourceError { + return &SourceError{ SourceType:source, Title:title, Path:path, @@ -34,7 +34,7 @@ func NewError(source, title,path,description string) *Error { } // Creates a link based on the configuration setting "errors.link" -func (e *Error) SetLink(errorLink string) { +func (e *SourceError) SetLink(errorLink string) { errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1) errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1) @@ -44,7 +44,7 @@ func (e *Error) SetLink(errorLink string) { // Error method constructs a plaintext version of the error, taking // account that fields are optionally set. Returns e.g. Compilation Error // (in views/header.html:51): expected right delim in end; got "}" -func (e *Error) Error() string { +func (e *SourceError) Error() string { if e == nil { panic("opps") } @@ -69,7 +69,7 @@ func (e *Error) Error() string { // ContextSource method returns a snippet of the source around // where the error occurred. -func (e *Error) ContextSource() []SourceLine { +func (e *SourceError) ContextSource() []SourceLine { if e.SourceLines == nil { return nil } diff --git a/utils/file.go b/utils/file.go index c070c35..6272820 100644 --- a/utils/file.go +++ b/utils/file.go @@ -150,7 +150,7 @@ func MustChmod(filename string, mode os.FileMode) { // Called if panic func PanicOnError(err error, msg string) { - if revErr, ok := err.(*Error); (ok && revErr != nil) || (!ok && err != nil) { + if revErr, ok := err.(*SourceError); (ok && revErr != nil) || (!ok && err != nil) { Logger.Panicf("Abort: %s: %s %s", msg, revErr, err) } } diff --git a/watcher/watcher.go b/watcher/watcher.go index c2d19fa..f4ee0be 100644 --- a/watcher/watcher.go +++ b/watcher/watcher.go @@ -20,7 +20,7 @@ import ( type Listener interface { // Refresh is invoked by the watcher on relevant filesystem events. // If the listener returns an error, it is served to the user on the current request. - Refresh() *utils.Error + Refresh() *utils.SourceError } // DiscerningListener allows the receiver to selectively watch files. @@ -44,7 +44,7 @@ type Watcher struct { paths *model.RevelContainer refreshTimer *time.Timer // The timer to countdown the next refresh timerMutex *sync.Mutex // A mutex to prevent concurrent updates - refreshChannel chan *utils.Error + refreshChannel chan *utils.SourceError refreshChannelCount int refreshTimerMS time.Duration // The number of milliseconds between refreshing builds } @@ -61,7 +61,7 @@ func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher { paths.Config.BoolDefault("watch", true) && paths.Config.StringDefault("watch.mode", "normal") == "eager", timerMutex: &sync.Mutex{}, - refreshChannel: make(chan *utils.Error, 10), + refreshChannel: make(chan *utils.SourceError, 10), refreshChannelCount: 0, } } @@ -178,7 +178,7 @@ func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher // Notify causes the watcher to forward any change events to listeners. // It returns the first (if any) error returned. -func (w *Watcher) Notify() *utils.Error { +func (w *Watcher) Notify() *utils.SourceError { if w.serial { // Serialize Notify() calls. w.notifyMutex.Lock() @@ -207,7 +207,7 @@ func (w *Watcher) Notify() *utils.Error { utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError) if w.forceRefresh || refresh || w.lastError == i { - var err *utils.Error + var err *utils.SourceError if w.serial { err = listener.Refresh() } else { @@ -229,7 +229,7 @@ func (w *Watcher) Notify() *utils.Error { // Build a queue for refresh notifications // this will not return until one of the queue completes -func (w *Watcher) notifyInProcess(listener Listener) (err *utils.Error) { +func (w *Watcher) notifyInProcess(listener Listener) (err *utils.SourceError) { shouldReturn := false // This code block ensures that either a timer is created // or that a process would be added the the h.refreshChannel