1 // Distributed under the Boost Software License, Version 1.0. 2 // (See accompanying file LICENSE_1_0.txt or copy at 3 // http://www.boost.org/LICENSE_1_0.txt) 4 5 module dscanner.analysis.if_constraints_indent; 6 7 import dparse.lexer; 8 import dparse.ast; 9 import dscanner.analysis.base; 10 import dsymbol.scope_ : Scope; 11 12 import std.algorithm.iteration : filter; 13 import std.range; 14 15 /** 16 Checks whether all if constraints have the same indention as their declaration. 17 */ 18 final class IfConstraintsIndentCheck : BaseAnalyzer 19 { 20 mixin AnalyzerInfo!"if_constraints_indent"; 21 22 /// 23 this(string fileName, const(Token)[] tokens, bool skipTests = false) 24 { 25 super(fileName, null, skipTests); 26 27 // convert tokens to a list of token starting positions per line 28 29 // libdparse columns start at 1 30 foreach (t; tokens) 31 { 32 // pad empty positions if we skip empty token-less lines 33 // t.line (unsigned) may be 0 if the token is uninitialized/broken, so don't subtract from it 34 // equivalent to: firstSymbolAtLine.length < t.line - 1 35 while (firstSymbolAtLine.length + 1 < t.line) 36 firstSymbolAtLine ~= Pos(1); 37 38 // insert a new line with positions if new line is reached 39 // (previous while pads skipped lines) 40 if (firstSymbolAtLine.length < t.line) 41 firstSymbolAtLine ~= Pos(t.column, t.type == tok!"if"); 42 } 43 } 44 45 override void visit(const FunctionDeclaration decl) 46 { 47 if (decl.constraint !is null) 48 checkConstraintSpace(decl.constraint, decl.name); 49 } 50 51 override void visit(const InterfaceDeclaration decl) 52 { 53 if (decl.constraint !is null) 54 checkConstraintSpace(decl.constraint, decl.name); 55 } 56 57 58 override void visit(const ClassDeclaration decl) 59 { 60 if (decl.constraint !is null) 61 checkConstraintSpace(decl.constraint, decl.name); 62 } 63 64 override void visit(const TemplateDeclaration decl) 65 { 66 if (decl.constraint !is null) 67 checkConstraintSpace(decl.constraint, decl.name); 68 } 69 70 override void visit(const UnionDeclaration decl) 71 { 72 if (decl.constraint !is null) 73 checkConstraintSpace(decl.constraint, decl.name); 74 } 75 76 override void visit(const StructDeclaration decl) 77 { 78 if (decl.constraint !is null) 79 checkConstraintSpace(decl.constraint, decl.name); 80 } 81 82 override void visit(const Constructor decl) 83 { 84 if (decl.constraint !is null) 85 checkConstraintSpace(decl.constraint, decl.line); 86 } 87 88 alias visit = ASTVisitor.visit; 89 90 private: 91 92 enum string KEY = "dscanner.style.if_constraints_indent"; 93 enum string MESSAGE = "If constraints should have the same indentation as the function"; 94 95 Pos[] firstSymbolAtLine; 96 static struct Pos 97 { 98 size_t column; 99 bool isIf; 100 } 101 102 /** 103 Check indentation of constraints 104 */ 105 void checkConstraintSpace(const Constraint constraint, const Token token) 106 { 107 checkConstraintSpace(constraint, token.line); 108 } 109 110 void checkConstraintSpace(const Constraint constraint, size_t line) 111 { 112 // dscanner lines start at 1 113 auto pDecl = firstSymbolAtLine[line - 1]; 114 115 // search for constraint if (might not be on the same line as the expression) 116 auto r = firstSymbolAtLine[line .. constraint.expression.line].retro.filter!(s => s.isIf); 117 118 // no hit = constraint is on the same line 119 if (r.empty) 120 addErrorMessage(constraint.expression.line, constraint.expression.column, KEY, MESSAGE); 121 else if (pDecl.column != r.front.column) 122 addErrorMessage(constraint.expression.line, constraint.expression.column, KEY, MESSAGE); 123 } 124 } 125 126 unittest 127 { 128 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 129 import dscanner.analysis.helpers : assertAnalyzerWarnings; 130 import std.format : format; 131 import std.stdio : stderr; 132 133 StaticAnalysisConfig sac = disabledConfig(); 134 sac.if_constraints_indent = Check.enabled; 135 136 assertAnalyzerWarnings(q{ 137 void foo(R)(R r) 138 if (R == null) 139 {} 140 141 void foo(R)(R r) 142 if (R == null) // [warn]: %s 143 {} 144 }}.format( 145 IfConstraintsIndentCheck.MESSAGE, 146 ), sac); 147 148 assertAnalyzerWarnings(q{ 149 void foo(R)(R r) 150 if (R == null) 151 {} 152 153 void foo(R)(R r) 154 if (R == null) // [warn]: %s 155 {} 156 157 void foo(R)(R r) 158 if (R == null) // [warn]: %s 159 {} 160 }}.format( 161 IfConstraintsIndentCheck.MESSAGE, 162 IfConstraintsIndentCheck.MESSAGE, 163 ), sac); 164 165 assertAnalyzerWarnings(q{ 166 struct Foo(R) 167 if (R == null) 168 {} 169 170 struct Foo(R) 171 if (R == null) // [warn]: %s 172 {} 173 174 struct Foo(R) 175 if (R == null) // [warn]: %s 176 {} 177 }}.format( 178 IfConstraintsIndentCheck.MESSAGE, 179 IfConstraintsIndentCheck.MESSAGE, 180 ), sac); 181 182 // test example from Phobos 183 assertAnalyzerWarnings(q{ 184 Num abs(Num)(Num x) @safe pure nothrow 185 if (is(typeof(Num.init >= 0)) && is(typeof(-Num.init)) && 186 !(is(Num* : const(ifloat*)) || is(Num* : const(idouble*)) 187 || is(Num* : const(ireal*)))) 188 { 189 static if (isFloatingPoint!(Num)) 190 return fabs(x); 191 else 192 return x >= 0 ? x : -x; 193 } 194 }, sac); 195 196 // weird constraint formatting 197 assertAnalyzerWarnings(q{ 198 struct Foo(R) 199 if 200 (R == null) 201 {} 202 203 struct Foo(R) 204 if 205 (R == null) 206 {} 207 208 struct Foo(R) 209 if 210 (R == null) // [warn]: %s 211 {} 212 213 struct Foo(R) 214 if ( 215 R == null) 216 {} 217 218 struct Foo(R) 219 if ( 220 R == null 221 ) 222 {} 223 224 struct Foo(R) 225 if ( 226 R == null // [warn]: %s 227 ) {} 228 }}.format( 229 IfConstraintsIndentCheck.MESSAGE, 230 IfConstraintsIndentCheck.MESSAGE, 231 ), sac); 232 233 // constraint on the same line 234 assertAnalyzerWarnings(q{ 235 struct CRC(uint N, ulong P) if (N == 32 || N == 64) // [warn]: %s 236 {} 237 }}.format( 238 IfConstraintsIndentCheck.MESSAGE, 239 ), sac); 240 241 stderr.writeln("Unittest for IfConstraintsIndentCheck passed."); 242 } 243 244 @("issue #829") 245 unittest 246 { 247 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 248 import dscanner.analysis.helpers : assertAnalyzerWarnings; 249 import std.format : format; 250 import std.stdio : stderr; 251 252 StaticAnalysisConfig sac = disabledConfig(); 253 sac.if_constraints_indent = Check.enabled; 254 255 assertAnalyzerWarnings(`void foo() { 256 '' 257 }`, sac); 258 }