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 }