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 && attribute.atAttribute.identifier.text == "disable") 75 dis = true; 76 } 77 if (ovr) 78 setOverride(true); 79 if (dis) 80 setDisabled(true); 81 if (dep) 82 setDeprecated(true); 83 dec.accept(this); 84 if (shouldPop && pushed) 85 pop(); 86 if (ovr) 87 setOverride(prevOverride); 88 if (dis) 89 setDisabled(prevDisabled); 90 if (dep) 91 setDeprecated(prevDeprecated); 92 } 93 94 override void visit(const VariableDeclaration variable) 95 { 96 if (!currentIsInteresting() || variable.comment !is null) 97 return; 98 if (variable.autoDeclaration !is null) 99 { 100 addMessage(variable.autoDeclaration.identifiers[0].line, 101 variable.autoDeclaration.identifiers[0].column, 102 variable.autoDeclaration.identifiers[0].text); 103 return; 104 } 105 foreach (dec; variable.declarators) 106 { 107 addMessage(dec.name.line, dec.name.column, dec.name.text); 108 return; 109 } 110 } 111 112 override void visit(const ConditionalDeclaration cond) 113 { 114 const VersionCondition ver = cond.compileCondition.versionCondition; 115 if (ver is null || (ver.token != tok!"unittest" && ver.token.text != "none")) 116 cond.accept(this); 117 else if (cond.falseDeclarations.length > 0) 118 foreach (f; cond.falseDeclarations) 119 visit(f); 120 } 121 122 override void visit(const FunctionBody fb) {} 123 override void visit(const Unittest u) {} 124 override void visit(const TraitsExpression t) {} 125 126 mixin V!ClassDeclaration; 127 mixin V!InterfaceDeclaration; 128 mixin V!StructDeclaration; 129 mixin V!UnionDeclaration; 130 mixin V!TemplateDeclaration; 131 mixin V!FunctionDeclaration; 132 mixin V!Constructor; 133 134 private: 135 136 mixin template V(T) 137 { 138 override void visit(const T declaration) 139 { 140 import std.traits : hasMember; 141 if (currentIsInteresting()) 142 { 143 if (declaration.comment is null) 144 { 145 static if (hasMember!(T, "name")) 146 { 147 static if (is (T == FunctionDeclaration)) 148 { 149 import std.algorithm : canFind; 150 if (!(ignoredFunctionNames.canFind(declaration.name.text) 151 || isGetterOrSetter(declaration.name.text) 152 || isProperty(declaration))) 153 { 154 addMessage(declaration.name.line, declaration.name.column, 155 declaration.name.text); 156 } 157 } 158 else 159 { 160 if (declaration.name.type != tok!"") 161 addMessage(declaration.name.line, declaration.name.column, 162 declaration.name.text); 163 } 164 } 165 else 166 { 167 addMessage(declaration.line, declaration.column, null); 168 } 169 } 170 static if (!(is (T == TemplateDeclaration) 171 || is(T == FunctionDeclaration))) 172 { 173 declaration.accept(this); 174 } 175 } 176 } 177 } 178 179 static bool isGetterOrSetter(string name) 180 { 181 return !matchAll(name, getSetRe).empty; 182 } 183 184 static bool isProperty(const FunctionDeclaration dec) 185 { 186 if (dec.memberFunctionAttributes is null) 187 return false; 188 foreach (attr; dec.memberFunctionAttributes) 189 { 190 if (attr.atAttribute && attr.atAttribute.identifier.text == "property") 191 return true; 192 } 193 return false; 194 } 195 196 void addMessage(size_t line, size_t column, string name) 197 { 198 import std.string : format; 199 addErrorMessage(line, column, "dscanner.style.undocumented_declaration", 200 name is null ? "Public declaration is undocumented." : 201 format("Public declaration '%s' is undocumented.", name)); 202 } 203 204 bool getOverride() 205 { 206 return stack[$ - 1].isOverride; 207 } 208 209 void setOverride(bool o = true) 210 { 211 stack[$ - 1].isOverride = o; 212 } 213 214 bool getDisabled() 215 { 216 return stack[$ - 1].isDisabled; 217 } 218 219 void setDisabled(bool d = true) 220 { 221 stack[$ - 1].isDisabled = d; 222 } 223 224 bool getDeprecated() 225 { 226 return stack[$ - 1].isDeprecated; 227 } 228 229 void setDeprecated(bool d = true) 230 { 231 stack[$ - 1].isDeprecated = d; 232 } 233 234 bool currentIsInteresting() 235 { 236 return stack[$ - 1].protection == tok!"public" && !stack[$ - 1].isOverride 237 && !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated; 238 } 239 240 void set(IdType p) 241 in { assert (isProtection(p)); } 242 body 243 { 244 stack[$ - 1].protection = p; 245 } 246 247 void push(IdType p) 248 in { assert (isProtection(p)); } 249 body 250 { 251 stack ~= ProtectionInfo(p, false); 252 } 253 254 void pop() 255 { 256 assert (stack.length > 1); 257 stack = stack[0 .. $ - 1]; 258 } 259 260 static struct ProtectionInfo 261 { 262 IdType protection; 263 bool isOverride; 264 bool isDeprecated; 265 bool isDisabled; 266 } 267 268 ProtectionInfo[] stack; 269 } 270 271 // Ignore undocumented symbols with these names 272 private immutable string[] ignoredFunctionNames = [ 273 "opCmp", 274 "opEquals", 275 "toString", 276 "toHash", 277 "main" 278 ]; 279 280 private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;