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.explicitly_annotated_unittests;
6 
7 import dparse.lexer;
8 import dparse.ast;
9 import dscanner.analysis.base;
10 
11 import std.stdio;
12 
13 /**
14  * Requires unittests to be explicitly annotated with either @safe or @system
15  */
16 final class ExplicitlyAnnotatedUnittestCheck : BaseAnalyzer
17 {
18 	enum string KEY = "dscanner.style.explicitly_annotated_unittest";
19 	enum string MESSAGE = "A unittest should be annotated with at least @safe or @system";
20     mixin AnalyzerInfo!"explicitly_annotated_unittests";
21 
22 	///
23 	this(string fileName, bool skipTests = false)
24 	{
25 		super(fileName, null, skipTests);
26 	}
27 
28 	override void visit(const Declaration decl)
29 	{
30 		if (decl.unittest_ !is null)
31 		{
32 			bool isSafeOrSystem;
33 			if (decl.attributes !is null)
34 			foreach (attribute; decl.attributes)
35 			{
36 				if (attribute.atAttribute !is null)
37 				{
38 					const token = attribute.atAttribute.identifier.text;
39 					if (token == "safe" || token == "system")
40 					{
41 						isSafeOrSystem = true;
42 						break;
43 					}
44 				}
45 			}
46 			if (!isSafeOrSystem)
47 				addErrorMessage(decl.unittest_.line, decl.unittest_.column, KEY, MESSAGE);
48 		}
49 		decl.accept(this);
50 	}
51 
52 	alias visit = BaseAnalyzer.visit;
53 
54 }
55 
56 unittest
57 {
58 	import std.stdio : stderr;
59 	import std.format : format;
60 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
61 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
62 
63 	StaticAnalysisConfig sac = disabledConfig();
64 	sac.explicitly_annotated_unittests = Check.enabled;
65 
66 	assertAnalyzerWarnings(q{
67 		@safe unittest {}
68 		@system unittest {}
69 		pure nothrow @system @nogc unittest {}
70 
71 		unittest {} // [warn]: %s
72 		pure nothrow @nogc unittest {} // [warn]: %s
73 	}}.format(
74 		ExplicitlyAnnotatedUnittestCheck.MESSAGE,
75 		ExplicitlyAnnotatedUnittestCheck.MESSAGE,
76 	), sac);
77 
78 	// nested
79 	assertAnalyzerWarnings(q{
80 		struct Foo
81 		{
82 			@safe unittest {}
83 			@system unittest {}
84 
85 			unittest {} // [warn]: %s
86 			pure nothrow @nogc unittest {} // [warn]: %s
87 		}
88 	}}.format(
89 		ExplicitlyAnnotatedUnittestCheck.MESSAGE,
90 		ExplicitlyAnnotatedUnittestCheck.MESSAGE,
91 	), sac);
92 
93 	stderr.writeln("Unittest for ExplicitlyAnnotatedUnittestCheck passed.");
94 }