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 dscanner.analysis.base : BaseAnalyzer;
10 
11 import std.format : format;
12 import std.range.primitives;
13 import std.stdio;
14 
15 /**
16  * Requires each public function to contain the following ddoc sections
17 	- PARAMS:
18 		- if the function has at least one parameter
19 		- every parameter must have a ddoc params entry (applies for template paramters too)
20 		- Ddoc params entries without a parameter trigger warnings as well
21 	- RETURNS: (except if it's void, only functions)
22  */
23 class ProperlyDocumentedPublicFunctions : BaseAnalyzer
24 {
25 	enum string MISSING_PARAMS_KEY = "dscanner.style.doc_missing_params";
26 	enum string MISSING_PARAMS_MESSAGE = "Parameter %s isn't documented in the `Params` section.";
27 	enum string MISSING_TEMPLATE_PARAMS_MESSAGE
28 		= "Template parameters %s isn't documented in the `Params` section.";
29 
30 	enum string NON_EXISTENT_PARAMS_KEY = "dscanner.style.doc_non_existing_params";
31 	enum string NON_EXISTENT_PARAMS_MESSAGE = "Documented parameter %s isn't a function parameter.";
32 
33 	enum string MISSING_RETURNS_KEY = "dscanner.style.doc_missing_returns";
34 	enum string MISSING_RETURNS_MESSAGE = "A public function needs to contain a `Returns` section.";
35 
36 	///
37 	this(string fileName, bool skipTests = false)
38 	{
39 		super(fileName, null, skipTests);
40 	}
41 
42 	override void visit(const Module mod)
43 	{
44 		islastSeenVisibilityLabelPublic = true;
45 		mod.accept(this);
46 		postCheckSeenDdocParams();
47 	}
48 
49 	override void visit(const Declaration decl)
50 	{
51 		import std.algorithm.searching : any;
52 		import std.algorithm.iteration : map;
53 
54 		// skip private symbols
55 		enum tokPrivate = tok!"private",
56 			 tokProtected = tok!"protected",
57 			 tokPackage = tok!"package",
58 			 tokPublic = tok!"public";
59 
60 		if (decl.attributes.length > 0)
61 		{
62 			const bool isPublic = !decl.attributes.map!`a.attribute`.any!(x => x == tokPrivate ||
63 																			   x == tokProtected ||
64 																			   x == tokPackage);
65 			// recognize label blocks
66 			if (!hasDeclaration(decl))
67 				islastSeenVisibilityLabelPublic = isPublic;
68 
69 			if (!isPublic)
70 				return;
71 		}
72 
73 		if (islastSeenVisibilityLabelPublic || decl.attributes.map!`a.attribute`.any!(x => x == tokPublic))
74 		{
75 			if (decl.functionDeclaration !is null ||
76 				decl.templateDeclaration !is null ||
77 				decl.mixinTemplateDeclaration !is null ||
78 				decl.classDeclaration !is null ||
79 				decl.structDeclaration !is null)
80 					decl.accept(this);
81 		}
82 	}
83 
84 	override void visit(const TemplateDeclaration decl)
85 	{
86 		setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
87 		checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters);
88 
89 		withinTemplate = true;
90 		scope(exit) withinTemplate = false;
91 		decl.accept(this);
92 	}
93 
94 	override void visit(const MixinTemplateDeclaration decl)
95 	{
96 		decl.accept(this);
97 	}
98 
99 	override void visit(const StructDeclaration decl)
100 	{
101 		setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
102 		checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters);
103 		decl.accept(this);
104 	}
105 
106 	override void visit(const ClassDeclaration decl)
107 	{
108 		setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
109 		checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters);
110 		decl.accept(this);
111 	}
112 
113 	override void visit(const FunctionDeclaration decl)
114 	{
115 		import std.algorithm.searching : any;
116 
117 		// ignore header declaration for now
118 		if (decl.functionBody is null)
119 			return;
120 
121 		auto comment = setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
122 
123 		checkDdocParams(decl.name.line, decl.name.column, decl.parameters,  decl.templateParameters);
124 
125 		enum voidType = tok!"void";
126 
127 		if (decl.returnType is null || decl.returnType.type2.builtinType != voidType)
128 			if (!(comment.isDitto || withinTemplate || comment.sections.any!(s => s.name == "Returns")))
129 				addErrorMessage(decl.name.line, decl.name.column, MISSING_RETURNS_KEY, MISSING_RETURNS_MESSAGE);
130 	}
131 
132 	alias visit = BaseAnalyzer.visit;
133 
134 private:
135 	bool islastSeenVisibilityLabelPublic;
136 	bool withinTemplate;
137 
138 	static struct Function
139 	{
140 		bool active;
141 		size_t line, column;
142 		const(string)[] ddocParams;
143 		bool[string] params;
144 	}
145 	Function lastSeenFun;
146 
147 	// find invalid ddoc parameters (i.e. they don't occur in a function declaration)
148 	void postCheckSeenDdocParams()
149 	{
150 		import std.format : format;
151 
152 		if (lastSeenFun.active)
153 		foreach (p; lastSeenFun.ddocParams)
154 			if (p !in lastSeenFun.params)
155 				addErrorMessage(lastSeenFun.line, lastSeenFun.column, NON_EXISTENT_PARAMS_KEY,
156 					NON_EXISTENT_PARAMS_MESSAGE.format(p));
157 
158 		lastSeenFun.active = false;
159 	}
160 
161 	auto setLastDdocParams(size_t line, size_t column, string commentText)
162 	{
163 		import ddoc.comments : parseComment;
164 		import std.algorithm.searching : find;
165 		import std.algorithm.iteration : map;
166 		import std.array : array;
167 
168 		const comment = parseComment(commentText, null);
169 		if (!comment.isDitto && !withinTemplate)
170 		{
171 			// check old function for invalid ddoc params
172 			if (lastSeenFun.active)
173 				postCheckSeenDdocParams();
174 
175 			const paramSection = comment.sections.find!(s => s.name == "Params");
176 			if (paramSection.empty)
177 			{
178 				lastSeenFun = Function(true, line, column, null);
179 			}
180 			else
181 			{
182 				auto ddocParams = paramSection[0].mapping.map!(a => a[0]).array;
183 				lastSeenFun = Function(true, line, column, ddocParams);
184 			}
185 		}
186 
187 		return comment;
188 	}
189 
190 	void checkDdocParams(size_t line, size_t column, const Parameters params,
191 					     const TemplateParameters templateParameters = null)
192 	{
193 		import std.array : array;
194 		import std.algorithm.searching : canFind, countUntil;
195 		import std.algorithm.iteration : map;
196 		import std.algorithm.mutation : remove;
197 		import std.range : indexed, iota;
198 
199 		// convert templateParameters into a string[] for faster access
200 		const(TemplateParameter)[] templateList;
201 		if (const tp = templateParameters)
202 		if (const tpl = tp.templateParameterList)
203 			templateList = tpl.items;
204 		string[] tlList = templateList.map!(a => templateParamName(a)).array;
205 
206 		// make a copy of all parameters and remove the seen ones later during the loop
207 		size_t[] unseenTemplates = templateList.length.iota.array;
208 
209 		if (lastSeenFun.active && params !is null)
210 			foreach (p; params.parameters)
211 			{
212 				string templateName;
213 				if (const t = p.type)
214 				if (const t2 = t.type2)
215 				if (const tip = t2.typeIdentifierPart)
216 				if (const iot = tip.identifierOrTemplateInstance)
217 					templateName = iot.identifier.text;
218 
219 				const idx = tlList.countUntil(templateName);
220 				if (idx >= 0)
221 				{
222 					unseenTemplates = unseenTemplates.remove(idx);
223 					tlList = tlList.remove(idx);
224 					// documenting template parameter should be allowed
225 					lastSeenFun.params[templateName] = true;
226 				}
227 
228 				if (!lastSeenFun.ddocParams.canFind(p.name.text))
229 					addErrorMessage(line, column, MISSING_PARAMS_KEY,
230 						format(MISSING_PARAMS_MESSAGE, p.name.text));
231 				else
232 					lastSeenFun.params[p.name.text] = true;
233 			}
234 
235 		// now check the remaining, not used template parameters
236 		auto unseenTemplatesArr = templateList.indexed(unseenTemplates).array;
237 		checkDdocParams(line, column, unseenTemplatesArr);
238 	}
239 
240 	void checkDdocParams(size_t line, size_t column, const TemplateParameters templateParams)
241 	{
242 
243 		if (lastSeenFun.active && templateParams !is null && templateParams.templateParameterList !is null)
244 			checkDdocParams(line, column, templateParams.templateParameterList.items);
245 	}
246 
247 	void checkDdocParams(size_t line, size_t column, const TemplateParameter[] templateParams)
248 	{
249 		import std.algorithm.searching : canFind;
250 		foreach (p; templateParams)
251 		{
252 			const name = templateParamName(p);
253 			assert(name, "Invalid template parameter name."); // this shouldn't happen
254 			if (!lastSeenFun.ddocParams.canFind(name))
255 				addErrorMessage(line, column, MISSING_PARAMS_KEY,
256 					format(MISSING_TEMPLATE_PARAMS_MESSAGE, name));
257 			else
258 				lastSeenFun.params[name] = true;
259 		}
260 	}
261 
262 	static string templateParamName(const TemplateParameter p)
263 	{
264 		if (p.templateTypeParameter)
265 			return p.templateTypeParameter.identifier.text;
266    		if (p.templateValueParameter)
267 			return p.templateValueParameter.identifier.text;
268    		if (p.templateAliasParameter)
269 			return p.templateAliasParameter.identifier.text;
270    		if (p.templateTupleParameter)
271 			return p.templateTupleParameter.identifier.text;
272    		if (p.templateThisParameter)
273 			return p.templateThisParameter.templateTypeParameter.identifier.text;
274 
275 		return null;
276 	}
277 
278 	bool hasDeclaration(const Declaration decl)
279 	{
280 		import std.meta : AliasSeq;
281 		alias properties = AliasSeq!(
282 			"aliasDeclaration",
283 		 	"aliasThisDeclaration",
284 		 	"anonymousEnumDeclaration",
285 		 	"attributeDeclaration",
286 		 	"classDeclaration",
287 		 	"conditionalDeclaration",
288 		 	"constructor",
289 		 	"debugSpecification",
290 		 	"destructor",
291 		 	"enumDeclaration",
292 		 	"eponymousTemplateDeclaration",
293 		 	"functionDeclaration",
294 		 	"importDeclaration",
295 		 	"interfaceDeclaration",
296 		 	"invariant_",
297 		 	"mixinDeclaration",
298 		 	"mixinTemplateDeclaration",
299 		 	"postblit",
300 		 	"pragmaDeclaration",
301 		 	"sharedStaticConstructor",
302 		 	"sharedStaticDestructor",
303 		 	"staticAssertDeclaration",
304 		 	"staticConstructor",
305 		 	"staticDestructor",
306 		 	"structDeclaration",
307 		 	"templateDeclaration",
308 		 	"unionDeclaration",
309 		 	"unittest_",
310 		 	"variableDeclaration",
311 		 	"versionSpecification",
312 		);
313 		if (decl.declarations !is null)
314 			return false;
315 
316 		auto isNull = true;
317 		foreach (property; properties)
318 			if (mixin("decl." ~ property ~ " !is null"))
319 				isNull = false;
320 
321 		return !isNull;
322 	}
323 }
324 
325 version(unittest)
326 {
327 	import std.stdio : stderr;
328 	import std.format : format;
329 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
330 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
331 }
332 
333 // missing params
334 unittest
335 {
336 	StaticAnalysisConfig sac = disabledConfig;
337 	sac.properly_documented_public_functions = Check.enabled;
338 
339 	assertAnalyzerWarnings(q{
340 		/**
341 		Some text
342 		*/
343 		void foo(int k){} // [warn]: %s
344 	}}.format(
345 		ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k")
346 	), sac);
347 
348 	assertAnalyzerWarnings(q{
349 		/**
350 		Some text
351 		*/
352 		void foo(int K)(){} // [warn]: %s
353 	}}.format(
354 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("K")
355 	), sac);
356 
357 	assertAnalyzerWarnings(q{
358 		/**
359 		Some text
360 		*/
361 		struct Foo(Bar){} // [warn]: %s
362 	}}.format(
363 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar")
364 	), sac);
365 
366 	assertAnalyzerWarnings(q{
367 		/**
368 		Some text
369 		*/
370 		class Foo(Bar){} // [warn]: %s
371 	}}.format(
372 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar")
373 	), sac);
374 
375 	assertAnalyzerWarnings(q{
376 		/**
377 		Some text
378 		*/
379 		template Foo(Bar){} // [warn]: %s
380 	}}.format(
381 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar")
382 	), sac);
383 
384 
385 	// test no parameters
386 	assertAnalyzerWarnings(q{
387 		/** Some text */
388 		void foo(){}
389 	}}, sac);
390 
391 	assertAnalyzerWarnings(q{
392 		/** Some text */
393 		struct Foo(){}
394 	}}, sac);
395 
396 	assertAnalyzerWarnings(q{
397 		/** Some text */
398 		class Foo(){}
399 	}}, sac);
400 
401 	assertAnalyzerWarnings(q{
402 		/** Some text */
403 		template Foo(){}
404 	}}, sac);
405 
406 }
407 
408 // missing returns (only functions)
409 unittest
410 {
411 	StaticAnalysisConfig sac = disabledConfig;
412 	sac.properly_documented_public_functions = Check.enabled;
413 
414 	assertAnalyzerWarnings(q{
415 		/**
416 		Some text
417 		*/
418 		int foo(){} // [warn]: %s
419 	}}.format(
420 		ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
421 	), sac);
422 
423 	assertAnalyzerWarnings(q{
424 		/**
425 		Some text
426 		*/
427 		auto foo(){} // [warn]: %s
428 	}}.format(
429 		ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
430 	), sac);
431 }
432 
433 // ignore private
434 unittest
435 {
436 	StaticAnalysisConfig sac = disabledConfig;
437 	sac.properly_documented_public_functions = Check.enabled;
438 
439 	assertAnalyzerWarnings(q{
440 		/**
441 		Some text
442 		*/
443 		private void foo(int k){}
444 	}}, sac);
445 
446 	// with block
447 	assertAnalyzerWarnings(q{
448 	private:
449 		/**
450 		Some text
451 		*/
452 		private void foo(int k){}
453 		public int bar(){} // [warn]: %s
454 	public:
455 		int foobar(){} // [warn]: %s
456 	}}.format(
457 		ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
458 		ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
459 	), sac);
460 
461 	// with block (template)
462 	assertAnalyzerWarnings(q{
463 	private:
464 		/**
465 		Some text
466 		*/
467 		private template foo(int k){}
468 		public template bar(T){} // [warn]: %s
469 	public:
470 		template foobar(T){} // [warn]: %s
471 	}}.format(
472 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
473 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
474 	), sac);
475 
476 	// with block (struct)
477 	assertAnalyzerWarnings(q{
478 	private:
479 		/**
480 		Some text
481 		*/
482 		private struct foo(int k){}
483 		public struct bar(T){} // [warn]: %s
484 	public:
485 		struct foobar(T){} // [warn]: %s
486 	}}.format(
487 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
488 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
489 	), sac);
490 }
491 
492 // test parameter names
493 unittest
494 {
495 	StaticAnalysisConfig sac = disabledConfig;
496 	sac.properly_documented_public_functions = Check.enabled;
497 
498 	assertAnalyzerWarnings(q{
499 /**
500  * Description.
501  *
502  * Params:
503  *
504  * Returns:
505  * A long description.
506  */
507 int foo(int k){} // [warn]: %s
508 	}}.format(
509 		ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k")
510 	), sac);
511 
512 	assertAnalyzerWarnings(q{
513 /**
514 Description.
515 
516 Params:
517 val =  A stupid parameter
518 k = A stupid parameter
519 
520 Returns:
521 A long description.
522 */
523 int foo(int k){} // [warn]: %s
524 	}}.format(
525 		ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("val")
526 	), sac);
527 
528 	assertAnalyzerWarnings(q{
529 /**
530 Description.
531 
532 Params:
533 
534 Returns:
535 A long description.
536 */
537 int foo(int k){} // [warn]: %s
538 	}}.format(
539 		ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k")
540 	), sac);
541 
542 	assertAnalyzerWarnings(q{
543 /**
544 Description.
545 
546 Params:
547 foo =  A stupid parameter
548 bad =  A stupid parameter (does not exist)
549 foobar  = A stupid parameter
550 
551 Returns:
552 A long description.
553 */
554 int foo(int foo, int foobar){} // [warn]: %s
555 	}}.format(
556 		ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad")
557 	), sac);
558 
559 	assertAnalyzerWarnings(q{
560 /**
561 Description.
562 
563 Params:
564 foo =  A stupid parameter
565 bad =  A stupid parameter (does not exist)
566 foobar  = A stupid parameter
567 
568 Returns:
569 A long description.
570 */
571 struct foo(int foo, int foobar){} // [warn]: %s
572 	}}.format(
573 		ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad")
574 	), sac);
575 
576 	// properly documented
577 	assertAnalyzerWarnings(q{
578 /**
579 Description.
580 
581 Params:
582 foo =  A stupid parameter
583 bar  = A stupid parameter
584 
585 Returns:
586 A long description.
587 */
588 int foo(int foo, int bar){}
589 	}}, sac);
590 
591 	assertAnalyzerWarnings(q{
592 /**
593 Description.
594 
595 Params:
596 foo =  A stupid parameter
597 bar  = A stupid parameter
598 
599 Returns:
600 A long description.
601 */
602 struct foo(int foo, int bar){}
603 	}}, sac);
604 }
605 
606 // support ditto
607 unittest
608 {
609 	StaticAnalysisConfig sac = disabledConfig;
610 	sac.properly_documented_public_functions = Check.enabled;
611 
612 	assertAnalyzerWarnings(q{
613 /**
614  * Description.
615  *
616  * Params:
617  * k =  A stupid parameter
618  *
619  * Returns:
620  * A long description.
621  */
622 int foo(int k){}
623 
624 /// ditto
625 int bar(int k){}
626 	}}, sac);
627 
628 	assertAnalyzerWarnings(q{
629 /**
630  * Description.
631  *
632  * Params:
633  * k =  A stupid parameter
634  * K =  A stupid parameter
635  *
636  * Returns:
637  * A long description.
638  */
639 int foo(int k){}
640 
641 /// ditto
642 struct Bar(K){}
643 	}}, sac);
644 
645 	assertAnalyzerWarnings(q{
646 /**
647  * Description.
648  *
649  * Params:
650  * k =  A stupid parameter
651  * f =  A stupid parameter
652  *
653  * Returns:
654  * A long description.
655  */
656 int foo(int k){}
657 
658 /// ditto
659 int bar(int f){}
660 	}}, sac);
661 
662 	assertAnalyzerWarnings(q{
663 /**
664  * Description.
665  *
666  * Params:
667  * k =  A stupid parameter
668  *
669  * Returns:
670  * A long description.
671  */
672 int foo(int k){}
673 
674 /// ditto
675 int bar(int bar){} // [warn]: %s
676 	}}.format(
677 		ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("bar")
678 	), sac);
679 
680 	assertAnalyzerWarnings(q{
681 /**
682  * Description.
683  *
684  * Params:
685  * k =  A stupid parameter
686  * bar =  A stupid parameter
687  * f =  A stupid parameter
688  *
689  * Returns:
690  * A long description.
691  * See_Also:
692  *	$(REF takeExactly, std,range)
693  */
694 int foo(int k){} // [warn]: %s
695 
696 /// ditto
697 int bar(int bar){}
698 	}}.format(
699 		ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("f")
700 	), sac);
701 }
702 
703  // check correct ddoc headers
704 unittest
705 {
706 	StaticAnalysisConfig sac = disabledConfig;
707 	sac.properly_documented_public_functions = Check.enabled;
708 
709 	assertAnalyzerWarnings(q{
710 /++
711     Counts elements in the given
712     $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
713     until the given predicate is true for one of the given $(D needles).
714 
715     Params:
716 		val  =  A stupid parameter
717 
718     Returns: Awesome values.
719   +/
720 string bar(string val){}
721 	}}, sac);
722 
723 	assertAnalyzerWarnings(q{
724 /++
725     Counts elements in the given
726     $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
727     until the given predicate is true for one of the given $(D needles).
728 
729     Params:
730 		val  =  A stupid parameter
731 
732     Returns: Awesome values.
733   +/
734 template bar(string val){}
735 	}}, sac);
736 
737 }
738 
739 unittest
740 {
741 	StaticAnalysisConfig sac = disabledConfig;
742 	sac.properly_documented_public_functions = Check.enabled;
743 
744 	assertAnalyzerWarnings(q{
745 /**
746  * Ddoc for the inner function appears here.
747  * This function is declared this way to allow for multiple variable-length
748  * template argument lists.
749  * ---
750  * abcde!("a", "b", "c")(100, x, y, z);
751  * ---
752  * Params:
753  *    Args = foo
754  *    U = bar
755  *    T = barr
756  *    varargs = foobar
757  *    t = foo
758  * Returns: bar
759  */
760 template abcde(Args ...) {
761     auto abcde(T, U...)(T t, U varargs) {
762         /// ....
763     }
764 }
765 	}}, sac);
766 }
767 
768 // Don't force the documentation of the template parameter if it's a used type in the parameter list
769 unittest
770 {
771 	StaticAnalysisConfig sac = disabledConfig;
772 	sac.properly_documented_public_functions = Check.enabled;
773 
774 	assertAnalyzerWarnings(q{
775 /++
776 An awesome description.
777 
778 Params:
779 	r =  an input range.
780 
781 Returns: Awesome values.
782 +/
783 string bar(R)(R r){}
784 	}}, sac);
785 
786 	assertAnalyzerWarnings(q{
787 /++
788 An awesome description.
789 
790 Params:
791 	r =  an input range.
792 
793 Returns: Awesome values.
794 +/
795 string bar(P, R)(R r){}// [warn]: %s
796 	}}.format(
797 		ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("P")
798 	), sac);
799 
800 	stderr.writeln("Unittest for ProperlyDocumentedPublicFunctions passed.");
801 }