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 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 static if (hasMember!(T, "storageClasses")) 154 { 155 // stop at declarations with a deprecated in their storage classes 156 foreach (sc; declaration.storageClasses) 157 if (sc.deprecated_ !is null) 158 return; 159 } 160 161 if (currentIsInteresting()) 162 { 163 if (declaration.comment.ptr is null) 164 { 165 static if (hasMember!(T, "name")) 166 { 167 static if (is(T == FunctionDeclaration)) 168 { 169 import std.algorithm : canFind; 170 171 if (!(ignoredFunctionNames.canFind(declaration.name.text) 172 || isGetterOrSetter(declaration.name.text) 173 || isProperty(declaration))) 174 { 175 addMessage(declaration.name.line, 176 declaration.name.column, declaration.name.text); 177 } 178 } 179 else 180 { 181 if (declaration.name.type != tok!"") 182 addMessage(declaration.name.line, 183 declaration.name.column, declaration.name.text); 184 } 185 } 186 else 187 { 188 addMessage(declaration.line, declaration.column, null); 189 } 190 } 191 static if (!(is(T == TemplateDeclaration) || is(T == FunctionDeclaration))) 192 { 193 declaration.accept(this); 194 } 195 } 196 } 197 } 198 199 static bool isGetterOrSetter(string name) 200 { 201 return !matchAll(name, getSetRe).empty; 202 } 203 204 static bool isProperty(const FunctionDeclaration dec) 205 { 206 if (dec.memberFunctionAttributes is null) 207 return false; 208 foreach (attr; dec.memberFunctionAttributes) 209 { 210 if (attr.atAttribute && attr.atAttribute.identifier.text == "property") 211 return true; 212 } 213 return false; 214 } 215 216 void addMessage(size_t line, size_t column, string name) 217 { 218 import std.string : format; 219 220 addErrorMessage(line, column, "dscanner.style.undocumented_declaration", name is null 221 ? "Public declaration is undocumented." 222 : format("Public declaration '%s' is undocumented.", name)); 223 } 224 225 bool getOverride() 226 { 227 return stack[$ - 1].isOverride; 228 } 229 230 void setOverride(bool o = true) 231 { 232 stack[$ - 1].isOverride = o; 233 } 234 235 bool getDisabled() 236 { 237 return stack[$ - 1].isDisabled; 238 } 239 240 void setDisabled(bool d = true) 241 { 242 stack[$ - 1].isDisabled = d; 243 } 244 245 bool getDeprecated() 246 { 247 return stack[$ - 1].isDeprecated; 248 } 249 250 void setDeprecated(bool d = true) 251 { 252 stack[$ - 1].isDeprecated = d; 253 } 254 255 bool currentIsInteresting() 256 { 257 return stack[$ - 1].protection == tok!"public" 258 && !getOverride() && !getDisabled() && !getDeprecated(); 259 } 260 261 void set(IdType p) 262 in 263 { 264 assert(isProtection(p)); 265 } 266 body 267 { 268 stack[$ - 1].protection = p; 269 } 270 271 void push(IdType p) 272 in 273 { 274 assert(isProtection(p)); 275 } 276 body 277 { 278 stack ~= ProtectionInfo(p, false); 279 } 280 281 void pop() 282 { 283 assert(stack.length > 1); 284 stack = stack[0 .. $ - 1]; 285 } 286 287 static struct ProtectionInfo 288 { 289 IdType protection; 290 bool isOverride; 291 bool isDeprecated; 292 bool isDisabled; 293 } 294 295 ProtectionInfo[] stack; 296 } 297 298 // Ignore undocumented symbols with these names 299 private immutable string[] ignoredFunctionNames = [ 300 "opCmp", "opEquals", "toString", "toHash", "main" 301 ]; 302 303 private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`; 304 305 unittest 306 { 307 import std.stdio : stderr; 308 import std.format : format; 309 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 310 import dscanner.analysis.helpers : assertAnalyzerWarnings; 311 312 StaticAnalysisConfig sac = disabledConfig(); 313 sac.undocumented_declaration_check = Check.enabled; 314 315 assertAnalyzerWarnings(q{ 316 class C{} // [warn]: Public declaration 'C' is undocumented. 317 interface I{} // [warn]: Public declaration 'I' is undocumented. 318 enum e = 0; // [warn]: Public declaration 'e' is undocumented. 319 void f(){} // [warn]: Public declaration 'f' is undocumented. 320 struct S{} // [warn]: Public declaration 'S' is undocumented. 321 template T(){} // [warn]: Public declaration 'T' is undocumented. 322 union U{} // [warn]: Public declaration 'U' is undocumented. 323 }, sac); 324 325 assertAnalyzerWarnings(q{ 326 /// C 327 class C{} 328 /// I 329 interface I{} 330 /// e 331 enum e = 0; 332 /// f 333 void f(){} 334 /// S 335 struct S{} 336 /// T 337 template T(){} 338 /// U 339 union U{} 340 }, sac); 341 342 // https://github.com/dlang-community/D-Scanner/issues/760 343 assertAnalyzerWarnings(q{ 344 deprecated auto func(){} 345 deprecated auto func()(){} 346 }, sac); 347 348 stderr.writeln("Unittest for UndocumentedDeclarationCheck passed."); 349 } 350