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 dscanner.analysis.properly_documented_public_functions; 6 7 import dparse.lexer; 8 import dparse.ast; 9 import dparse.formatter : astFmt = format; 10 import dscanner.analysis.base : BaseAnalyzer; 11 12 import std.format : format; 13 import std.range.primitives; 14 import std.stdio; 15 16 /** 17 * Requires each public function to contain the following ddoc sections 18 - PARAMS: 19 - if the function has at least one parameter 20 - every parameter must have a ddoc params entry (applies for template paramters too) 21 - Ddoc params entries without a parameter trigger warnings as well 22 - RETURNS: (except if it's void, only functions) 23 */ 24 class ProperlyDocumentedPublicFunctions : BaseAnalyzer 25 { 26 enum string MISSING_PARAMS_KEY = "dscanner.style.doc_missing_params"; 27 enum string MISSING_PARAMS_MESSAGE = "Parameter %s isn't documented in the `Params` section."; 28 enum string MISSING_TEMPLATE_PARAMS_MESSAGE 29 = "Template parameters %s isn't documented in the `Params` section."; 30 31 enum string NON_EXISTENT_PARAMS_KEY = "dscanner.style.doc_non_existing_params"; 32 enum string NON_EXISTENT_PARAMS_MESSAGE = "Documented parameter %s isn't a function parameter."; 33 34 enum string MISSING_RETURNS_KEY = "dscanner.style.doc_missing_returns"; 35 enum string MISSING_RETURNS_MESSAGE = "A public function needs to contain a `Returns` section."; 36 37 enum string MISSING_THROW_KEY = "dscanner.style.doc_missing_throw"; 38 enum string MISSING_THROW_MESSAGE = "An instance of `%s` is thrown but not documented in the `Throws` section"; 39 40 /// 41 this(string fileName, bool skipTests = false) 42 { 43 super(fileName, null, skipTests); 44 } 45 46 override void visit(const Module mod) 47 { 48 islastSeenVisibilityLabelPublic = true; 49 mod.accept(this); 50 postCheckSeenDdocParams(); 51 } 52 53 override void visit(const Declaration decl) 54 { 55 import std.algorithm.searching : any; 56 import std.algorithm.iteration : map; 57 58 // skip private symbols 59 enum tokPrivate = tok!"private", 60 tokProtected = tok!"protected", 61 tokPackage = tok!"package", 62 tokPublic = tok!"public"; 63 64 // Nested funcs for `Throws` 65 bool decNestedFunc; 66 if (decl.functionDeclaration) 67 { 68 nestedFuncs++; 69 decNestedFunc = true; 70 } 71 scope(exit) 72 { 73 if (decNestedFunc) 74 nestedFuncs--; 75 } 76 if (nestedFuncs > 1) 77 { 78 decl.accept(this); 79 return; 80 } 81 82 if (decl.attributes.length > 0) 83 { 84 const bool isPublic = !decl.attributes.map!`a.attribute`.any!(x => x == tokPrivate || 85 x == tokProtected || 86 x == tokPackage); 87 // recognize label blocks 88 if (!hasDeclaration(decl)) 89 islastSeenVisibilityLabelPublic = isPublic; 90 91 if (!isPublic) 92 return; 93 } 94 95 if (islastSeenVisibilityLabelPublic || decl.attributes.map!`a.attribute`.any!(x => x == tokPublic)) 96 { 97 // Don't complain about non-documented function declarations 98 if ((decl.functionDeclaration !is null && decl.functionDeclaration.comment.ptr !is null) || 99 (decl.templateDeclaration !is null && decl.templateDeclaration.comment.ptr !is null) || 100 decl.mixinTemplateDeclaration !is null || 101 (decl.classDeclaration !is null && decl.classDeclaration.comment.ptr !is null) || 102 (decl.structDeclaration !is null && decl.structDeclaration.comment.ptr !is null)) 103 decl.accept(this); 104 } 105 } 106 107 override void visit(const TemplateDeclaration decl) 108 { 109 setLastDdocParams(decl.name.line, decl.name.column, decl.comment); 110 checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters); 111 112 withinTemplate = true; 113 scope(exit) withinTemplate = false; 114 decl.accept(this); 115 } 116 117 override void visit(const MixinTemplateDeclaration decl) 118 { 119 decl.accept(this); 120 } 121 122 override void visit(const StructDeclaration decl) 123 { 124 setLastDdocParams(decl.name.line, decl.name.column, decl.comment); 125 checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters); 126 decl.accept(this); 127 } 128 129 override void visit(const ClassDeclaration decl) 130 { 131 setLastDdocParams(decl.name.line, decl.name.column, decl.comment); 132 checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters); 133 decl.accept(this); 134 } 135 136 override void visit(const FunctionDeclaration decl) 137 { 138 import std.algorithm.searching : all, any; 139 import std.array : Appender; 140 141 // ignore header declaration for now 142 if (decl.functionBody is null) 143 return; 144 145 if (nestedFuncs == 1) 146 thrown.length = 0; 147 // detect ThrowStatement only if not nothrow 148 if (!decl.attributes.any!(a => a.attribute.text == "nothrow")) 149 { 150 decl.accept(this); 151 if (nestedFuncs == 1 && !hasThrowSection(decl.comment)) 152 foreach(t; thrown) 153 { 154 Appender!(char[]) app; 155 astFmt(&app, t); 156 addErrorMessage(decl.name.line, decl.name.column, MISSING_THROW_KEY, 157 MISSING_THROW_MESSAGE.format(app.data)); 158 } 159 } 160 161 if (nestedFuncs == 1) 162 { 163 auto comment = setLastDdocParams(decl.name.line, decl.name.column, decl.comment); 164 checkDdocParams(decl.name.line, decl.name.column, decl.parameters, decl.templateParameters); 165 enum voidType = tok!"void"; 166 if (decl.returnType is null || decl.returnType.type2.builtinType != voidType) 167 if (!(comment.isDitto || withinTemplate || comment.sections.any!(s => s.name == "Returns"))) 168 addErrorMessage(decl.name.line, decl.name.column, 169 MISSING_RETURNS_KEY, MISSING_RETURNS_MESSAGE); 170 } 171 } 172 173 // remove thrown Type that are caught 174 override void visit(const TryStatement ts) 175 { 176 import std.algorithm.iteration : filter; 177 import std.algorithm.searching : canFind; 178 import std.array : array; 179 180 ts.accept(this); 181 182 if (ts.catches) 183 thrown = thrown.filter!(a => !ts.catches.catches 184 .canFind!(b => b.type == a)) 185 .array; 186 } 187 188 override void visit(const ThrowStatement ts) 189 { 190 import std.algorithm.searching : canFind; 191 192 ts.accept(this); 193 if (ts.expression && ts.expression.items.length == 1) 194 if (const UnaryExpression ue = cast(UnaryExpression) ts.expression.items[0]) 195 { 196 if (ue.newExpression && ue.newExpression.type && 197 !thrown.canFind!(a => a == ue.newExpression.type)) 198 { 199 thrown ~= ue.newExpression.type; 200 } 201 } 202 } 203 204 alias visit = BaseAnalyzer.visit; 205 206 private: 207 bool islastSeenVisibilityLabelPublic; 208 bool withinTemplate; 209 size_t nestedFuncs; 210 211 static struct Function 212 { 213 bool active; 214 size_t line, column; 215 const(string)[] ddocParams; 216 bool[string] params; 217 } 218 Function lastSeenFun; 219 220 const(Type)[] thrown; 221 222 // find invalid ddoc parameters (i.e. they don't occur in a function declaration) 223 void postCheckSeenDdocParams() 224 { 225 import std.format : format; 226 227 if (lastSeenFun.active) 228 foreach (p; lastSeenFun.ddocParams) 229 if (p !in lastSeenFun.params) 230 addErrorMessage(lastSeenFun.line, lastSeenFun.column, NON_EXISTENT_PARAMS_KEY, 231 NON_EXISTENT_PARAMS_MESSAGE.format(p)); 232 233 lastSeenFun.active = false; 234 } 235 236 bool hasThrowSection(string commentText) 237 { 238 import std.algorithm.searching : canFind; 239 import ddoc.comments : parseComment; 240 241 const comment = parseComment(commentText, null); 242 return comment.isDitto || comment.sections.canFind!(s => s.name == "Throws"); 243 } 244 245 auto setLastDdocParams(size_t line, size_t column, string commentText) 246 { 247 import ddoc.comments : parseComment; 248 import std.algorithm.searching : find; 249 import std.algorithm.iteration : map; 250 import std.array : array; 251 252 const comment = parseComment(commentText, null); 253 if (withinTemplate) 254 { 255 const paramSection = comment.sections.find!(s => s.name == "Params"); 256 if (!paramSection.empty) 257 lastSeenFun.ddocParams ~= paramSection[0].mapping.map!(a => a[0]).array; 258 } 259 else if (!comment.isDitto) 260 { 261 // check old function for invalid ddoc params 262 if (lastSeenFun.active) 263 postCheckSeenDdocParams(); 264 265 const paramSection = comment.sections.find!(s => s.name == "Params"); 266 if (paramSection.empty) 267 { 268 lastSeenFun = Function(true, line, column, null); 269 } 270 else 271 { 272 auto ddocParams = paramSection[0].mapping.map!(a => a[0]).array; 273 lastSeenFun = Function(true, line, column, ddocParams); 274 } 275 } 276 277 return comment; 278 } 279 280 void checkDdocParams(size_t line, size_t column, const Parameters params, 281 const TemplateParameters templateParameters = null) 282 { 283 import std.array : array; 284 import std.algorithm.searching : canFind, countUntil; 285 import std.algorithm.iteration : map; 286 import std.algorithm.mutation : remove; 287 import std.range : indexed, iota; 288 289 // convert templateParameters into a string[] for faster access 290 const(TemplateParameter)[] templateList; 291 if (const tp = templateParameters) 292 if (const tpl = tp.templateParameterList) 293 templateList = tpl.items; 294 string[] tlList = templateList.map!(a => templateParamName(a)).array; 295 296 // make a copy of all parameters and remove the seen ones later during the loop 297 size_t[] unseenTemplates = templateList.length.iota.array; 298 299 if (lastSeenFun.active && params !is null) 300 foreach (p; params.parameters) 301 { 302 string templateName; 303 if (const t = p.type) 304 if (const t2 = t.type2) 305 if (const tip = t2.typeIdentifierPart) 306 if (const iot = tip.identifierOrTemplateInstance) 307 templateName = iot.identifier.text; 308 309 const idx = tlList.countUntil(templateName); 310 if (idx >= 0) 311 { 312 unseenTemplates = unseenTemplates.remove(idx); 313 tlList = tlList.remove(idx); 314 // documenting template parameter should be allowed 315 lastSeenFun.params[templateName] = true; 316 } 317 318 if (!lastSeenFun.ddocParams.canFind(p.name.text)) 319 addErrorMessage(line, column, MISSING_PARAMS_KEY, 320 format(MISSING_PARAMS_MESSAGE, p.name.text)); 321 else 322 lastSeenFun.params[p.name.text] = true; 323 } 324 325 // now check the remaining, not used template parameters 326 auto unseenTemplatesArr = templateList.indexed(unseenTemplates).array; 327 checkDdocParams(line, column, unseenTemplatesArr); 328 } 329 330 void checkDdocParams(size_t line, size_t column, const TemplateParameters templateParams) 331 { 332 if (lastSeenFun.active && templateParams !is null && 333 templateParams.templateParameterList !is null) 334 checkDdocParams(line, column, templateParams.templateParameterList.items); 335 } 336 337 void checkDdocParams(size_t line, size_t column, const TemplateParameter[] templateParams) 338 { 339 import std.algorithm.searching : canFind; 340 foreach (p; templateParams) 341 { 342 const name = templateParamName(p); 343 assert(name, "Invalid template parameter name."); // this shouldn't happen 344 if (!lastSeenFun.ddocParams.canFind(name)) 345 addErrorMessage(line, column, MISSING_PARAMS_KEY, 346 format(MISSING_TEMPLATE_PARAMS_MESSAGE, name)); 347 else 348 lastSeenFun.params[name] = true; 349 } 350 } 351 352 static string templateParamName(const TemplateParameter p) 353 { 354 if (p.templateTypeParameter) 355 return p.templateTypeParameter.identifier.text; 356 if (p.templateValueParameter) 357 return p.templateValueParameter.identifier.text; 358 if (p.templateAliasParameter) 359 return p.templateAliasParameter.identifier.text; 360 if (p.templateTupleParameter) 361 return p.templateTupleParameter.identifier.text; 362 if (p.templateThisParameter) 363 return p.templateThisParameter.templateTypeParameter.identifier.text; 364 365 return null; 366 } 367 368 bool hasDeclaration(const Declaration decl) 369 { 370 import std.meta : AliasSeq; 371 alias properties = AliasSeq!( 372 "aliasDeclaration", 373 "aliasThisDeclaration", 374 "anonymousEnumDeclaration", 375 "attributeDeclaration", 376 "classDeclaration", 377 "conditionalDeclaration", 378 "constructor", 379 "debugSpecification", 380 "destructor", 381 "enumDeclaration", 382 "eponymousTemplateDeclaration", 383 "functionDeclaration", 384 "importDeclaration", 385 "interfaceDeclaration", 386 "invariant_", 387 "mixinDeclaration", 388 "mixinTemplateDeclaration", 389 "postblit", 390 "pragmaDeclaration", 391 "sharedStaticConstructor", 392 "sharedStaticDestructor", 393 "staticAssertDeclaration", 394 "staticConstructor", 395 "staticDestructor", 396 "structDeclaration", 397 "templateDeclaration", 398 "unionDeclaration", 399 "unittest_", 400 "variableDeclaration", 401 "versionSpecification", 402 ); 403 if (decl.declarations !is null) 404 return false; 405 406 auto isNull = true; 407 foreach (property; properties) 408 if (mixin("decl." ~ property ~ " !is null")) 409 isNull = false; 410 411 return !isNull; 412 } 413 } 414 415 version(unittest) 416 { 417 import std.stdio : stderr; 418 import std.format : format; 419 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 420 import dscanner.analysis.helpers : assertAnalyzerWarnings; 421 } 422 423 // missing params 424 unittest 425 { 426 StaticAnalysisConfig sac = disabledConfig; 427 sac.properly_documented_public_functions = Check.enabled; 428 429 assertAnalyzerWarnings(q{ 430 /** 431 Some text 432 */ 433 void foo(int k){} // [warn]: %s 434 }c.format( 435 ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k") 436 ), sac); 437 438 assertAnalyzerWarnings(q{ 439 /** 440 Some text 441 */ 442 void foo(int K)(){} // [warn]: %s 443 }c.format( 444 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("K") 445 ), sac); 446 447 assertAnalyzerWarnings(q{ 448 /** 449 Some text 450 */ 451 struct Foo(Bar){} // [warn]: %s 452 }c.format( 453 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") 454 ), sac); 455 456 assertAnalyzerWarnings(q{ 457 /** 458 Some text 459 */ 460 class Foo(Bar){} // [warn]: %s 461 }c.format( 462 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") 463 ), sac); 464 465 assertAnalyzerWarnings(q{ 466 /** 467 Some text 468 */ 469 template Foo(Bar){} // [warn]: %s 470 }c.format( 471 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") 472 ), sac); 473 474 475 // test no parameters 476 assertAnalyzerWarnings(q{ 477 /** Some text */ 478 void foo(){} 479 }c, sac); 480 481 assertAnalyzerWarnings(q{ 482 /** Some text */ 483 struct Foo(){} 484 }c, sac); 485 486 assertAnalyzerWarnings(q{ 487 /** Some text */ 488 class Foo(){} 489 }c, sac); 490 491 assertAnalyzerWarnings(q{ 492 /** Some text */ 493 template Foo(){} 494 }c, sac); 495 496 } 497 498 // missing returns (only functions) 499 unittest 500 { 501 StaticAnalysisConfig sac = disabledConfig; 502 sac.properly_documented_public_functions = Check.enabled; 503 504 assertAnalyzerWarnings(q{ 505 /** 506 Some text 507 */ 508 int foo(){} // [warn]: %s 509 }c.format( 510 ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, 511 ), sac); 512 513 assertAnalyzerWarnings(q{ 514 /** 515 Some text 516 */ 517 auto foo(){} // [warn]: %s 518 }c.format( 519 ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, 520 ), sac); 521 } 522 523 // ignore private 524 unittest 525 { 526 StaticAnalysisConfig sac = disabledConfig; 527 sac.properly_documented_public_functions = Check.enabled; 528 529 assertAnalyzerWarnings(q{ 530 /** 531 Some text 532 */ 533 private void foo(int k){} 534 }c, sac); 535 536 // with block 537 assertAnalyzerWarnings(q{ 538 private: 539 /** 540 Some text 541 */ 542 private void foo(int k){} 543 /// 544 public int bar(){} // [warn]: %s 545 public: 546 /// 547 int foobar(){} // [warn]: %s 548 }c.format( 549 ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, 550 ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, 551 ), sac); 552 553 // with block (template) 554 assertAnalyzerWarnings(q{ 555 private: 556 /** 557 Some text 558 */ 559 private template foo(int k){} 560 /// 561 public template bar(T){} // [warn]: %s 562 public: 563 /// 564 template foobar(T){} // [warn]: %s 565 }c.format( 566 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), 567 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), 568 ), sac); 569 570 // with block (struct) 571 assertAnalyzerWarnings(q{ 572 private: 573 /** 574 Some text 575 */ 576 private struct foo(int k){} 577 /// 578 public struct bar(T){} // [warn]: %s 579 public: 580 /// 581 struct foobar(T){} // [warn]: %s 582 }c.format( 583 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), 584 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), 585 ), sac); 586 } 587 588 // test parameter names 589 unittest 590 { 591 StaticAnalysisConfig sac = disabledConfig; 592 sac.properly_documented_public_functions = Check.enabled; 593 594 assertAnalyzerWarnings(q{ 595 /** 596 * Description. 597 * 598 * Params: 599 * 600 * Returns: 601 * A long description. 602 */ 603 int foo(int k){} // [warn]: %s 604 }c.format( 605 ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k") 606 ), sac); 607 608 assertAnalyzerWarnings(q{ 609 /** 610 Description. 611 612 Params: 613 val = A stupid parameter 614 k = A stupid parameter 615 616 Returns: 617 A long description. 618 */ 619 int foo(int k){} // [warn]: %s 620 }c.format( 621 ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("val") 622 ), sac); 623 624 assertAnalyzerWarnings(q{ 625 /** 626 Description. 627 628 Params: 629 630 Returns: 631 A long description. 632 */ 633 int foo(int k){} // [warn]: %s 634 }c.format( 635 ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k") 636 ), sac); 637 638 assertAnalyzerWarnings(q{ 639 /** 640 Description. 641 642 Params: 643 foo = A stupid parameter 644 bad = A stupid parameter (does not exist) 645 foobar = A stupid parameter 646 647 Returns: 648 A long description. 649 */ 650 int foo(int foo, int foobar){} // [warn]: %s 651 }c.format( 652 ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad") 653 ), sac); 654 655 assertAnalyzerWarnings(q{ 656 /** 657 Description. 658 659 Params: 660 foo = A stupid parameter 661 bad = A stupid parameter (does not exist) 662 foobar = A stupid parameter 663 664 Returns: 665 A long description. 666 */ 667 struct foo(int foo, int foobar){} // [warn]: %s 668 }c.format( 669 ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad") 670 ), sac); 671 672 // properly documented 673 assertAnalyzerWarnings(q{ 674 /** 675 Description. 676 677 Params: 678 foo = A stupid parameter 679 bar = A stupid parameter 680 681 Returns: 682 A long description. 683 */ 684 int foo(int foo, int bar){} 685 }c, sac); 686 687 assertAnalyzerWarnings(q{ 688 /** 689 Description. 690 691 Params: 692 foo = A stupid parameter 693 bar = A stupid parameter 694 695 Returns: 696 A long description. 697 */ 698 struct foo(int foo, int bar){} 699 }c, sac); 700 } 701 702 // support ditto 703 unittest 704 { 705 StaticAnalysisConfig sac = disabledConfig; 706 sac.properly_documented_public_functions = Check.enabled; 707 708 assertAnalyzerWarnings(q{ 709 /** 710 * Description. 711 * 712 * Params: 713 * k = A stupid parameter 714 * 715 * Returns: 716 * A long description. 717 */ 718 int foo(int k){} 719 720 /// ditto 721 int bar(int k){} 722 }c, sac); 723 724 assertAnalyzerWarnings(q{ 725 /** 726 * Description. 727 * 728 * Params: 729 * k = A stupid parameter 730 * K = A stupid parameter 731 * 732 * Returns: 733 * A long description. 734 */ 735 int foo(int k){} 736 737 /// ditto 738 struct Bar(K){} 739 }c, sac); 740 741 assertAnalyzerWarnings(q{ 742 /** 743 * Description. 744 * 745 * Params: 746 * k = A stupid parameter 747 * f = A stupid parameter 748 * 749 * Returns: 750 * A long description. 751 */ 752 int foo(int k){} 753 754 /// ditto 755 int bar(int f){} 756 }c, sac); 757 758 assertAnalyzerWarnings(q{ 759 /** 760 * Description. 761 * 762 * Params: 763 * k = A stupid parameter 764 * 765 * Returns: 766 * A long description. 767 */ 768 int foo(int k){} 769 770 /// ditto 771 int bar(int bar){} // [warn]: %s 772 }c.format( 773 ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("bar") 774 ), sac); 775 776 assertAnalyzerWarnings(q{ 777 /** 778 * Description. 779 * 780 * Params: 781 * k = A stupid parameter 782 * bar = A stupid parameter 783 * f = A stupid parameter 784 * 785 * Returns: 786 * A long description. 787 * See_Also: 788 * $(REF takeExactly, std,range) 789 */ 790 int foo(int k){} // [warn]: %s 791 792 /// ditto 793 int bar(int bar){} 794 }c.format( 795 ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("f") 796 ), sac); 797 } 798 799 // check correct ddoc headers 800 unittest 801 { 802 StaticAnalysisConfig sac = disabledConfig; 803 sac.properly_documented_public_functions = Check.enabled; 804 805 assertAnalyzerWarnings(q{ 806 /++ 807 Counts elements in the given 808 $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 809 until the given predicate is true for one of the given $(D needles). 810 811 Params: 812 val = A stupid parameter 813 814 Returns: Awesome values. 815 +/ 816 string bar(string val){} 817 }c, sac); 818 819 assertAnalyzerWarnings(q{ 820 /++ 821 Counts elements in the given 822 $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 823 until the given predicate is true for one of the given $(D needles). 824 825 Params: 826 val = A stupid parameter 827 828 Returns: Awesome values. 829 +/ 830 template bar(string val){} 831 }c, sac); 832 833 } 834 835 unittest 836 { 837 StaticAnalysisConfig sac = disabledConfig; 838 sac.properly_documented_public_functions = Check.enabled; 839 840 assertAnalyzerWarnings(q{ 841 /** 842 * Ddoc for the inner function appears here. 843 * This function is declared this way to allow for multiple variable-length 844 * template argument lists. 845 * --- 846 * abcde!("a", "b", "c")(100, x, y, z); 847 * --- 848 * Params: 849 * Args = foo 850 * U = bar 851 * T = barr 852 * varargs = foobar 853 * t = foo 854 * Returns: bar 855 */ 856 template abcde(Args ...) { 857 /// 858 auto abcde(T, U...)(T t, U varargs) { 859 /// .... 860 } 861 } 862 }c, sac); 863 } 864 865 // Don't force the documentation of the template parameter if it's a used type in the parameter list 866 unittest 867 { 868 StaticAnalysisConfig sac = disabledConfig; 869 sac.properly_documented_public_functions = Check.enabled; 870 871 assertAnalyzerWarnings(q{ 872 /++ 873 An awesome description. 874 875 Params: 876 r = an input range. 877 878 Returns: Awesome values. 879 +/ 880 string bar(R)(R r){} 881 }c, sac); 882 883 assertAnalyzerWarnings(q{ 884 /++ 885 An awesome description. 886 887 Params: 888 r = an input range. 889 890 Returns: Awesome values. 891 +/ 892 string bar(P, R)(R r){}// [warn]: %s 893 }c.format( 894 ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("P") 895 ), sac); 896 } 897 898 // https://github.com/dlang-community/D-Scanner/issues/601 899 unittest 900 { 901 StaticAnalysisConfig sac = disabledConfig; 902 sac.properly_documented_public_functions = Check.enabled; 903 904 assertAnalyzerWarnings(q{ 905 void put(Range)(Range items) if (canPutConstRange!Range) 906 { 907 alias p = put!(Unqual!Range); 908 p(items); 909 } 910 }, sac); 911 } 912 913 unittest 914 { 915 StaticAnalysisConfig sac = disabledConfig; 916 sac.properly_documented_public_functions = Check.enabled; 917 918 assertAnalyzerWarnings(q{ 919 /++ 920 Throw but likely catched. 921 +/ 922 void bar(){ 923 try{throw new Exception("bla");throw new Error("bla");} 924 catch(Exception){} catch(Error){}} 925 }c, sac); 926 } 927 928 unittest 929 { 930 StaticAnalysisConfig sac = disabledConfig; 931 sac.properly_documented_public_functions = Check.enabled; 932 933 assertAnalyzerWarnings(q{ 934 /++ 935 Simple case 936 +/ 937 void bar(){throw new Exception("bla");} // [warn]: %s 938 }c.format( 939 ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Exception") 940 ), sac); 941 } 942 943 unittest 944 { 945 StaticAnalysisConfig sac = disabledConfig; 946 sac.properly_documented_public_functions = Check.enabled; 947 948 assertAnalyzerWarnings(q{ 949 /++ 950 Supposed to be documented 951 952 Throws: Exception if... 953 +/ 954 void bar(){throw new Exception("bla");} 955 }c.format( 956 ), sac); 957 } 958 959 unittest 960 { 961 StaticAnalysisConfig sac = disabledConfig; 962 sac.properly_documented_public_functions = Check.enabled; 963 964 assertAnalyzerWarnings(q{ 965 /++ 966 rethrow 967 +/ 968 void bar(){try throw new Exception("bla"); catch(Exception) throw new Error();} // [warn]: %s 969 }c.format( 970 ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Error") 971 ), sac); 972 } 973 974 unittest 975 { 976 StaticAnalysisConfig sac = disabledConfig; 977 sac.properly_documented_public_functions = Check.enabled; 978 979 assertAnalyzerWarnings(q{ 980 /++ 981 trust nothrow before everything 982 +/ 983 void bar() nothrow {try throw new Exception("bla"); catch(Exception) assert(0);} 984 }c, sac); 985 } 986 987 unittest 988 { 989 StaticAnalysisConfig sac = disabledConfig; 990 sac.properly_documented_public_functions = Check.enabled; 991 992 assertAnalyzerWarnings(q{ 993 /++ 994 case of throw in nested func 995 +/ 996 void bar() // [warn]: %s 997 { 998 void foo(){throw new AssertError("bla");} 999 foo(); 1000 } 1001 }c.format( 1002 ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError") 1003 ), sac); 1004 } 1005 1006 unittest 1007 { 1008 StaticAnalysisConfig sac = disabledConfig; 1009 sac.properly_documented_public_functions = Check.enabled; 1010 1011 assertAnalyzerWarnings(q{ 1012 /++ 1013 case of throw in nested func but caught 1014 +/ 1015 void bar() 1016 { 1017 void foo(){throw new AssertError("bla");} 1018 try foo(); 1019 catch (AssertError){} 1020 } 1021 }c, sac); 1022 } 1023 1024 unittest 1025 { 1026 StaticAnalysisConfig sac = disabledConfig; 1027 sac.properly_documented_public_functions = Check.enabled; 1028 1029 assertAnalyzerWarnings(q{ 1030 /++ 1031 case of double throw in nested func but only 1 caught 1032 +/ 1033 void bar() // [warn]: %s 1034 { 1035 void foo(){throw new AssertError("bla");throw new Error("bla");} 1036 try foo(); 1037 catch (Error){} 1038 } 1039 }c.format( 1040 ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError") 1041 ), sac); 1042 } 1043 1044 // https://github.com/dlang-community/D-Scanner/issues/583 1045 unittest 1046 { 1047 StaticAnalysisConfig sac = disabledConfig; 1048 sac.properly_documented_public_functions = Check.enabled; 1049 1050 assertAnalyzerWarnings(q{ 1051 /++ 1052 Implements the homonym function (also known as `accumulate`) 1053 1054 Returns: 1055 the accumulated `result` 1056 1057 Params: 1058 fun = one or more functions 1059 +/ 1060 template reduce(fun...) 1061 if (fun.length >= 1) 1062 { 1063 /++ 1064 No-seed version. The first element of `r` is used as the seed's value. 1065 1066 Params: 1067 r = an iterable value as defined by `isIterable` 1068 1069 Returns: 1070 the final result of the accumulator applied to the iterable 1071 +/ 1072 auto reduce(R)(R r){} 1073 } 1074 }c.format( 1075 ), sac); 1076 1077 stderr.writeln("Unittest for ProperlyDocumentedPublicFunctions passed."); 1078 }