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