-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor engine * update README
Showing
8 changed files
with
341 additions
and
252 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Package internal provides the core functionality for a Go-compatible linting tool. | ||
// | ||
// This package implements a flexible and extensible linting engine that can be used | ||
// to analyze Go and Gno code for potential issues, style violations, and areas of improvement. | ||
// It is designed to be easily extendable with custom lint rules while providing a set of | ||
// default rules out of the box. | ||
// | ||
// Key components: | ||
// | ||
// Engine: The main linting engine that coordinates the linting process. | ||
// It manages a collection of lint rules and applies them to the given source files. | ||
// | ||
// LintRule: An interface that defines the contract for all lint rules. | ||
// Each lint rule must implement the Check method to analyze the code and return issues. | ||
// | ||
// Issue: Represents a single lint issue found in the code, including its location and description. | ||
// | ||
// SymbolTable: A data structure that keeps track of defined symbols across the codebase, | ||
// helping to reduce false positives in certain lint rules. | ||
// | ||
// SourceCode: A simple structure to represent the content of a source file as a collection of lines. | ||
// | ||
// The package also includes several helper functions for file operations, running external tools, | ||
// and managing temporary files during the linting process. | ||
// | ||
// Usage: | ||
// | ||
// engine, err := internal.NewEngine("path/to/root/dir") | ||
// if err != nil { | ||
// // handle error | ||
// } | ||
// | ||
// // Optionally add custom rules | ||
// engine.AddRule(myCustomRule) | ||
// | ||
// issues, err := engine.Run("path/to/file.go") | ||
// if err != nil { | ||
// // handle error | ||
// } | ||
// | ||
// // Process the found issues | ||
// for _, issue := range issues { | ||
// fmt.Printf("Found issue: %s at %s\n", issue.Message, issue.Start) | ||
// } | ||
// | ||
// This package is intended for internal use within the linting tool and should not be | ||
// imported by external packages. | ||
package internal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package internal | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// Engine manages the linting process. | ||
type Engine struct { | ||
SymbolTable *SymbolTable | ||
rules []LintRule | ||
} | ||
|
||
// NewEngine creates a new lint engine. | ||
func NewEngine(rootDir string) (*Engine, error) { | ||
st, err := BuildSymbolTable(rootDir) | ||
if err != nil { | ||
return nil, fmt.Errorf("error building symbol table: %w", err) | ||
} | ||
|
||
engine := &Engine{SymbolTable: st} | ||
engine.registerDefaultRules() | ||
|
||
return engine, nil | ||
} | ||
|
||
// registerDefaultRules adds the default set of lint rules to the engine. | ||
func (e *Engine) registerDefaultRules() { | ||
e.rules = append(e.rules, | ||
&GolangciLintRule{}, | ||
&UnnecessaryElseRule{}, | ||
&UnusedFunctionRule{}, | ||
) | ||
} | ||
|
||
// AddRule allows adding custom lint rules to the engine. | ||
func (e *Engine) AddRule(rule LintRule) { | ||
e.rules = append(e.rules, rule) | ||
} | ||
|
||
// Run applies all lint rules to the given file and returns a slice of Issues. | ||
func (e *Engine) Run(filename string) ([]Issue, error) { | ||
tempFile, err := e.prepareFile(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer e.cleanupTemp(tempFile) | ||
|
||
var allIssues []Issue | ||
for _, rule := range e.rules { | ||
issues, err := rule.Check(tempFile) | ||
if err != nil { | ||
return nil, fmt.Errorf("error running lint rule: %w", err) | ||
} | ||
allIssues = append(allIssues, issues...) | ||
} | ||
|
||
filtered := e.filterUndefinedIssues(allIssues) | ||
|
||
// map issues back to .gno file if necessary | ||
if strings.HasSuffix(filename, ".gno") { | ||
for i := range filtered { | ||
filtered[i].Filename = filename | ||
} | ||
} | ||
|
||
return filtered, nil | ||
} | ||
|
||
func (e *Engine) prepareFile(filename string) (string, error) { | ||
if strings.HasSuffix(filename, "gno") { | ||
return createTempGoFile(filename) | ||
} | ||
return filename, nil | ||
} | ||
|
||
func (e *Engine) cleanupTemp(temp string) { | ||
if temp != "" && strings.HasPrefix(filepath.Base(temp), "temp_") { | ||
_ = os.Remove(temp) | ||
} | ||
} | ||
|
||
func (e *Engine) filterUndefinedIssues(issues []Issue) []Issue { | ||
var filtered []Issue | ||
for _, issue := range issues { | ||
if issue.Rule == "typecheck" && strings.Contains(issue.Message, "undefined:") { | ||
symbol := strings.TrimSpace(strings.TrimPrefix(issue.Message, "undefined:")) | ||
if e.SymbolTable.IsDefined(symbol) { | ||
// ignore issues if the symbol is defined in the symbol table | ||
continue | ||
} | ||
} | ||
filtered = append(filtered, issue) | ||
} | ||
return filtered | ||
} | ||
|
||
func createTempGoFile(gnoFile string) (string, error) { | ||
content, err := os.ReadFile(gnoFile) | ||
if err != nil { | ||
return "", fmt.Errorf("error reading .gno file: %w", err) | ||
} | ||
|
||
dir := filepath.Dir(gnoFile) | ||
tempFile, err := os.CreateTemp(dir, "temp_*.go") | ||
if err != nil { | ||
return "", fmt.Errorf("error creating temp file: %w", err) | ||
} | ||
|
||
_, err = tempFile.Write(content) | ||
if err != nil { | ||
os.Remove(tempFile.Name()) | ||
return "", fmt.Errorf("error writing to temp file: %w", err) | ||
} | ||
|
||
err = tempFile.Close() | ||
if err != nil { | ||
os.Remove(tempFile.Name()) | ||
return "", fmt.Errorf("error closing temp file: %w", err) | ||
} | ||
|
||
return tempFile.Name(), nil | ||
} | ||
|
||
// ReadSourceFile reads the content of a file and returns it as a `SourceCode` struct. | ||
func ReadSourceCode(filename string) (*SourceCode, error) { | ||
content, err := os.ReadFile(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
lines := strings.Split(string(content), "\n") | ||
return &SourceCode{Lines: lines}, nil | ||
} |
Oops, something went wrong.