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