diff --git a/README.md b/README.md index 93b6dc73..591dbccd 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ Note that the "--skipTests" option is the equivalent of changing each * Useless initializers. * Allman brace style * Redundant visibility attributes +* Two or more consecutive empty lines #### Wishlist diff --git a/src/analysis/config.d b/src/analysis/config.d index 0a2ca208..34faa668 100644 --- a/src/analysis/config.d +++ b/src/analysis/config.d @@ -185,4 +185,7 @@ struct StaticAnalysisConfig @INI("Check for redundant attributes") string redundant_attributes_check = Check.enabled; + + @INI("Check for two or more consecutive empty lines") + string consecutive_empty_lines = Check.disabled; } diff --git a/src/analysis/consecutive_empty_lines.d b/src/analysis/consecutive_empty_lines.d new file mode 100644 index 00000000..1e7346ca --- /dev/null +++ b/src/analysis/consecutive_empty_lines.d @@ -0,0 +1,100 @@ +// 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.consecutive_empty_lines; + +import dparse.lexer; +import dparse.ast; +import analysis.base : BaseAnalyzer, Message; +import dsymbol.scope_ : Scope; + +import std.algorithm; +import std.range; + +/** +Checks whether a file contains two or more consecutive empty lines +*/ +class ConsecutiveEmptyLinesCheck : 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) + { + auto curLine = tokens[i].line; + auto prevTokenLine = tokens[i-1].line; + if (curLine >= prevTokenLine + 2) + { + addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE); + } + } + } + + enum string KEY = "dscanner.style.consecutive_empty_lines"; + enum string MESSAGE = "Consecutive empty lines detected"; +} + +unittest +{ + import analysis.config : StaticAnalysisConfig, Check, disabledConfig; + import analysis.helpers; + import std.stdio; + + StaticAnalysisConfig sac = disabledConfig(); + sac.consecutive_empty_lines = Check.enabled; + + // between functions + auto msgs = getAnalyzerWarnings(q{ + void testConsecutiveEmptyLines(){} + + + void foo(){} + }c, sac); + assert(msgs.length == 1); + Message msg = Message("test", 5, 3, ConsecutiveEmptyLinesCheck.KEY, ConsecutiveEmptyLinesCheck.MESSAGE); + assert(msgs.front == msg); + + // between functions (with comments) + msgs = getAnalyzerWarnings(q{ + void testConsecutiveEmptyLines(){} + + /// + void foo(){} + }c, sac); + assert(msgs.length == 0); + + msgs = getAnalyzerWarnings(q{ + void testConsecutiveEmptyLines(){} + + + /// + void foo(){} + }c, sac); + assert(msgs.length == 0); + msgs.writeln; + msg = Message("test", 5, 3, ConsecutiveEmptyLinesCheck.KEY, ConsecutiveEmptyLinesCheck.MESSAGE); + assert(msgs.front == msg); + + + // within a function + msgs = getAnalyzerWarnings(q{ + void testConsecutiveEmptyLines() + { + int a; + + + int b; + } + }c, sac); + assert(msgs.length == 1); + msg = Message("test", 7, 4, ConsecutiveEmptyLinesCheck.KEY, ConsecutiveEmptyLinesCheck.MESSAGE); + assert(msgs.front == msg); + + stderr.writeln("Unittest for ConsecutiveEmptyLines passed."); +} diff --git a/src/analysis/helpers.d b/src/analysis/helpers.d index 5312ff90..c309e14c 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; @@ -59,7 +57,20 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig 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__) +{ + + MessageSet rawWarnings = getAnalyzerWarnings(code, config, file, line); + string[] codeLines = code.split("\n"); // Get the warnings ordered by line diff --git a/src/analysis/run.d b/src/analysis/run.d index 3e3a1dd8..ad061ccf 100644 --- a/src/analysis/run.d +++ b/src/analysis/run.d @@ -68,6 +68,7 @@ import analysis.vcall_in_ctor; import analysis.useless_initializer; import analysis.allman; import analysis.redundant_attributes; +import analysis.consecutive_empty_lines; import dsymbol.string_interning : internString; import dsymbol.scope_; @@ -198,6 +199,7 @@ const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p, LexerConfig config; config.fileName = fileName; config.stringBehavior = StringBehavior.source; + config.commentBehavior = CommentBehavior.intern; tokens = getTokensForParser(code, config, &cache); if (linesOfCode !is null) (*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens); @@ -399,6 +401,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.consecutive_empty_lines != Check.disabled) + checks ~= new ConsecutiveEmptyLinesCheck(fileName, tokens, + analysisConfig.consecutive_empty_lines == Check.skip && !ut); + version (none) if (analysisConfig.redundant_if_check != Check.disabled) checks ~= new IfStatementCheck(fileName, moduleScope,