1 // Distributed under the Boost Software License, Version 1.0. 2 // (See accompanying file LICENSE_1_0.txt or copy at 3 // http://www.boost.org/LICENSE_1_0.txt) 4 5 module analysis.has_public_example; 6 7 import analysis.base; 8 import dsymbol.scope_ : Scope; 9 import dparse.ast; 10 import dparse.lexer; 11 12 import std.algorithm; 13 import std.stdio; 14 15 /** 16 * Checks for public declarations without a documented unittests. 17 * For now, variable and enum declarations aren't checked. 18 */ 19 class HasPublicExampleCheck : BaseAnalyzer 20 { 21 alias visit = BaseAnalyzer.visit; 22 23 this(string fileName, const(Scope)* sc, bool skipTests = false) 24 { 25 super(fileName, sc, skipTests); 26 } 27 28 override void visit(const Module mod) 29 { 30 // the last seen declaration is memorized 31 Declaration lastDecl; 32 33 // keep track of ddoced unittests after visiting lastDecl 34 bool hasNoDdocUnittest; 35 36 // on lastDecl reset we check for seen ddoced unittests since lastDecl was observed 37 void checkLastDecl() 38 { 39 if (lastDecl !is null && hasNoDdocUnittest) 40 triggerError(lastDecl); 41 lastDecl = null; 42 } 43 44 // check all public top-level declarations 45 foreach (decl; mod.declarations) 46 { 47 if (!isPublic(decl.attributes)) 48 { 49 checkLastDecl(); 50 continue; 51 } 52 53 const bool hasDdocHeader = hasDdocHeader(decl); 54 55 // check the documentation of a unittest declaration 56 if (decl.unittest_ !is null) 57 { 58 if (hasDdocHeader) 59 hasNoDdocUnittest = false; 60 } 61 // add all declarations that could be publicly documented to the lastDecl "stack" 62 else if (hasDittableDecl(decl)) 63 { 64 // ignore dittoed declarations 65 if (hasDittos(decl)) 66 continue; 67 68 // new public symbol -> check the previous decl 69 checkLastDecl; 70 71 lastDecl = hasDdocHeader ? cast(Declaration) decl : null; 72 hasNoDdocUnittest = true; 73 } 74 else 75 // ran into variableDeclaration or something else -> reset & validate current lastDecl "stack" 76 checkLastDecl; 77 } 78 checkLastDecl; 79 } 80 81 private: 82 83 bool hasDitto(Decl)(const Decl decl) 84 { 85 import ddoc.comments : parseComment; 86 if (decl is null || decl.comment is null) 87 return false; 88 89 return parseComment(decl.comment, null).isDitto; 90 } 91 92 bool hasDittos(Decl)(const Decl decl) 93 { 94 foreach (property; possibleDeclarations) 95 if (mixin("hasDitto(decl." ~ property ~ ")")) 96 return true; 97 return false; 98 } 99 100 bool hasDittableDecl(Decl)(const Decl decl) 101 { 102 foreach (property; possibleDeclarations) 103 if (mixin("decl." ~ property ~ " !is null")) 104 return true; 105 return false; 106 } 107 108 import std.meta : AliasSeq; 109 alias possibleDeclarations = AliasSeq!( 110 "classDeclaration", 111 "enumDeclaration", 112 "functionDeclaration", 113 "interfaceDeclaration", 114 "structDeclaration", 115 "templateDeclaration", 116 "unionDeclaration", 117 //"variableDeclaration", 118 ); 119 120 bool hasDdocHeader(const Declaration decl) 121 { 122 if (decl.declarations !is null) 123 return false; 124 125 // unittest can have ddoc headers as well, but don't have a name 126 if (decl.unittest_ !is null && decl.unittest_.comment.ptr !is null) 127 return true; 128 129 foreach (property; possibleDeclarations) 130 if (mixin("decl." ~ property ~ " !is null && decl." ~ property ~ ".comment.ptr !is null")) 131 return true; 132 133 return false; 134 } 135 136 bool isPublic(const Attribute[] attrs) 137 { 138 import dparse.lexer : tok; 139 140 enum tokPrivate = tok!"private", tokProtected = tok!"protected", tokPackage = tok!"package"; 141 142 if (attrs.map!`a.attribute`.any!(x => x == tokPrivate || x == tokProtected || x == tokPackage)) 143 return false; 144 145 return true; 146 } 147 148 void triggerError(const Declaration decl) 149 { 150 foreach (property; possibleDeclarations) 151 if (auto fn = mixin("decl." ~ property)) 152 addMessage(fn.name.line, fn.name.column, fn.name.text); 153 } 154 155 void addMessage(size_t line, size_t column, string name) 156 { 157 import std..string : format; 158 159 addErrorMessage(line, column, "dscanner.style.has_public_example", name is null 160 ? "Public declaration has no documented example." 161 : format("Public declaration '%s' has no documented example.", name)); 162 } 163 } 164 165 unittest 166 { 167 import std.stdio : stderr; 168 import std.format : format; 169 import analysis.config : StaticAnalysisConfig, Check, disabledConfig; 170 import analysis.helpers : assertAnalyzerWarnings; 171 172 StaticAnalysisConfig sac = disabledConfig(); 173 sac.has_public_example = Check.enabled; 174 175 assertAnalyzerWarnings(q{ 176 /// C 177 class C{} 178 /// 179 unittest {} 180 181 /// I 182 interface I{} 183 /// 184 unittest {} 185 186 /// e 187 enum e = 0; 188 /// 189 unittest {} 190 191 /// f 192 void f(){} 193 /// 194 unittest {} 195 196 /// S 197 struct S{} 198 /// 199 unittest {} 200 201 /// T 202 template T(){} 203 /// 204 unittest {} 205 206 /// U 207 union U{} 208 /// 209 unittest {} 210 }, sac); 211 212 // enums or variables don't need to have public unittest 213 assertAnalyzerWarnings(q{ 214 /// C 215 class C{} // [warn]: Public declaration 'C' has no documented example. 216 unittest {} 217 218 /// I 219 interface I{} // [warn]: Public declaration 'I' has no documented example. 220 unittest {} 221 222 /// f 223 void f(){} // [warn]: Public declaration 'f' has no documented example. 224 unittest {} 225 226 /// S 227 struct S{} // [warn]: Public declaration 'S' has no documented example. 228 unittest {} 229 230 /// T 231 template T(){} // [warn]: Public declaration 'T' has no documented example. 232 unittest {} 233 234 /// U 235 union U{} // [warn]: Public declaration 'U' has no documented example. 236 unittest {} 237 }, sac); 238 239 // test module header unittest 240 assertAnalyzerWarnings(q{ 241 unittest {} 242 /// C 243 class C{} // [warn]: Public declaration 'C' has no documented example. 244 }, sac); 245 246 // test documented module header unittest 247 assertAnalyzerWarnings(q{ 248 /// 249 unittest {} 250 /// C 251 class C{} // [warn]: Public declaration 'C' has no documented example. 252 }, sac); 253 254 // test multiple unittest blocks 255 assertAnalyzerWarnings(q{ 256 /// C 257 class C{} // [warn]: Public declaration 'C' has no documented example. 258 unittest {} 259 unittest {} 260 unittest {} 261 262 /// U 263 union U{} 264 unittest {} 265 /// 266 unittest {} 267 unittest {} 268 }, sac); 269 270 /// check private 271 assertAnalyzerWarnings(q{ 272 /// C 273 private class C{} 274 275 /// I 276 protected interface I{} 277 278 /// e 279 package enum e = 0; 280 281 /// f 282 package(std) void f(){} 283 284 /// S 285 extern(C) struct S{} 286 /// 287 unittest {} 288 }, sac); 289 290 // check intermediate private declarations 291 // removed for issue #500 292 /*assertAnalyzerWarnings(q{ 293 /// C 294 class C{} 295 private void foo(){} 296 /// 297 unittest {} 298 }, sac);*/ 299 300 // check intermediate ditto-ed declarations 301 assertAnalyzerWarnings(q{ 302 /// I 303 interface I{} 304 /// ditto 305 void f(){} 306 /// 307 unittest {} 308 }, sac); 309 310 // test reset on private symbols (#500) 311 assertAnalyzerWarnings(q{ 312 /// 313 void dirName(C)(C[] path) {} // [warn]: Public declaration 'dirName' has no documented example. 314 private void _dirName(R)(R path) {} 315 /// 316 unittest {} 317 }, sac); 318 319 stderr.writeln("Unittest for HasPublicExampleCheck passed."); 320 } 321