diff --git a/README.md b/README.md index 5ce4df3..4540910 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Note that the "--skipTests" option is the equivalent of changing each * Check for that imports are sorted. Initially implemented to lint Phobos. By default disabled. * Virtual calls inside classes constructors. * Useless initializers. +* Allman brace style #### Wishlist diff --git a/src/analysis/allman.d b/src/analysis/allman.d new file mode 100644 index 0000000..242a53d --- /dev/null +++ b/src/analysis/allman.d @@ -0,0 +1,133 @@ +// 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.allman; + +import dparse.lexer; +import dparse.ast; +import analysis.base : BaseAnalyzer; +import dsymbol.scope_ : Scope; + +import std.algorithm; +import std.range; + +/** +Checks for the allman style (braces should be on their own line) +------------ +if (param < 0) { +} +------------ +should be +------------ +if (param < 0) +{ +} +------------ +*/ +class AllManCheck : BaseAnalyzer +{ + /// + this(string fileName, const(Token)[] tokens, bool skipTests = false) + { + super(fileName, null, skipTests); + foreach (i; 1 .. tokens.length - 1) + { + auto curLine = tokens[i].line; + auto prevTokenLine = tokens[i-1].line; + if (tokens[i].type == tok!"{" && curLine == prevTokenLine) + { + // ignore struct initialization + if (tokens[i-1].type == tok!"=") + continue; + // ignore inline { } braces + if (curLine != tokens[i + 1].line) + addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE); + } + if (tokens[i].type == tok!"}" && curLine == prevTokenLine) + { + // ignore inline { } braces + if (!tokens[0 .. i].retro.until!(t => t.line != curLine).canFind!(t => t.type == tok!"{")) + addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE); + } + } + } + + enum string KEY = "dscanner.style.allman"; + enum string MESSAGE = "Braces should be on their own line"; +} + +unittest +{ + import analysis.config : StaticAnalysisConfig, Check, disabledConfig; + import analysis.helpers : assertAnalyzerWarnings; + import std.format : format; + import std.stdio : stderr; + + StaticAnalysisConfig sac = disabledConfig(); + sac.allman_braces_check = Check.enabled; + + // check common allman style violation + assertAnalyzerWarnings(q{ + void testAllman() + { + while (true) { // [warn]: %s + auto f = 1; + } + + do { // [warn]: %s + auto f = 1; + } while (true); + + // inline braces are OK + while (true) { auto f = 1; } + + if (true) { // [warn]: %s + auto f = 1; + } + if (true) + { + auto f = 1; } // [warn]: %s + if (true) { auto f = 1; } + foreach (r; [1]) { // [warn]: %s + } + foreach (r; [1]) { } + foreach_reverse (r; [1]) { // [warn]: %s + } + foreach_reverse (r; [1]) { } + for (int i = 0; i < 10; i++) { // [warn]: %s + } + for (int i = 0; i < 10; i++) { } + + // nested check + while (true) { // [warn]: %s + while (true) { // [warn]: %s + auto f = 1; + } + } + } + }c.format( + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + AllManCheck.MESSAGE, + ), sac); + + // check struct initialization + assertAnalyzerWarnings(q{ +unittest +{ + struct Foo { int a; } + Foo foo = { + a: 1; + }; +} + }, sac); + + stderr.writeln("Unittest for Allman passed."); +} diff --git a/src/analysis/config.d b/src/analysis/config.d index 43fb124..4c801ab 100644 --- a/src/analysis/config.d +++ b/src/analysis/config.d @@ -179,4 +179,7 @@ struct StaticAnalysisConfig @INI("Check for useless user defined initializers") string useless_initializer = Check.enabled; + + @INI("Check allman brace style") + string allman_braces_check = Check.disabled; } diff --git a/src/analysis/run.d b/src/analysis/run.d index 0dcb4f7..b200b9d 100644 --- a/src/analysis/run.d +++ b/src/analysis/run.d @@ -66,6 +66,7 @@ import analysis.properly_documented_public_functions; import analysis.final_attribute; import analysis.vcall_in_ctor; import analysis.useless_initializer; +import analysis.allman; import dsymbol.string_interning : internString; import dsymbol.scope_; @@ -389,6 +390,10 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a checks ~= new UselessInitializerChecker(fileName, analysisConfig.useless_initializer == Check.skipTests && !ut); + if (analysisConfig.allman_braces_check != Check.disabled) + checks ~= new AllManCheck(fileName, tokens, + analysisConfig.allman_braces_check == Check.skipTests && !ut); + version (none) if (analysisConfig.redundant_if_check != Check.disabled) checks ~= new IfStatementCheck(fileName, moduleScope,