1 // Copyright Brian Schott (Hackerpilot) 2014. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module dscanner.analysis.undocumented; 7 8 import dscanner.analysis.base; 9 import dsymbol.scope_ : Scope; 10 import dparse.ast; 11 import dparse.lexer; 12 13 import std.regex : ctRegex, matchAll; 14 import std.stdio; 15 16 /** 17 * Checks for undocumented public declarations. Ignores some operator overloads, 18 * main functions, and functions whose name starts with "get" or "set". 19 */ 20 class UndocumentedDeclarationCheck : BaseAnalyzer 21 { 22 alias visit = BaseAnalyzer.visit; 23 24 this(string fileName, const(Scope)* sc, bool skipTests = false) 25 { 26 super(fileName, sc, skipTests); 27 } 28 29 override void visit(const Module mod) 30 { 31 push(tok!"public"); 32 mod.accept(this); 33 } 34 35 override void visit(const Declaration dec) 36 { 37 if (dec.attributeDeclaration) 38 { 39 auto attr = dec.attributeDeclaration.attribute; 40 if (isProtection(attr.attribute.type)) 41 set(attr.attribute.type); 42 else if (attr.attribute == tok!"override") 43 setOverride(true); 44 else if (attr.deprecated_ !is null) 45 setDeprecated(true); 46 else if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable") 47 setDisabled(true); 48 } 49 50 immutable bool shouldPop = dec.attributeDeclaration is null; 51 immutable bool prevOverride = getOverride(); 52 immutable bool prevDisabled = getDisabled(); 53 immutable bool prevDeprecated = getDeprecated(); 54 bool dis; 55 bool dep; 56 bool ovr; 57 bool pushed; 58 foreach (attribute; dec.attributes) 59 { 60 if (isProtection(attribute.attribute.type)) 61 { 62 if (shouldPop) 63 { 64 pushed = true; 65 push(attribute.attribute.type); 66 } 67 else 68 set(attribute.attribute.type); 69 } 70 else if (attribute.attribute == tok!"override") 71 ovr = true; 72 else if (attribute.deprecated_ !is null) 73 dep = true; 74 else if (attribute.atAttribute !is null 75 && attribute.atAttribute.identifier.text == "disable") 76 dis = true; 77 } 78 if (ovr) 79 setOverride(true); 80 if (dis) 81 setDisabled(true); 82 if (dep) 83 setDeprecated(true); 84 dec.accept(this); 85 if (shouldPop && pushed) 86 pop(); 87 if (ovr) 88 setOverride(prevOverride); 89 if (dis) 90 setDisabled(prevDisabled); 91 if (dep) 92 setDeprecated(prevDeprecated); 93 } 94 95 override void visit(const VariableDeclaration variable) 96 { 97 if (!currentIsInteresting() || variable.comment.ptr !is null) 98 return; 99 if (variable.autoDeclaration !is null) 100 { 101 addMessage(variable.autoDeclaration.parts[0].identifier.line, 102 variable.autoDeclaration.parts[0].identifier.column, 103 variable.autoDeclaration.parts[0].identifier.text); 104 return; 105 } 106 foreach (dec; variable.declarators) 107 { 108 if (dec.comment.ptr is null) 109 addMessage(dec.name.line, dec.name.column, dec.name.text); 110 return; 111 } 112 } 113 114 override void visit(const ConditionalDeclaration cond) 115 { 116 const VersionCondition ver = cond.compileCondition.versionCondition; 117 if (ver is null || (ver.token != tok!"unittest" && ver.token.text != "none")) 118 cond.accept(this); 119 else if (cond.falseDeclarations.length > 0) 120 foreach (f; cond.falseDeclarations) 121 visit(f); 122 } 123 124 override void visit(const FunctionBody fb) 125 { 126 } 127 128 override void visit(const Unittest u) 129 { 130 } 131 132 override void visit(const TraitsExpression t) 133 { 134 } 135 136 mixin V!AnonymousEnumMember; 137 mixin V!ClassDeclaration; 138 mixin V!EnumDeclaration; 139 mixin V!InterfaceDeclaration; 140 mixin V!StructDeclaration; 141 mixin V!UnionDeclaration; 142 mixin V!TemplateDeclaration; 143 mixin V!FunctionDeclaration; 144 mixin V!Constructor; 145 146 private: 147 148 mixin template V(T) 149 { 150 override void visit(const T declaration) 151 { 152 import std.traits : hasMember; 153 154 if (currentIsInteresting()) 155 { 156 if (declaration.comment.ptr is null) 157 { 158 static if (hasMember!(T, "name")) 159 { 160 static if (is(T == FunctionDeclaration)) 161 { 162 import std.algorithm : canFind; 163 164 if (!(ignoredFunctionNames.canFind(declaration.name.text) 165 || isGetterOrSetter(declaration.name.text) 166 || isProperty(declaration))) 167 { 168 addMessage(declaration.name.line, 169 declaration.name.column, declaration.name.text); 170 } 171 } 172 else 173 { 174 if (declaration.name.type != tok!"") 175 addMessage(declaration.name.line, 176 declaration.name.column, declaration.name.text); 177 } 178 } 179 else 180 { 181 addMessage(declaration.line, declaration.column, null); 182 } 183 } 184 static if (!(is(T == TemplateDeclaration) || is(T == FunctionDeclaration))) 185 { 186 declaration.accept(this); 187 } 188 } 189 } 190 } 191 192 static bool isGetterOrSetter(string name) 193 { 194 return !matchAll(name, getSetRe).empty; 195 } 196 197 static bool isProperty(const FunctionDeclaration dec) 198 { 199 if (dec.memberFunctionAttributes is null) 200 return false; 201 foreach (attr; dec.memberFunctionAttributes) 202 { 203 if (attr.atAttribute && attr.atAttribute.identifier.text == "property") 204 return true; 205 } 206 return false; 207 } 208 209 void addMessage(size_t line, size_t column, string name) 210 { 211 import std..string : format; 212 213 addErrorMessage(line, column, "dscanner.style.undocumented_declaration", name is null 214 ? "Public declaration is undocumented." 215 : format("Public declaration '%s' is undocumented.", name)); 216 } 217 218 bool getOverride() 219 { 220 return stack[$ - 1].isOverride; 221 } 222 223 void setOverride(bool o = true) 224 { 225 stack[$ - 1].isOverride = o; 226 } 227 228 bool getDisabled() 229 { 230 return stack[$ - 1].isDisabled; 231 } 232 233 void setDisabled(bool d = true) 234 { 235 stack[$ - 1].isDisabled = d; 236 } 237 238 bool getDeprecated() 239 { 240 return stack[$ - 1].isDeprecated; 241 } 242 243 void setDeprecated(bool d = true) 244 { 245 stack[$ - 1].isDeprecated = d; 246 } 247 248 bool currentIsInteresting() 249 { 250 return stack[$ - 1].protection == tok!"public" 251 && !stack[$ - 1].isOverride && !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated; 252 } 253 254 void set(IdType p) 255 in 256 { 257 assert(isProtection(p)); 258 } 259 body 260 { 261 stack[$ - 1].protection = p; 262 } 263 264 void push(IdType p) 265 in 266 { 267 assert(isProtection(p)); 268 } 269 body 270 { 271 stack ~= ProtectionInfo(p, false); 272 } 273 274 void pop() 275 { 276 assert(stack.length > 1); 277 stack = stack[0 .. $ - 1]; 278 } 279 280 static struct ProtectionInfo 281 { 282 IdType protection; 283 bool isOverride; 284 bool isDeprecated; 285 bool isDisabled; 286 } 287 288 ProtectionInfo[] stack; 289 } 290 291 // Ignore undocumented symbols with these names 292 private immutable string[] ignoredFunctionNames = [ 293 "opCmp", "opEquals", "toString", "toHash", "main" 294 ]; 295 296 private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`; 297 298 unittest 299 { 300 import std.stdio : stderr; 301 import std.format : format; 302 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 303 import dscanner.analysis.helpers : assertAnalyzerWarnings; 304 305 StaticAnalysisConfig sac = disabledConfig(); 306 sac.undocumented_declaration_check = Check.enabled; 307 308 assertAnalyzerWarnings(q{ 309 class C{} // [warn]: Public declaration 'C' is undocumented. 310 interface I{} // [warn]: Public declaration 'I' is undocumented. 311 enum e = 0; // [warn]: Public declaration 'e' is undocumented. 312 void f(){} // [warn]: Public declaration 'f' is undocumented. 313 struct S{} // [warn]: Public declaration 'S' is undocumented. 314 template T(){} // [warn]: Public declaration 'T' is undocumented. 315 union U{} // [warn]: Public declaration 'U' is undocumented. 316 }, sac); 317 318 assertAnalyzerWarnings(q{ 319 /// C 320 class C{} 321 /// I 322 interface I{} 323 /// e 324 enum e = 0; 325 /// f 326 void f(){} 327 /// S 328 struct S{} 329 /// T 330 template T(){} 331 /// U 332 union U{} 333 }, sac); 334 335 stderr.writeln("Unittest for UndocumentedDeclarationCheck passed."); 336 } 337