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