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 analysis.undocumented; 7 8 import 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) 25 { 26 super(fileName, sc); 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 = false; 55 bool dep = false; 56 bool ovr = false; 57 bool pushed = false; 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.identifiers[0].line, 102 variable.autoDeclaration.identifiers[0].column, 103 variable.autoDeclaration.identifiers[0].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!ClassDeclaration; 137 mixin V!InterfaceDeclaration; 138 mixin V!StructDeclaration; 139 mixin V!UnionDeclaration; 140 mixin V!TemplateDeclaration; 141 mixin V!FunctionDeclaration; 142 mixin V!Constructor; 143 144 private: 145 146 mixin template V(T) 147 { 148 override void visit(const T declaration) 149 { 150 import std.traits : hasMember; 151 152 if (currentIsInteresting()) 153 { 154 if (declaration.comment.ptr is null) 155 { 156 static if (hasMember!(T, "name")) 157 { 158 static if (is(T == FunctionDeclaration)) 159 { 160 import std.algorithm : canFind; 161 162 if (!(ignoredFunctionNames.canFind(declaration.name.text) 163 || isGetterOrSetter(declaration.name.text) 164 || isProperty(declaration))) 165 { 166 addMessage(declaration.name.line, 167 declaration.name.column, declaration.name.text); 168 } 169 } 170 else 171 { 172 if (declaration.name.type != tok!"") 173 addMessage(declaration.name.line, 174 declaration.name.column, declaration.name.text); 175 } 176 } 177 else 178 { 179 addMessage(declaration.line, declaration.column, null); 180 } 181 } 182 static if (!(is(T == TemplateDeclaration) || is(T == FunctionDeclaration))) 183 { 184 declaration.accept(this); 185 } 186 } 187 } 188 } 189 190 static bool isGetterOrSetter(string name) 191 { 192 return !matchAll(name, getSetRe).empty; 193 } 194 195 static bool isProperty(const FunctionDeclaration dec) 196 { 197 if (dec.memberFunctionAttributes is null) 198 return false; 199 foreach (attr; dec.memberFunctionAttributes) 200 { 201 if (attr.atAttribute && attr.atAttribute.identifier.text == "property") 202 return true; 203 } 204 return false; 205 } 206 207 void addMessage(size_t line, size_t column, string name) 208 { 209 import std.string : format; 210 211 addErrorMessage(line, column, "dscanner.style.undocumented_declaration", name is null 212 ? "Public declaration is undocumented." 213 : format("Public declaration '%s' is undocumented.", name)); 214 } 215 216 bool getOverride() 217 { 218 return stack[$ - 1].isOverride; 219 } 220 221 void setOverride(bool o = true) 222 { 223 stack[$ - 1].isOverride = o; 224 } 225 226 bool getDisabled() 227 { 228 return stack[$ - 1].isDisabled; 229 } 230 231 void setDisabled(bool d = true) 232 { 233 stack[$ - 1].isDisabled = d; 234 } 235 236 bool getDeprecated() 237 { 238 return stack[$ - 1].isDeprecated; 239 } 240 241 void setDeprecated(bool d = true) 242 { 243 stack[$ - 1].isDeprecated = d; 244 } 245 246 bool currentIsInteresting() 247 { 248 return stack[$ - 1].protection == tok!"public" 249 && !stack[$ - 1].isOverride && !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated; 250 } 251 252 void set(IdType p) 253 in 254 { 255 assert(isProtection(p)); 256 } 257 body 258 { 259 stack[$ - 1].protection = p; 260 } 261 262 void push(IdType p) 263 in 264 { 265 assert(isProtection(p)); 266 } 267 body 268 { 269 stack ~= ProtectionInfo(p, false); 270 } 271 272 void pop() 273 { 274 assert(stack.length > 1); 275 stack = stack[0 .. $ - 1]; 276 } 277 278 static struct ProtectionInfo 279 { 280 IdType protection; 281 bool isOverride; 282 bool isDeprecated; 283 bool isDisabled; 284 } 285 286 ProtectionInfo[] stack; 287 } 288 289 // Ignore undocumented symbols with these names 290 private immutable string[] ignoredFunctionNames = [ 291 "opCmp", "opEquals", "toString", "toHash", "main" 292 ]; 293 294 private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;