diff --git a/README.md b/README.md index 553c0ab0..1027aab6 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ Note that the "--skipTests" option is the equivalent of changing each * Allman brace style * Redundant visibility attributes * Public declarations without a documented unittest. By default disabled. +* Trailing whitespace #### Wishlist diff --git a/src/analysis/config.d b/src/analysis/config.d index 1966e985..8932fa0f 100644 --- a/src/analysis/config.d +++ b/src/analysis/config.d @@ -188,6 +188,9 @@ struct StaticAnalysisConfig @INI("Check public declarations without a documented unittest") string has_public_example = Check.disabled; + + @INI("Check for trailing whitespace") + string trailing_whitespace = Check.disabled; @INI("Module-specific filters") ModuleFilters filters; diff --git a/src/analysis/helpers.d b/src/analysis/helpers.d index 390c7b2a..b5ae97d2 100644 --- a/src/analysis/helpers.d +++ b/src/analysis/helpers.d @@ -41,11 +41,9 @@ S after(S)(S value, S separator) if (isSomeString!S) } /** - * This assert function will analyze the passed in code, get the warnings, - * and make sure they match the warnings in the comments. Warnings are - * marked like so: // [warn]: Failed to do somethings. - */ -void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, +* Get analzer warnings for the given code +*/ +MessageSet getAnalyzerWarnings(string code, const StaticAnalysisConfig config, string file = __FILE__, size_t line = __LINE__) { import analysis.run : parseModule; @@ -55,12 +53,26 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, StringCache cache = StringCache(StringCache.defaultBucketCount); RollbackAllocator r; const(Token)[] tokens; - const(Module) m = parseModule(file, cast(ubyte[]) code, &r, cache, false, tokens); + const(Module) m = parseModule(file, cast(ubyte[]) code, &r, cache, false, tokens, + null, null, null, &config); auto moduleCache = ModuleCache(new CAllocatorImpl!Mallocator); // Run the code and get any warnings - MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens); + return analyze("test", m, config, moduleCache, tokens); +} + +/** + * This assert function will analyze the passed in code, get the warnings, + * and make sure they match the warnings in the comments. Warnings are + * marked like so: // [warn]: Failed to do somethings. + */ +void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, + string file = __FILE__, size_t line = __LINE__) +{ + import std.ascii : newline; + + MessageSet rawWarnings = getAnalyzerWarnings(code, config, file, line); string[] codeLines = code.split(newline); // Get the warnings ordered by line diff --git a/src/analysis/run.d b/src/analysis/run.d index a8244fb7..85522742 100644 --- a/src/analysis/run.d +++ b/src/analysis/run.d @@ -70,6 +70,7 @@ import analysis.useless_initializer; import analysis.allman; import analysis.redundant_attributes; import analysis.has_public_example; +import analysis.trailing_whitespace; import dsymbol.string_interning : internString; import dsymbol.scope_; @@ -174,7 +175,7 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, uint warningCount; const(Token)[] tokens; const Module m = parseModule(fileName, code, &r, cache, false, tokens, - null, &errorCount, &warningCount); + null, &errorCount, &warningCount, &config); assert(m); if (errorCount > 0 || (staticAnalyze && warningCount > 0)) hasErrors = true; @@ -193,13 +194,20 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p, ref StringCache cache, bool report, ref const(Token)[] tokens, - ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null) + ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null, + const StaticAnalysisConfig* analysisConfig = null) { import stats : isLineOfCode; LexerConfig config; config.fileName = fileName; config.stringBehavior = StringBehavior.source; + if (analysisConfig !is null && analysisConfig.trailing_whitespace != Check.disabled) + { + import std.stdio; + writeln("Include whitespace"); + config.whitespaceBehavior = WhitespaceBehavior.include; + } tokens = getTokensForParser(code, config, &cache); if (linesOfCode !is null) (*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens); @@ -479,6 +487,10 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a checks ~= new RedundantAttributesCheck(fileName, moduleScope, analysisConfig.redundant_attributes_check == Check.skipTests && !ut); + if (analysisConfig.trailing_whitespace != Check.disabled) + checks ~= new TrailingWhitespaceCheck(fileName, tokens, + analysisConfig.useless_initializer == Check.skipTests && !ut); + if (moduleName.shouldRun!"has_public_example"(analysisConfig)) checks ~= new HasPublicExampleCheck(fileName, moduleScope, analysisConfig.has_public_example == Check.skipTests && !ut); diff --git a/src/analysis/trailing_whitespace.d b/src/analysis/trailing_whitespace.d new file mode 100644 index 00000000..a9f836c1 --- /dev/null +++ b/src/analysis/trailing_whitespace.d @@ -0,0 +1,59 @@ +// 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.trailing_whitespace; + +import dparse.lexer; +import dparse.ast; +import analysis.base : BaseAnalyzer, Message; +import dsymbol.scope_ : Scope; + +import std.algorithm; +import std.range; + +/** +Checks for trailing whitespace +*/ +class TrailingWhitespaceCheck : BaseAnalyzer +{ + /// + this(string fileName, const(Token)[] tokens, bool skipTests = false) + { + super(fileName, null, skipTests); + import std.stdio; + foreach (t; tokens) + writeln("type", t.type.str, " line: ", t.line); + + foreach (i; 1 .. tokens.length) + { + addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE); + } + } + + enum string KEY = "dscanner.style.trailing_whitespace"; + enum string MESSAGE = "Trailing whitespace detected."; +} + +unittest +{ + import analysis.config : StaticAnalysisConfig, Check, disabledConfig; + import analysis.helpers; + import std.stdio; + + StaticAnalysisConfig sac = disabledConfig(); + sac.trailing_whitespace = Check.enabled; + + // between functions + auto msgs = getAnalyzerWarnings(q{ + void testConsecutiveEmptyLines(){} + + + void foo(){} + }c, sac); + assert(msgs.length == 1); + Message msg = Message("test", 5, 3, TrailingWhitespaceCheck.KEY, TrailingWhitespaceCheck.MESSAGE); + assert(msgs.front == msg); + + stderr.writeln("Unittest for TrailingWhitespaceCheck passed."); +}