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