diff --git a/sonar-project.properties b/sonar-project.properties index 05a21f5..a75bf1d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,4 +2,4 @@ sonar.projectKey=dscanner sonar.projectName=D Scanner sonar.projectVersion=1.0 sonar.sourceEncoding=UTF-8 -sonar.sources=. +sonar.sources=src diff --git a/src/analysis/helpers.d b/src/analysis/helpers.d index b5d0033..0f5ca9a 100644 --- a/src/analysis/helpers.d +++ b/src/analysis/helpers.d @@ -53,9 +53,15 @@ S after(S)(S value, S separator) void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, string file=__FILE__, size_t line=__LINE__) { import analysis.run; + import std.d.lexer : StringCache; + import std.d.parser; + + StringCache cache = StringCache(StringCache.defaultBucketCount); + ParseAllocator p = new ParseAllocator; + const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false); // Run the code and get any warnings - MessageSet rawWarnings = analyze("test", cast(ubyte[]) code, config); + MessageSet rawWarnings = analyze("test", m, config); string[] codeLines = code.split("\n"); // Get the warnings ordered by line diff --git a/src/analysis/run.d b/src/analysis/run.d index 08a615c..0639974 100644 --- a/src/analysis/run.d +++ b/src/analysis/run.d @@ -1,3 +1,8 @@ +// Copyright Brian Schott (Hackerpilot) 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + module analysis.run; import std.stdio; @@ -29,6 +34,7 @@ import analysis.length_subtraction; import analysis.builtin_property_names; import analysis.asm_style; import analysis.logic_precedence; +import analysis.stats_collector; bool first = true; @@ -65,63 +71,74 @@ void syntaxCheck(string[] fileNames) analyze(fileNames, config, false); } -// For multiple files -void analyze(string[] fileNames, StaticAnalysisConfig config, - bool staticAnalyze = true, bool report = false) +void generateReport(string[] fileNames, const StaticAnalysisConfig config) { - if (report) - { - writeln("{"); - writeln(` "issues": [`); - - } - + writeln("{"); + writeln(` "issues": [`); first = true; - + StatsCollector stats = new StatsCollector(""); + ulong lineOfCodeCount; foreach (fileName; fileNames) { File f = File(fileName); if (f.size == 0) continue; auto code = uninitializedArray!(ubyte[])(to!size_t(f.size)); f.rawRead(code); - - MessageSet results = analyze(fileName, code, config, staticAnalyze, report); - if (report) + ParseAllocator p = new ParseAllocator; + StringCache cache = StringCache(StringCache.defaultBucketCount); + const Module m = parseModule(fileName, code, p, cache, true, &lineOfCodeCount); + stats.visit(m); + MessageSet results = analyze(fileName, m, config, true); + foreach (result; results[]) { - foreach (result; results[]) - { - writeJSON(result.key, result.fileName, result.line, result.column, result.message); - } - } - else if (results !is null) - { - foreach (result; results[]) - writefln("%s(%d:%d)[warn]: %s", result.fileName, result.line, - result.column, result.message); + writeJSON(result.key, result.fileName, result.line, result.column, result.message); } } + writeln(); + writeln(" ],"); + writefln(` "interfaceCount": %d,`, stats.interfaceCount); + writefln(` "classCount": %d,`, stats.classCount); + writefln(` "functionCount": %d,`, stats.functionCount); + writefln(` "templateCount": %d,`, stats.templateCount); + writefln(` "structCount": %d,`, stats.structCount); + writefln(` "statementCount": %d,`, stats.statementCount); + writefln(` "lineOfCodeCount": %d,`, lineOfCodeCount); + writefln(` "undocumentedPublicSymbols": %d`, stats.undocumentedPublicSymbols); + writeln("}"); +} - if (report) +// For multiple files +void analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true) +{ + foreach (fileName; fileNames) { - writeln(); - writeln(" ]"); - writeln("}"); + File f = File(fileName); + if (f.size == 0) continue; + auto code = uninitializedArray!(ubyte[])(to!size_t(f.size)); + f.rawRead(code); + ParseAllocator p = new ParseAllocator; + StringCache cache = StringCache(StringCache.defaultBucketCount); + const Module m = parseModule(fileName, code, p, cache, false); + MessageSet results = analyze(fileName, m, config, staticAnalyze); + if (results is null) + continue; + foreach (result; results[]) + writefln("%s(%d:%d)[warn]: %s", result.fileName, result.line, + result.column, result.message); } } -// For a string -MessageSet analyze(string fileName, ubyte[] code, const StaticAnalysisConfig analysisConfig, - bool staticAnalyze = true, bool report = false) +const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p, + ref StringCache cache, bool report, ulong* linesOfCode = null) { - import std.parallelism; - + import stats : isLineOfCode; auto lexer = byToken(code); LexerConfig config; config.fileName = fileName; config.stringBehavior = StringBehavior.source; - StringCache cache = StringCache(StringCache.defaultBucketCount); const(Token)[] tokens = getTokensForParser(code, config, &cache); - + if (linesOfCode !is null) + (*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens); foreach (message; lexer.messages) { if (report) @@ -131,9 +148,14 @@ MessageSet analyze(string fileName, ubyte[] code, const StaticAnalysisConfig ana messageFunction(fileName, message.line, message.column, message.message, message.isError); } + return std.d.parser.parseModule(tokens, fileName, p, + report ? &messageFunctionJSON : &messageFunction); +} - ParseAllocator p = new ParseAllocator; - Module m = parseModule(tokens, fileName, p, report ? &messageFunctionJSON : &messageFunction); +MessageSet analyze(string fileName, const Module m, + const StaticAnalysisConfig analysisConfig, bool staticAnalyze = true) +{ + import std.parallelism; if (!staticAnalyze) return null; @@ -167,7 +189,6 @@ MessageSet analyze(string fileName, ubyte[] code, const StaticAnalysisConfig ana foreach (check; checks) foreach (message; check.messages) set.insert(message); - p.deallocateAll(); return set; } diff --git a/src/analysis/stats_collector.d b/src/analysis/stats_collector.d new file mode 100644 index 0000000..c3cc6fd --- /dev/null +++ b/src/analysis/stats_collector.d @@ -0,0 +1,65 @@ +// Copyright Brian Schott (Hackerpilot) 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +module analysis.stats_collector; + +import std.d.ast; +import std.d.lexer; +import analysis.base; + +class StatsCollector : BaseAnalyzer +{ + alias visit = ASTVisitor.visit; + + this(string fileName) + { + super(fileName); + } + + override void visit(const Statement statement) + { + statementCount++; + statement.accept(this); + } + + override void visit(const ClassDeclaration classDeclaration) + { + classCount++; + classDeclaration.accept(this); + } + + override void visit(const InterfaceDeclaration interfaceDeclaration) + { + interfaceCount++; + interfaceDeclaration.accept(this); + } + + override void visit(const FunctionDeclaration functionDeclaration) + { + functionCount++; + functionDeclaration.accept(this); + } + + override void visit(const StructDeclaration structDeclaration) + { + structCount++; + structDeclaration.accept(this); + } + + override void visit(const TemplateDeclaration templateDeclaration) + { + templateCount++; + templateDeclaration.accept(this); + } + + uint interfaceCount; + uint classCount; + uint functionCount; + uint templateCount; + uint structCount; + uint statementCount; + uint lineOfCodeCount; + uint undocumentedPublicSymbols; +} diff --git a/src/analysis/style.d b/src/analysis/style.d index 6f9a088..f09d728 100644 --- a/src/analysis/style.d +++ b/src/analysis/style.d @@ -52,7 +52,7 @@ class StyleChecker : BaseAnalyzer void checkLowercaseName(string type, ref const Token name) { - if (name.text.matchFirst(varFunNameRegex).length == 0) + if (name.text.length > 0 && name.text.matchFirst(varFunNameRegex).length == 0) addErrorMessage(name.line, name.column, KEY, type ~ " name '" ~ name.text ~ "' does not match style guidelines."); } diff --git a/src/main.d b/src/main.d index 839bc79..7723c82 100644 --- a/src/main.d +++ b/src/main.d @@ -160,7 +160,10 @@ int run(string[] args) string s = configLocation is null ? getConfigurationLocation() : configLocation; if (s.exists()) readINIFile(config, s); - analyze(expandArgs(args), config, true, report); + if (report) + generateReport(expandArgs(args), config); + else + analyze(expandArgs(args), config, true); } else if (syntaxCheck) {