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 std.d.ast; 9 import std.d.lexer; 10 import analysis.base; 11 12 import std.regex : ctRegex, matchAll; 13 import std.stdio; 14 15 /** 16 * Checks for undocumented public declarations. Ignores some operator overloads, 17 * main functions, and functions whose name starts with "get" or "set". 18 */ 19 class UndocumentedDeclarationCheck : BaseAnalyzer 20 { 21 alias visit = BaseAnalyzer.visit; 22 23 this(string fileName) 24 { 25 super(fileName); 26 } 27 28 override void visit(const Module mod) 29 { 30 push(tok!"public"); 31 mod.accept(this); 32 } 33 34 override void visit(const Declaration dec) 35 { 36 if (dec.attributeDeclaration) 37 { 38 auto attr = dec.attributeDeclaration.attribute; 39 if (isProtection(attr.attribute.type)) 40 set(attr.attribute.type); 41 else if (attr.attribute == tok!"override") 42 setOverride(true); 43 else if (attr.deprecated_ !is null) 44 setDeprecated(true); 45 else if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable") 46 setDisabled(true); 47 } 48 49 immutable bool shouldPop = dec.attributeDeclaration is null; 50 immutable bool prevOverride = getOverride(); 51 immutable bool prevDisabled = getDisabled(); 52 immutable bool prevDeprecated = getDeprecated(); 53 bool dis = false; 54 bool dep = false; 55 bool ovr = false; 56 bool pushed = false; 57 foreach (attribute; dec.attributes) 58 { 59 if (isProtection(attribute.attribute.type)) 60 { 61 if (shouldPop) 62 { 63 pushed = true; 64 push(attribute.attribute.type); 65 } 66 else 67 set(attribute.attribute.type); 68 } 69 else if (attribute.attribute == tok!"override") 70 ovr = true; 71 else if (attribute.deprecated_ !is null) 72 dep = true; 73 else if (attribute.atAttribute !is null && attribute.atAttribute.identifier.text == "disable") 74 dis = true; 75 } 76 if (ovr) 77 setOverride(true); 78 if (dis) 79 setDisabled(true); 80 if (dep) 81 setDeprecated(true); 82 dec.accept(this); 83 if (shouldPop && pushed) 84 pop(); 85 if (ovr) 86 setOverride(prevOverride); 87 if (dis) 88 setDisabled(prevDisabled); 89 if (dep) 90 setDeprecated(prevDeprecated); 91 } 92 93 override void visit(const VariableDeclaration variable) 94 { 95 if (!currentIsInteresting() || variable.comment !is null) 96 return; 97 if (variable.autoDeclaration !is null) 98 { 99 addMessage(variable.autoDeclaration.identifiers[0].line, 100 variable.autoDeclaration.identifiers[0].column, 101 variable.autoDeclaration.identifiers[0].text); 102 return; 103 } 104 foreach (dec; variable.declarators) 105 { 106 addMessage(dec.name.line, dec.name.column, dec.name.text); 107 return; 108 } 109 } 110 111 override void visit(const ConditionalDeclaration cond) 112 { 113 const VersionCondition ver = cond.compileCondition.versionCondition; 114 if (ver is null || (ver.token != tok!"unittest" && ver.token.text != "none")) 115 cond.accept(this); 116 else if (cond.falseDeclaration !is null) 117 visit(cond.falseDeclaration); 118 } 119 120 override void visit(const FunctionBody fb) {} 121 override void visit(const Unittest u) {} 122 override void visit(const TraitsExpression t) {} 123 124 mixin V!ClassDeclaration; 125 mixin V!InterfaceDeclaration; 126 mixin V!StructDeclaration; 127 mixin V!UnionDeclaration; 128 mixin V!TemplateDeclaration; 129 mixin V!FunctionDeclaration; 130 mixin V!Constructor; 131 132 private: 133 134 mixin template V(T) 135 { 136 override void visit(const T declaration) 137 { 138 import std.traits : hasMember; 139 if (currentIsInteresting()) 140 { 141 if (declaration.comment is null) 142 { 143 static if (hasMember!(T, "name")) 144 { 145 static if (is (T == FunctionDeclaration)) 146 { 147 import std.algorithm : canFind; 148 if (!(ignoredFunctionNames.canFind(declaration.name.text) 149 || isGetterOrSetter(declaration.name.text) 150 || isProperty(declaration))) 151 { 152 addMessage(declaration.name.line, declaration.name.column, 153 declaration.name.text); 154 } 155 } 156 else 157 { 158 if (declaration.name.type != tok!"") 159 addMessage(declaration.name.line, declaration.name.column, 160 declaration.name.text); 161 } 162 } 163 else 164 { 165 addMessage(declaration.line, declaration.column, null); 166 } 167 } 168 static if (!(is (T == TemplateDeclaration) 169 || is(T == FunctionDeclaration))) 170 { 171 declaration.accept(this); 172 } 173 } 174 } 175 } 176 177 static bool isGetterOrSetter(string name) 178 { 179 return !matchAll(name, getSetRe).empty; 180 } 181 182 static bool isProperty(const FunctionDeclaration dec) 183 { 184 if (dec.memberFunctionAttributes is null) 185 return false; 186 foreach (attr; dec.memberFunctionAttributes) 187 { 188 if (attr.atAttribute && attr.atAttribute.identifier.text == "property") 189 return true; 190 } 191 return false; 192 } 193 194 void addMessage(size_t line, size_t column, string name) 195 { 196 import std.string : format; 197 addErrorMessage(line, column, "dscanner.style.undocumented_declaration", 198 name is null ? "Public declaration is undocumented." : 199 format("Public declaration '%s' is undocumented.", name)); 200 } 201 202 bool getOverride() 203 { 204 return stack[$ - 1].isOverride; 205 } 206 207 void setOverride(bool o = true) 208 { 209 stack[$ - 1].isOverride = o; 210 } 211 212 bool getDisabled() 213 { 214 return stack[$ - 1].isDisabled; 215 } 216 217 void setDisabled(bool d = true) 218 { 219 stack[$ - 1].isDisabled = d; 220 } 221 222 bool getDeprecated() 223 { 224 return stack[$ - 1].isDeprecated; 225 } 226 227 void setDeprecated(bool d = true) 228 { 229 stack[$ - 1].isDeprecated = d; 230 } 231 232 bool currentIsInteresting() 233 { 234 return stack[$ - 1].protection == tok!"public" && !stack[$ - 1].isOverride 235 && !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated; 236 } 237 238 void set(IdType p) 239 in { assert (isProtection(p)); } 240 body 241 { 242 stack[$ - 1].protection = p; 243 } 244 245 void push(IdType p) 246 in { assert (isProtection(p)); } 247 body 248 { 249 stack ~= ProtectionInfo(p, false); 250 } 251 252 void pop() 253 { 254 assert (stack.length > 1); 255 stack = stack[0 .. $ - 1]; 256 } 257 258 static struct ProtectionInfo 259 { 260 IdType protection; 261 bool isOverride; 262 bool isDeprecated; 263 bool isDisabled; 264 } 265 266 ProtectionInfo[] stack; 267 } 268 269 // Ignore undocumented symbols with these names 270 private immutable string[] ignoredFunctionNames = [ 271 "opCmp", 272 "opEquals", 273 "toString", 274 "toHash", 275 "main" 276 ]; 277 278 private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;