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 }