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 }