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