From b3fb931428d92f9d985e2f1f1f35a85466e4ae4d Mon Sep 17 00:00:00 2001 From: Kamil Chmielewski <45183584+ChmielewskiKamil@users.noreply.github.com> Date: Sat, 14 Dec 2024 17:09:33 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Centralize=20analysis=20da?= =?UTF-8?q?ta=20in=20Analyzer=20struct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyzer/analyzer.go | 79 ++++++++++++++++++++++++++++++--------- analyzer/analyzer_test.go | 31 +++++---------- main.go | 20 ++-------- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 136b675..67b3da7 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -1,8 +1,10 @@ package analyzer import ( + "fmt" "solbot/analyzer/screamingsnakeconst" "solbot/ast" + "solbot/parser" "solbot/reporter" "solbot/symbols" "solbot/token" @@ -18,47 +20,90 @@ func GetAllDetectors() *[]Detector { } } -func AnalyzeFile(file *ast.File) []reporter.Finding { - globalEnv := symbols.NewEnvironment() +type Analyzer struct { + // All findings found during the analysis. + findings []reporter.Finding + + currentFile *ast.File // The currently analysed file; returned from parser.ParseFile. + currentFileEnv *symbols.Environment // The environment of the currently analyred file. + parser *parser.Parser // Parser is used to parse newly encountered files. +} + +func (a *Analyzer) Init(filePathToAnalyze string) error { + a.parser = &parser.Parser{} + + sourceFile, err := token.NewSourceFile(filePathToAnalyze, "") + if err != nil { + return err + } + + a.parser.Init(sourceFile) + file := a.parser.ParseFile() + if file == nil { + return fmt.Errorf("Could not parse file. Check parses errors.") + } + + a.currentFile = file + return nil +} + +func (a *Analyzer) AnalyzeCurrentFile() { + a.AnalyzeFile(a.currentFile) +} + +func (a *Analyzer) AnalyzeFile(file *ast.File) { + fileEnv := symbols.NewEnvironment() + a.currentFileEnv = fileEnv // Phase 1: Get all declarations first to avoid unknown symbol errors if // the symbols are defined later in a file or somewhere else. - discoverSymbols(file, globalEnv, nil) + a.discoverSymbols(file, fileEnv) // Phase 2: Populate all definitions and references. Resolve overrides and // inheritance structure. - resolveDefinitions(file, globalEnv) + a.resolveDefinitions(file, fileEnv) // Phase 3: The environment is populated with context at this point. // Diagnose issues with the code. Run detectors. - findings := detectIssues(file, globalEnv) + findings := a.detectIssues(file, fileEnv) - return findings + for _, finding := range findings { + a.findings = append(a.findings, finding) + } +} + +func (a *Analyzer) GetFindings() []reporter.Finding { + return a.findings +} + +func (a *Analyzer) GetCurrentFileEnv() *symbols.Environment { + return a.currentFileEnv +} + +func (a *Analyzer) GetParserErrors() parser.ErrorList { + return a.parser.Errors() } //////////////////////////////////////////////////////////////////// // PHASE 1 // //////////////////////////////////////////////////////////////////// -func discoverSymbols(node ast.Node, - env *symbols.Environment, src *token.SourceFile) { +func (a *Analyzer) discoverSymbols(node ast.Node, env *symbols.Environment) { switch n := node.(type) { case *ast.File: for _, decl := range n.Declarations { - discoverSymbols(decl, env, n.SourceFile) + a.discoverSymbols(decl, env) } case *ast.FunctionDeclaration: - populateFunctionDeclaration(n, env, src) + a.populateFunctionDeclaration(n, env) } } -func populateFunctionDeclaration( - node *ast.FunctionDeclaration, - env *symbols.Environment, - src *token.SourceFile) { +func (a *Analyzer) populateFunctionDeclaration( + node *ast.FunctionDeclaration, env *symbols.Environment) { baseSymbol := symbols.BaseSymbol{ Name: node.Name.Value, - SourceFile: src, + SourceFile: a.currentFile.SourceFile, Offset: node.Pos, AstNode: node, } @@ -74,9 +119,9 @@ func populateFunctionDeclaration( // PHASE 2 // //////////////////////////////////////////////////////////////////// -func resolveDefinitions(node ast.Node, env *symbols.Environment) {} +func (a *Analyzer) resolveDefinitions(node ast.Node, env *symbols.Environment) {} -func detectIssues(node ast.Node, env *symbols.Environment) []reporter.Finding { +func (a *Analyzer) detectIssues(node ast.Node, env *symbols.Environment) []reporter.Finding { var findings []reporter.Finding // detectors := *GetAllDetectors() diff --git a/analyzer/analyzer_test.go b/analyzer/analyzer_test.go index 64eb6c9..56f7778 100644 --- a/analyzer/analyzer_test.go +++ b/analyzer/analyzer_test.go @@ -1,31 +1,16 @@ package analyzer import ( - "solbot/parser" - "solbot/symbols" - "solbot/token" "testing" ) func Test_PopulateSymbols_GetSymbolsByName(t *testing.T) { testContractPath := "testdata/foundry/src/002_SimpleCounter.sol" - p := parser.Parser{} - sourceFile, err := token.NewSourceFile(testContractPath, "") - if err != nil { - t.Fatalf("Could not create source file: %s", err) - } - - p.Init(sourceFile) - - file := p.ParseFile() - checkParserErrors(t, &p) + analyzer := Analyzer{} + analyzer.Init(testContractPath) + checkParserErrors(t, &analyzer) - if file == nil { - t.Fatalf("ParseFile() returned nil") - } - - env := symbols.NewEnvironment() - discoverSymbols(file, env, nil) + analyzer.AnalyzeCurrentFile() tests := []struct { expectedSymbolName string @@ -36,6 +21,10 @@ func Test_PopulateSymbols_GetSymbolsByName(t *testing.T) { } for _, tt := range tests { + env := analyzer.GetCurrentFileEnv() + if env == nil { + t.Fatalf("Currently analyzed file's env is nil.") + } _, found := env.Get(tt.expectedSymbolName) if !found { t.Fatalf("Symbol: '%s' not found.", tt.expectedSymbolName) @@ -43,8 +32,8 @@ func Test_PopulateSymbols_GetSymbolsByName(t *testing.T) { } } -func checkParserErrors(t *testing.T, p *parser.Parser) { - errors := p.Errors() +func checkParserErrors(t *testing.T, a *Analyzer) { + errors := a.GetParserErrors() if len(errors) == 0 { return } diff --git a/main.go b/main.go index ab62075..7a0f85e 100644 --- a/main.go +++ b/main.go @@ -12,9 +12,7 @@ import ( "solbot/lsp" "solbot/lsp/analysis" "solbot/lsp/rpc" - "solbot/parser" "solbot/reporter" - "solbot/token" ) func main() { @@ -64,24 +62,14 @@ func startLanguageServer() { func startAnalyzer(filePath string) error { println("Solbot starts") - p := parser.Parser{} - handle, err := token.NewSourceFile(filePath, "") - if err != nil { - return err - } - p.Init(handle) - p.ToggleTracing() - - file := p.ParseFile() + a := analyzer.Analyzer{} + a.Init(filePath) + a.AnalyzeCurrentFile() println("Solbot is analyzing your file...") - findings := analyzer.AnalyzeFile(file) - for _, finding := range findings { - finding.CalculatePositions(handle) - } - reporter.GenerateReport(findings, "solbot.md") + reporter.GenerateReport(a.GetFindings(), "solbot.md") return nil }