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