From af95b3eba92289f582399147212d19d82c1f4582 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Tue, 5 Apr 2022 18:11:52 +0200 Subject: [PATCH] Introduce local threshold --- make.rb | 4 +- sample/sample.d | 6 -- src/cogito/arguments.d | 55 +++++++++++++- src/cogito/meter.d | 166 ++++++++++++++++++++++++++++------------- src/main.d | 6 +- 5 files changed, 174 insertions(+), 63 deletions(-) delete mode 100644 sample/sample.d diff --git a/make.rb b/make.rb index 5467ef1..3f51c1c 100755 --- a/make.rb +++ b/make.rb @@ -89,7 +89,7 @@ def clean dub 'clean' end -(ARGV.empty? ? ['d'] : ARGV).each do |argument| +(ARGV.empty? ? ['run'] : ARGV).each do |argument| case argument when 'clean' clean @@ -97,7 +97,7 @@ def clean build argument when 'run' build 'debug' - system 'build/cogito', 'sample/sample.d' + system 'build/cogito', 'tools/sample.d' when 'test' build 'unittest' system 'build/test', '-s' diff --git a/sample/sample.d b/sample/sample.d deleted file mode 100644 index 484e43b..0000000 --- a/sample/sample.d +++ /dev/null @@ -1,6 +0,0 @@ -module a; - -bool f() -{ - return true && true; -} diff --git a/src/cogito/arguments.d b/src/cogito/arguments.d index f3788aa..092bf7d 100644 --- a/src/cogito/arguments.d +++ b/src/cogito/arguments.d @@ -9,7 +9,9 @@ import std.typecons; /// Help message. enum string help = q"HELP Usage: cogito [OPTION…] SOURCE… - --threshold NUMBER fail if the source score exceeds this threshold + --module-threshold NUMBER fail if the source score exceeds this threshold + --threshold NUMBER fail if a function score exceeds this threshold + --format ENUMERATION "silent", "flat" or "verbose" Return codes: 0 Success @@ -18,6 +20,16 @@ Usage: cogito [OPTION…] SOURCE… 3 Excess of threshold HELP"; +/** + * Possible output formats. + */ +enum OutputFormat +{ + silent, + flat, + verbose +} + /** * Arguments supported by the CLI. */ @@ -25,8 +37,12 @@ struct Arguments { /// Input files. string[] files = []; - /// Threshold. + /// Module threshold. + Nullable!uint moduleThreshold; + /// Function threshold. Nullable!uint threshold; + /// Output format. + Nullable!OutputFormat format; /// Display help message. bool help = false; } @@ -122,12 +138,20 @@ SumType!(ArgumentError, Arguments) parseArguments(string[] args) { return ArgumentResult(ArgumentError(ArgumentError.Type.unknown, args.front)); } - else if (args.front == "--threshold") + else if (args.front == "--module-threshold" || args.front == "--threshold") { + const next = args.front; args.popFront; try { - arguments.threshold = nullable(args.front.to!uint); + if (next == "--module-threshold") + { + arguments.moduleThreshold = nullable(args.front.to!uint); + } + else + { + arguments.threshold = nullable(args.front.to!uint); + } } catch (ConvException e) { @@ -137,6 +161,29 @@ SumType!(ArgumentError, Arguments) parseArguments(string[] args) return ArgumentResult(error); } } + else if (args.front == "--format") + { + args.popFront; + if (args.front == "flat") + { + arguments.format = nullable(OutputFormat.flat); + } + else if (args.front == "silent") + { + arguments.format = nullable(OutputFormat.silent); + } + else if (args.front == "verbose") + { + arguments.format = nullable(OutputFormat.verbose); + } + else + { + auto error = ArgumentError(ArgumentError.Type.wrongType, args.front); + error.expected = "silent|flat|verbose"; + + return ArgumentResult(error); + } + } else if (args.front.startsWith("--")) { return ArgumentResult(ArgumentError(ArgumentError.Type.unknown, args.front)); diff --git a/src/cogito/meter.d b/src/cogito/meter.d index 633d4e6..5002eec 100644 --- a/src/cogito/meter.d +++ b/src/cogito/meter.d @@ -8,6 +8,7 @@ import dmd.globals; import dmd.console; import dmd.root.outbuffer; +import cogito.arguments; import cogito.list; import std.algorithm; import std.conv; @@ -98,50 +99,95 @@ struct Meter this.type = type; } - private void toString(void delegate(const(char)[]) sink, const uint indentation) + /** + * Returns: $(D_KEYWORD true) if any function inside the current node + * excceds the threshold, otherwise $(D_KEYWORD false). + */ + bool isAbove(Nullable!uint threshold) { - const indentBytes = ' '.repeat(indentation * 2).array; - const nextIndentation = indentation + 1; - const nextIndentBytes = ' '.repeat(nextIndentation * 2).array; - const identifierName = this.name.toString(); - - sink(indentBytes); - sink(identifierName.empty ? "(λ)" : identifierName); - debug + if (threshold.isNull) + { + return false; + } + if (this.type == Type.callable) { - sink(":\n"); - sink(nextIndentBytes); - sink("Location: "); - sink(to!string(this.location.linnum)); - sink(":"); - sink(to!string(this.location.charnum)); - sink("\n"); - sink(nextIndentBytes); - sink("Score: "); + return this.score > threshold.get; } else { - sink(": "); + return reduce!((accum, x) => accum || x.isAbove(threshold))(false, this.inner[]); } - sink(to!string(this.score)); - sink("\n"); + } + + mixin Ruler!(); +} + +/** + * Prints the information about the given identifier. + * + * Params: + * sink = Function used to print the information. + * indentation = Indentation. + */ +void verbose(ref Meter meter, void delegate(const(char)[]) sink, + const uint indentation = 1) +{ + const indentBytes = ' '.repeat(indentation * 2).array; + const nextIndentation = indentation + 1; + const nextIndentBytes = ' '.repeat(nextIndentation * 2).array; + const identifierName = meter.name.toString(); + + sink(indentBytes); + sink(identifierName.empty ? "(λ)" : identifierName); + + sink(":\n"); + sink(nextIndentBytes); + sink("Location: "); + sink(to!string(meter.location.linnum)); + sink(":"); + sink(to!string(meter.location.charnum)); + sink("\n"); + sink(nextIndentBytes); + sink("Score: "); + + sink(meter.score.to!string); + sink("\n"); + + meter.inner[].each!(meter => verbose(meter, sink, nextIndentation)); +} - this.inner[].each!(meter => meter.toString(sink, nextIndentation)); +/** + * Prints the information about the given identifier. + * + * Params: + * sink = Function used to print the information. + * path = Identifier path. + * always = Produce output not looking at threshold. + * threshold = Function score limit. + */ +void flat(ref Meter meter, void delegate(const(char)[]) sink, + bool always, Nullable!uint threshold, + const string[] path = []) +{ + if (!always && !meter.isAbove(threshold)) + { + return; } + const identifierName = meter.name.toString(); + const anonymousName = identifierName.empty? "(λ)" : identifierName; + const nameParts = path ~ [anonymousName.idup]; - /** - * Prints the information about the given identifier. Debug build provides - * more details. - * - * Params: - * sink = Function used to print the information. - */ - void toString(void delegate(const(char)[]) sink) + if (meter.type == Meter.Type.callable) { - toString(sink, 1); + sink(" "); + sink(nameParts.join(".")); + sink(": "); + sink(meter.score.to!string); + sink("\n"); } - mixin Ruler!(); + meter.inner[].each!(meter => flat(meter, sink, + always, threshold, nameParts)); } /** @@ -166,6 +212,19 @@ struct Source this.filename = filename; } + /** + * Returns: $(D_KEYWORD true) if any function inside the current node + * excceds the threshold, otherwise $(D_KEYWORD false). + */ + bool isAbove(Nullable!uint threshold) + { + if (threshold.isNull) + { + return false; + } + return reduce!((accum, x) => accum || x.isAbove(threshold))(false, this.inner[]); + } + mixin Ruler!(); } @@ -174,36 +233,43 @@ struct Source * * Params: * source = Collected metrics and scores. - * threshold = Maximum acceptable score. + * threshold = Maximum acceptable function score. + * moduleThreshold = Maximum acceptable module score. + * format = Output format. * - * Returns: true if the score exceeds the threshold, otherwise returns false. + * Returns: $(D_KEYWORD true) if the score exceeds the threshold, otherwise + * returns $(D_KEYWORD false). */ -bool printMeter(Source source, Nullable!uint threshold) +bool printMeter(Source source, Nullable!uint threshold, + Nullable!uint moduleThreshold, Nullable!OutputFormat format) { const sourceScore = source.score; - debug - { - enum bool isDebug = true; - } - else - { - enum bool isDebug = false; - } - const bool aboveThreshold = !threshold.isNull && sourceScore > threshold.get; + const bool aboveModuleThreshold = !moduleThreshold.isNull + && sourceScore > moduleThreshold.get; + const bool aboveThreshold = aboveModuleThreshold || source.isAbove(threshold); - if (aboveThreshold || isDebug) + if ((aboveThreshold || format == nullable(OutputFormat.verbose)) + && format != nullable(OutputFormat.silent)) { - writefln("\x1b[36m%s:\x1b[0m", source.filename); + writefln("\x1b[36m%s: %s\x1b[0m", source.filename, sourceScore); - if (!source.inner.empty) + foreach (m; source.inner[]) { - foreach (m; source.inner[]) + if (format == nullable(OutputFormat.verbose)) { - m.toString(input => write(input)); + verbose(m, input => write(input)); + } + else if (aboveModuleThreshold || format == nullable(OutputFormat.flat)) + { + flat(m, input => write(input), true, threshold); + } + else + { + flat(m, input => write(input), false, threshold); } } - writefln(" \x1b[36mScore: %s\x1b[0m", sourceScore); } + return aboveThreshold; } diff --git a/src/main.d b/src/main.d index 1d0773a..cd18118 100644 --- a/src/main.d +++ b/src/main.d @@ -13,7 +13,11 @@ int accumulateResult(Arguments arguments, int accumulator, Result result) printErrors(errors); return 1; }, - (Source source) => printMeter(source, arguments.threshold) ? 3 : 0 + (Source source) { + const result = printMeter(source, arguments.threshold, + arguments.moduleThreshold, arguments.format); + return result ? 3 : 0; + } )(result); if (accumulator == 1 || nextResult == 1) {