diff --git a/src/codegen/count.go b/src/codegen/count.go index 360ae3d..772c4e8 100644 --- a/src/codegen/count.go +++ b/src/codegen/count.go @@ -9,6 +9,7 @@ type count struct { Branch counter Data counter Loop counter + Statement counter SubBranch counter Switch counter } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index c82f7bc..c2e572f 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -65,6 +65,15 @@ func Compile(build *config.Build) (*core.Environment, error) { } } + // Check that every package has enough assertions + if build.LintAssertionDensity { + err = lintAssertionDensity(env) + + if err != nil { + return nil, err + } + } + // Now that we know which functions are alive, start parallel // assembly code generation only for the live functions. parallel(env.LiveFunctions(), func(f *core.Function) { diff --git a/src/compiler/errors.go b/src/compiler/errors.go index bdb2a53..66a3070 100644 --- a/src/compiler/errors.go +++ b/src/compiler/errors.go @@ -35,4 +35,15 @@ type UnusedImport struct { func (err *UnusedImport) Error() string { return fmt.Sprintf("Unused import '%s'", err.Package) +} + +// LowAssertionDensity error is created when a package has too few assertions. +type LowAssertionDensity struct { + Package string + Asserts int + Statements int +} + +func (err *LowAssertionDensity) Error() string { + return fmt.Sprintf("Package '%s' has %d assertions in %d statements (minimum %d)", err.Package, err.Asserts, err.Statements, err.Statements/MinimumStatements) } \ No newline at end of file diff --git a/src/compiler/errors_test.go b/src/compiler/errors_test.go index 143d338..f93b0ae 100644 --- a/src/compiler/errors_test.go +++ b/src/compiler/errors_test.go @@ -21,6 +21,7 @@ var errs = []struct { &core.UnknownIdentifier{Name: "unknown2"}, }}}, {"UnusedImport.q", &compiler.UnusedImport{Package: "run"}}, + {"LowAssertionDensity.q", &compiler.LowAssertionDensity{Package: "main", Asserts: 0, Statements: 50}}, } func TestErrors(t *testing.T) { diff --git a/src/compiler/lintAssertionDensity.go b/src/compiler/lintAssertionDensity.go new file mode 100644 index 0000000..f46b500 --- /dev/null +++ b/src/compiler/lintAssertionDensity.go @@ -0,0 +1,50 @@ +package compiler + +import ( + "sort" + + "git.urbach.dev/cli/q/src/core" +) + +const ( + // MinimumAssertionDensity is the minimum assertion percentage per package. + MinimumAssertionDensity = 2 + + // MinimumStatements is the minimum number of statements before the density check applies. + MinimumStatements = 100 / MinimumAssertionDensity +) + +// lintAssertionDensity checks that every package has enough assertions. +func lintAssertionDensity(env *core.Environment) error { + var errs []*LowAssertionDensity + + for name, pkg := range env.Packages { + asserts := 0 + statements := 0 + + for _, function := range pkg.Functions { + for variant := range function.Variants { + asserts += int(variant.Count.Assert) + statements += int(variant.Count.Statement) + } + } + + if statements < MinimumStatements { + continue + } + + if asserts < statements/MinimumStatements { + errs = append(errs, &LowAssertionDensity{Package: name, Asserts: asserts, Statements: statements}) + } + } + + if len(errs) == 0 { + return nil + } + + sort.Slice(errs, func(i int, j int) bool { + return errs[i].Package < errs[j].Package + }) + + return errs[0] +} \ No newline at end of file diff --git a/src/compiler/testdata/LowAssertionDensity.q b/src/compiler/testdata/LowAssertionDensity.q new file mode 100644 index 0000000..1a3b90c --- /dev/null +++ b/src/compiler/testdata/LowAssertionDensity.q @@ -0,0 +1,56 @@ +global { + x int64 +} + +main() { + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 + x = x + 1 +} \ No newline at end of file diff --git a/src/config/Build.go b/src/config/Build.go index 2c311ed..78665f0 100644 --- a/src/config/Build.go +++ b/src/config/Build.go @@ -2,12 +2,13 @@ package config // Build describes the parameters for the "build" command. type Build struct { - Files []string - Arch Arch - OS OS - Dry bool - Fold bool - Reorder bool - LintBinaryOps bool - LintDeadCode bool + Files []string + Arch Arch + OS OS + Dry bool + Fold bool + Reorder bool + LintAssertionDensity bool + LintBinaryOps bool + LintDeadCode bool } \ No newline at end of file diff --git a/src/config/Lint.go b/src/config/Lint.go index 9128ade..9623bc9 100644 --- a/src/config/Lint.go +++ b/src/config/Lint.go @@ -2,6 +2,7 @@ package config // Lint enables or disables the linter. func (build *Build) Lint(enabled bool) { + build.LintAssertionDensity = enabled build.LintBinaryOps = enabled build.LintDeadCode = enabled } \ No newline at end of file diff --git a/src/core/compileASTNode.go b/src/core/compileASTNode.go index d3ebf32..d9c978e 100644 --- a/src/core/compileASTNode.go +++ b/src/core/compileASTNode.go @@ -6,6 +6,8 @@ import ( // compileASTNode compiles a node in the AST. func (f *Function) compileASTNode(node ast.Node) error { + f.Count.Statement++ + switch node := node.(type) { case *ast.Assert: return f.compileAssert(node)