Skip to content

Commit

Permalink
Merge pull request #49 from Halleck45/feat_diff
Browse files Browse the repository at this point in the history
feat: Way to compare code with sha1, branch or tag
  • Loading branch information
Halleck45 committed Apr 26, 2024
2 parents 67fb9e5 + 410f143 commit 17f1478
Show file tree
Hide file tree
Showing 28 changed files with 736 additions and 250 deletions.
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,46 @@

**AST Metrics is a blazing-fast static code analyzer that works across programming languages..** It empowers you to gain deep insights into your code structure, identify potential problems early on, and improve code quality. Leveraging the efficiency of Go, AST Metrics delivers exceptional performance for large codebases.

[Twitter](https://twitter.com/Halleck45) | [Contributing](.github/CONTRIBUTING.md)
[Twitter](https://twitter.com/Halleck45) | [Contributing](.github/CONTRIBUTING.md) |
[Getting started](https://halleck45.github.io/ast-metrics/getting-started/)

## Quick start

Open your terminal and run the following command:

```bash
```console
ast-metrics analyze --report-html=<directory> /path/to/your/code
```

## Installation

AST Metrics is a standalone package. It does not require any other software to be installed.

**Follow the [installation instructions](https://halleck45.github.io/ast-metrics/getting-started/install/)**, or download directly the correct binary for your platform:
```console
curl -s https://raw.githubusercontent.com/Halleck45/ast-metrics/main/scripts/download.sh|bash
```

or follow the detailled [installation instructions](https://halleck45.github.io/ast-metrics/getting-started/install/).

> [!IMPORTANT]
> Please always read any script found on the internet before running it, and never use privileged access to run it.
## Features

| Platform | i386 | amd64 | arm64 |
| -------- | ------ | ------ | ------ |
| ![](./docs/emoji-tux.png) Linux | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Linux_i386) | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Linux_x86_64) | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Linux_arm64)
| ![](./docs/emoji-apple.png) macOS | - | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Darwin_x86_64) | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Darwin_arm64)
| ![](./docs/emoji-windows.png) Windows | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Windows_i386.exe) | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Windows_x86_64.exe) | [Download](https://github.com/Halleck45/ast-metrics/releases/download/v0.0.12-alpha/ast-metrics_Windows_arm64.exe)
+ **Designed for CI/CD**. You can integrate it into your pipeline to check that your code meets your quality standards.
+ **Fast and efficient**.
+ Provides simple and detailed reports.
+ **Code analysis**: *cyclomatic complexity, maintainability, size...*
+ **Coupling analysis**: *instability, afferent coupling...*
+ **Activity analysis**: *number of commits, bus factor...*

[Read more in the documentation](https://halleck45.github.io/ast-metrics/)

## Contributing

AST Metrics is experimental and actively developed. We welcome contributions.

**Feel free to [open a discussion](https://github.com/Halleck45/ast-metrics/discussions)**. We love suggestions, ideas, bug reports, and other contributions.
**Feel free to [open a discussion](https://github.com/Halleck45/ast-metrics/discussions)**. We love suggestions, ideas, bug reports, and other contributions.

If you want to contribute code, please read the [contributing guidelines](.github/CONTRIBUTING.md) to get started.

Expand Down
11 changes: 11 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ func main() {
Usage: "Load configuration from file",
Category: "Configuration",
},
// Diff mode (comparaison between current branch and another one or commit)
&cli.StringFlag{
Name: "compare-with",
Usage: "Compare with another Git branch or commit",
Category: "Global options",
},
},
Action: func(cCtx *cli.Context) error {

Expand Down Expand Up @@ -168,6 +174,11 @@ func main() {
configuration.Reports.Markdown = cCtx.String("report-markdown")
}

// Compare with
if cCtx.String("compare-with") != "" {
configuration.CompareWith = cCtx.String("compare-with")
}

// Run command
command := Command.NewAnalyzeCommand(configuration, outWriter, runners, isInteractive)

Expand Down
115 changes: 87 additions & 28 deletions src/Analyzer/Aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/halleck45/ast-metrics/src/Engine"
pb "github.com/halleck45/ast-metrics/src/NodeType"
"github.com/halleck45/ast-metrics/src/Scm"
)

type ProjectAggregated struct {
Expand All @@ -15,10 +16,12 @@ type ProjectAggregated struct {
ByProgrammingLanguage map[string]Aggregated
ErroredFiles []*pb.File
Evaluation *EvaluationResult
Comparaison *ProjectComparaison
}

type Aggregated struct {
ConcernedFiles []*pb.File
Comparaison *Comparaison
// hashmap of classes, just with the qualified name, used for afferent coupling calculation
ClassesAfferentCoupling map[string]int
NbFiles int
Expand Down Expand Up @@ -64,11 +67,19 @@ type Aggregated struct {
PackageRelations map[string]map[string]int // counter of dependencies. Ex: A -> B -> 2
}

type ProjectComparaison struct {
ByFile Comparaison
ByClass Comparaison
Combined Comparaison
ByProgrammingLanguage map[string]Comparaison
}

type Aggregator struct {
files []*pb.File
projectAggregated ProjectAggregated
analyzers []AggregateAnalyzer
gitSummaries []ResultOfGitAnalysis
ComparaidFiles []*pb.File
}

type TopCommitter struct {
Expand All @@ -83,13 +94,13 @@ type ResultOfGitAnalysis struct {
CountCommiters int
CountCommitsForLanguage int
CountCommitsIgnored int
GitRepository Scm.GitRepository
}

func NewAggregator(files []*pb.File, gitSummaries []ResultOfGitAnalysis) *Aggregator {
return &Aggregator{
files: files,
projectAggregated: ProjectAggregated{},
gitSummaries: gitSummaries,
files: files,
gitSummaries: gitSummaries,
}
}

Expand Down Expand Up @@ -142,66 +153,110 @@ func newAggregated() Aggregated {

func (r *Aggregator) Aggregates() ProjectAggregated {

// We create a new aggregated object for each type of aggregation
r.projectAggregated = r.executeAggregationOnFiles(r.files)

// Do the same for the comparaison files (if needed)
if r.ComparaidFiles != nil {
comparaidAggregated := r.executeAggregationOnFiles(r.ComparaidFiles)

// Compare
comparaison := ProjectComparaison{}
comparator := NewComparator()
comparaison.Combined = comparator.Compare(r.projectAggregated.Combined, comparaidAggregated.Combined)
r.projectAggregated.Combined.Comparaison = &comparaison.Combined

comparaison.ByClass = comparator.Compare(r.projectAggregated.ByClass, comparaidAggregated.ByClass)
r.projectAggregated.ByClass.Comparaison = &comparaison.ByClass

comparaison.ByFile = comparator.Compare(r.projectAggregated.ByFile, comparaidAggregated.ByFile)
r.projectAggregated.ByFile.Comparaison = &comparaison.ByFile

// By language
comparaison.ByProgrammingLanguage = make(map[string]Comparaison)
for lng, byLanguage := range r.projectAggregated.ByProgrammingLanguage {
if _, ok := comparaidAggregated.ByProgrammingLanguage[lng]; !ok {
continue
}
c := comparator.Compare(byLanguage, comparaidAggregated.ByProgrammingLanguage[lng])
comparaison.ByProgrammingLanguage[lng] = c

// assign to the original object (slow, but otherwise we need to change the whole structure ByProgrammingLanguage map)
// @see https://stackoverflow.com/questions/42605337/cannot-assign-to-struct-field-in-a-map
// Feel free to change this
entry := r.projectAggregated.ByProgrammingLanguage[lng]
entry.Comparaison = &c
r.projectAggregated.ByProgrammingLanguage[lng] = entry
}
r.projectAggregated.Comparaison = &comparaison
}

return r.projectAggregated
}

func (r *Aggregator) executeAggregationOnFiles(files []*pb.File) ProjectAggregated {

// We create a new aggregated object for each type of aggregation
// ByFile, ByClass, Combined
r.projectAggregated.ByFile = newAggregated()
r.projectAggregated.ByClass = newAggregated()
r.projectAggregated.Combined = newAggregated()
projectAggregated := ProjectAggregated{}
projectAggregated.ByFile = newAggregated()
projectAggregated.ByClass = newAggregated()
projectAggregated.Combined = newAggregated()

// Count files
r.projectAggregated.ByClass.NbFiles = len(r.files)
r.projectAggregated.ByFile.NbFiles = len(r.files)
r.projectAggregated.Combined.NbFiles = len(r.files)
projectAggregated.ByClass.NbFiles = len(files)
projectAggregated.ByFile.NbFiles = len(files)
projectAggregated.Combined.NbFiles = len(files)

// Prepare errors
r.projectAggregated.ErroredFiles = make([]*pb.File, 0)
projectAggregated.ErroredFiles = make([]*pb.File, 0)

for _, file := range r.files {
for _, file := range files {

// Files with errors
if file.Errors != nil && len(file.Errors) > 0 {
r.projectAggregated.ErroredFiles = append(r.projectAggregated.ErroredFiles, file)
projectAggregated.ErroredFiles = append(projectAggregated.ErroredFiles, file)
}

if file.Stmts == nil {
continue
}

// By language
if r.projectAggregated.ByProgrammingLanguage == nil {
r.projectAggregated.ByProgrammingLanguage = make(map[string]Aggregated)
if projectAggregated.ByProgrammingLanguage == nil {
projectAggregated.ByProgrammingLanguage = make(map[string]Aggregated)
}
if _, ok := r.projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage]; !ok {
r.projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage] = newAggregated()
if _, ok := projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage]; !ok {
projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage] = newAggregated()

}
byLanguage := r.projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage]
byLanguage := projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage]
byLanguage.NbFiles++

// Make calculations: sums of metrics, etc.
r.calculateSums(file, &r.projectAggregated.ByFile)
r.calculateSums(file, &r.projectAggregated.ByClass)
r.calculateSums(file, &r.projectAggregated.Combined)
r.calculateSums(file, &projectAggregated.ByFile)
r.calculateSums(file, &projectAggregated.ByClass)
r.calculateSums(file, &projectAggregated.Combined)
r.calculateSums(file, &byLanguage)
r.projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage] = byLanguage
projectAggregated.ByProgrammingLanguage[file.ProgrammingLanguage] = byLanguage
}

// Consolidate averages
r.consolidate(&r.projectAggregated.ByFile)
r.consolidate(&r.projectAggregated.ByClass)
r.consolidate(&r.projectAggregated.Combined)
r.consolidate(&projectAggregated.ByFile)
r.consolidate(&projectAggregated.ByClass)
r.consolidate(&projectAggregated.Combined)

// by language
for lng, byLanguage := range r.projectAggregated.ByProgrammingLanguage {
for lng, byLanguage := range projectAggregated.ByProgrammingLanguage {
r.consolidate(&byLanguage)
r.projectAggregated.ByProgrammingLanguage[lng] = byLanguage
projectAggregated.ByProgrammingLanguage[lng] = byLanguage
}

// Risks
riskAnalyzer := NewRiskAnalyzer()
riskAnalyzer.Analyze(r.projectAggregated)
riskAnalyzer.Analyze(projectAggregated)

return r.projectAggregated
return projectAggregated
}

// Consolidate the aggregated data
Expand Down Expand Up @@ -408,6 +463,10 @@ func (r *Aggregator) WithAggregateAnalyzer(analyzer AggregateAnalyzer) {
r.analyzers = append(r.analyzers, analyzer)
}

func (r *Aggregator) WithComparaison(allResultsCloned []*pb.File) {
r.ComparaidFiles = allResultsCloned
}

// Calculate the aggregated data
func (r *Aggregator) calculateSums(file *pb.File, specificAggregation *Aggregated) {
classes := Engine.GetClassesInFile(file)
Expand Down
5 changes: 3 additions & 2 deletions src/Analyzer/AstAnalyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import (
Component "github.com/halleck45/ast-metrics/src/Analyzer/Component"
Volume "github.com/halleck45/ast-metrics/src/Analyzer/Volume"
pb "github.com/halleck45/ast-metrics/src/NodeType"
"github.com/halleck45/ast-metrics/src/Storage"
"github.com/pterm/pterm"
"github.com/yargevad/filepathx"
"google.golang.org/protobuf/proto"
)

func Start(workdir string, progressbar *pterm.SpinnerPrinter) []*pb.File {
func Start(workdir *Storage.Workdir, progressbar *pterm.SpinnerPrinter) []*pb.File {

// List all ASTs files (*.bin) in the workdir
astFiles, err := filepathx.Glob(workdir + "/**/*.bin")
astFiles, err := filepathx.Glob(workdir.Path() + "/**/*.bin")
if err != nil {
panic(err)
}
Expand Down
13 changes: 9 additions & 4 deletions src/Analyzer/AstAnalyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/halleck45/ast-metrics/src/Engine"
pb "github.com/halleck45/ast-metrics/src/NodeType"
"github.com/halleck45/ast-metrics/src/Storage"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -65,17 +66,21 @@ func TestAnalyzerStart(t *testing.T) {
}

// Dump protobuf object to destination
workdir := t.TempDir()
binPath := workdir + string(os.PathSeparator) + "tmp.bin"
Engine.DumpProtobuf(&protoFile, binPath)
storage := Storage.NewWithName("test")
storage.Ensure()
binPath := storage.AstDirectory() + string(os.PathSeparator) + "tmp.bin"
err := Engine.DumpProtobuf(&protoFile, binPath)
if err != nil {
t.Error("Error dumping protobuf object", err)
}

// Ensure file exists
if _, err := os.Stat(binPath); err != nil {
t.Error("File not found", binPath)
}

// Start analysis
parsedFiles := Start(workdir, nil)
parsedFiles := Start(storage, nil)

// Now first parsed file should be the same as the one we dumped, + analysis
assert.Equal(t, "Go", parsedFiles[0].ProgrammingLanguage)
Expand Down

0 comments on commit 17f1478

Please sign in to comment.