1 module dscanner.analysis.constructors;
2 
3 import dparse.ast;
4 import dparse.lexer;
5 import std.stdio;
6 import dscanner.analysis.base;
7 import dscanner.analysis.helpers;
8 import dsymbol.scope_ : Scope;
9 
10 final class ConstructorCheck : BaseAnalyzer
11 {
12 	alias visit = BaseAnalyzer.visit;
13 
14 	mixin AnalyzerInfo!"constructor_check";
15 
16 	this(string fileName, const(Scope)* sc, bool skipTests = false)
17 	{
18 		super(fileName, sc, skipTests);
19 	}
20 
21 	override void visit(const ClassDeclaration classDeclaration)
22 	{
23 		immutable bool oldHasDefault = hasDefaultArgConstructor;
24 		immutable bool oldHasNoArg = hasNoArgConstructor;
25 		hasNoArgConstructor = false;
26 		hasDefaultArgConstructor = false;
27 		immutable State prev = state;
28 		state = State.inClass;
29 		classDeclaration.accept(this);
30 		if (hasNoArgConstructor && hasDefaultArgConstructor)
31 		{
32 			addErrorMessage(classDeclaration.name.line,
33 					classDeclaration.name.column, "dscanner.confusing.constructor_args",
34 					"This class has a zero-argument constructor as well as a"
35 					~ " constructor with one default argument. This can be confusing.");
36 		}
37 		hasDefaultArgConstructor = oldHasDefault;
38 		hasNoArgConstructor = oldHasNoArg;
39 		state = prev;
40 	}
41 
42 	override void visit(const StructDeclaration structDeclaration)
43 	{
44 		immutable State prev = state;
45 		state = State.inStruct;
46 		structDeclaration.accept(this);
47 		state = prev;
48 	}
49 
50 	override void visit(const Constructor constructor)
51 	{
52 		final switch (state)
53 		{
54 		case State.inStruct:
55 			if (constructor.parameters.parameters.length == 1
56 					&& constructor.parameters.parameters[0].default_ !is null)
57 			{
58 				addErrorMessage(constructor.line, constructor.column,
59 						"dscanner.confusing.struct_constructor_default_args",
60 						"This struct constructor can never be called with its "
61 						~ "default argument.");
62 			}
63 			break;
64 		case State.inClass:
65 			if (constructor.parameters.parameters.length == 1
66 					&& constructor.parameters.parameters[0].default_ !is null)
67 			{
68 				hasDefaultArgConstructor = true;
69 			}
70 			else if (constructor.parameters.parameters.length == 0)
71 				hasNoArgConstructor = true;
72 			break;
73 		case State.ignoring:
74 			break;
75 		}
76 	}
77 
78 private:
79 
80 	enum State : ubyte
81 	{
82 		ignoring,
83 		inClass,
84 		inStruct
85 	}
86 
87 	State state;
88 
89 	bool hasNoArgConstructor;
90 	bool hasDefaultArgConstructor;
91 }
92 
93 unittest
94 {
95 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
96 
97 	StaticAnalysisConfig sac = disabledConfig();
98 	sac.constructor_check = Check.enabled;
99 	assertAnalyzerWarnings(q{
100 		class Cat // [warn]: This class has a zero-argument constructor as well as a constructor with one default argument. This can be confusing.
101 		{
102 			this() {}
103 			this(string name = "kittie") {}
104 		}
105 
106 		struct Dog
107 		{
108 			this() {}
109 			this(string name = "doggie") {} // [warn]: This struct constructor can never be called with its default argument.
110 		}
111 	}}, sac);
112 
113 	stderr.writeln("Unittest for ConstructorCheck passed.");
114 }