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 // libdparse columns start at 1 28 foreach (t; tokens) 29 { 30 while (firstSymbolAtLine.length < t.line - 1) 31 firstSymbolAtLine ~= Pos(1); 32 33 if (firstSymbolAtLine.length < t.line) 34 firstSymbolAtLine ~= Pos(t.column, t.type == tok!"if"); 35 } 36 } 37 38 override void visit(const FunctionDeclaration decl) 39 { 40 if (decl.constraint !is null) 41 checkConstraintSpace(decl.constraint, decl.name); 42 } 43 44 override void visit(const InterfaceDeclaration decl) 45 { 46 if (decl.constraint !is null) 47 checkConstraintSpace(decl.constraint, decl.name); 48 } 49 50 51 override void visit(const ClassDeclaration decl) 52 { 53 if (decl.constraint !is null) 54 checkConstraintSpace(decl.constraint, decl.name); 55 } 56 57 override void visit(const TemplateDeclaration decl) 58 { 59 if (decl.constraint !is null) 60 checkConstraintSpace(decl.constraint, decl.name); 61 } 62 63 override void visit(const UnionDeclaration decl) 64 { 65 if (decl.constraint !is null) 66 checkConstraintSpace(decl.constraint, decl.name); 67 } 68 69 override void visit(const StructDeclaration decl) 70 { 71 if (decl.constraint !is null) 72 checkConstraintSpace(decl.constraint, decl.name); 73 } 74 75 override void visit(const Constructor decl) 76 { 77 if (decl.constraint !is null) 78 checkConstraintSpace(decl.constraint, decl.line); 79 } 80 81 alias visit = ASTVisitor.visit; 82 83 private: 84 85 enum string KEY = "dscanner.style.if_constraints_indent"; 86 enum string MESSAGE = "If constraints should have the same indentation as the function"; 87 88 Pos[] firstSymbolAtLine; 89 static struct Pos 90 { 91 size_t column; 92 bool isIf; 93 } 94 95 /** 96 Check indentation of constraints 97 */ 98 void checkConstraintSpace(const Constraint constraint, const Token token) 99 { 100 checkConstraintSpace(constraint, token.line); 101 } 102 103 void checkConstraintSpace(const Constraint constraint, size_t line) 104 { 105 // dscanner lines start at 1 106 auto pDecl = firstSymbolAtLine[line - 1]; 107 108 // search for constraint if (might not be on the same line as the expression) 109 auto r = firstSymbolAtLine[line .. constraint.expression.line].retro.filter!(s => s.isIf); 110 111 // no hit = constraint is on the same line 112 if (r.empty) 113 addErrorMessage(constraint.expression.line, constraint.expression.column, KEY, MESSAGE); 114 else if (pDecl.column != r.front.column) 115 addErrorMessage(constraint.expression.line, constraint.expression.column, KEY, MESSAGE); 116 } 117 } 118 119 unittest 120 { 121 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 122 import dscanner.analysis.helpers : assertAnalyzerWarnings; 123 import std.format : format; 124 import std.stdio : stderr; 125 126 StaticAnalysisConfig sac = disabledConfig(); 127 sac.if_constraints_indent = Check.enabled; 128 129 assertAnalyzerWarnings(q{ 130 void foo(R)(R r) 131 if (R == null) 132 {} 133 134 void foo(R)(R r) 135 if (R == null) // [warn]: %s 136 {} 137 }}.format( 138 IfConstraintsIndentCheck.MESSAGE, 139 ), sac); 140 141 assertAnalyzerWarnings(q{ 142 void foo(R)(R r) 143 if (R == null) 144 {} 145 146 void foo(R)(R r) 147 if (R == null) // [warn]: %s 148 {} 149 150 void foo(R)(R r) 151 if (R == null) // [warn]: %s 152 {} 153 }}.format( 154 IfConstraintsIndentCheck.MESSAGE, 155 IfConstraintsIndentCheck.MESSAGE, 156 ), sac); 157 158 assertAnalyzerWarnings(q{ 159 struct Foo(R) 160 if (R == null) 161 {} 162 163 struct Foo(R) 164 if (R == null) // [warn]: %s 165 {} 166 167 struct Foo(R) 168 if (R == null) // [warn]: %s 169 {} 170 }}.format( 171 IfConstraintsIndentCheck.MESSAGE, 172 IfConstraintsIndentCheck.MESSAGE, 173 ), sac); 174 175 // test example from Phobos 176 assertAnalyzerWarnings(q{ 177 Num abs(Num)(Num x) @safe pure nothrow 178 if (is(typeof(Num.init >= 0)) && is(typeof(-Num.init)) && 179 !(is(Num* : const(ifloat*)) || is(Num* : const(idouble*)) 180 || is(Num* : const(ireal*)))) 181 { 182 static if (isFloatingPoint!(Num)) 183 return fabs(x); 184 else 185 return x >= 0 ? x : -x; 186 } 187 }, sac); 188 189 // weird constraint formatting 190 assertAnalyzerWarnings(q{ 191 struct Foo(R) 192 if 193 (R == null) 194 {} 195 196 struct Foo(R) 197 if 198 (R == null) 199 {} 200 201 struct Foo(R) 202 if 203 (R == null) // [warn]: %s 204 {} 205 206 struct Foo(R) 207 if ( 208 R == null) 209 {} 210 211 struct Foo(R) 212 if ( 213 R == null 214 ) 215 {} 216 217 struct Foo(R) 218 if ( 219 R == null // [warn]: %s 220 ) {} 221 }}.format( 222 IfConstraintsIndentCheck.MESSAGE, 223 IfConstraintsIndentCheck.MESSAGE, 224 ), sac); 225 226 // constraint on the same line 227 assertAnalyzerWarnings(q{ 228 struct CRC(uint N, ulong P) if (N == 32 || N == 64) // [warn]: %s 229 {} 230 }}.format( 231 IfConstraintsIndentCheck.MESSAGE, 232 ), sac); 233 234 stderr.writeln("Unittest for IfConstraintsIndentCheck passed."); 235 }