1 module dscanner.analysis.mismatched_args; 2 3 import dscanner.analysis.base : BaseAnalyzer; 4 import dscanner.utils : safeAccess; 5 import dsymbol.scope_; 6 import dsymbol.symbol; 7 import dparse.ast; 8 import dparse.lexer : tok; 9 import dsymbol.builtin.names; 10 11 /// Checks for mismatched argument and parameter names 12 final class MismatchedArgumentCheck : BaseAnalyzer 13 { 14 /// 15 this(string fileName, const(Scope)* sc, bool skipTests = false) 16 { 17 super(fileName, sc, skipTests); 18 } 19 20 override void visit(const FunctionCallExpression fce) 21 { 22 import std.typecons : scoped; 23 import std.algorithm.iteration : each, map; 24 import std.array : array; 25 26 if (fce.arguments is null) 27 return; 28 auto argVisitor = scoped!ArgVisitor; 29 argVisitor.visit(fce.arguments); 30 const istring[] args = argVisitor.args; 31 32 auto identVisitor = scoped!IdentVisitor; 33 if (fce.unaryExpression !is null) 34 identVisitor.visit(fce.unaryExpression); 35 else if (fce.type !is null) 36 identVisitor.visit(fce.type); 37 38 const(DSymbol)*[] symbols = resolveSymbol(sc, identVisitor.names.length > 0 39 ? identVisitor.names : [CONSTRUCTOR_SYMBOL_NAME]); 40 41 static struct ErrorMessage 42 { 43 size_t line; 44 size_t column; 45 string message; 46 } 47 48 ErrorMessage[] messages; 49 bool matched; 50 51 foreach (sym; symbols) 52 { 53 // The cast is a hack because .array() confuses the compiler's overload 54 // resolution code. 55 const(istring)[] params = sym is null ? [] : sym.argNames[].map!(a => cast() a).array(); 56 const ArgMismatch[] mismatches = compareArgsToParams(params, args); 57 if (mismatches.length == 0) 58 matched = true; 59 else 60 { 61 foreach (size_t i, ref const mm; mismatches) 62 { 63 messages ~= ErrorMessage(argVisitor.lines[i], 64 argVisitor.columns[i], createWarningFromMismatch(mm)); 65 } 66 } 67 } 68 69 if (!matched) 70 foreach (m; messages) 71 addErrorMessage(m.line, m.column, KEY, m.message); 72 } 73 74 alias visit = ASTVisitor.visit; 75 76 private: 77 78 enum string KEY = "dscanner.confusing.argument_parameter_mismatch"; 79 } 80 81 final class IdentVisitor : ASTVisitor 82 { 83 override void visit(const IdentifierOrTemplateInstance ioti) 84 { 85 import dsymbol.string_interning : internString; 86 87 if (ioti.identifier != tok!"") 88 names ~= internString(ioti.identifier.text); 89 else 90 names ~= internString(ioti.templateInstance.identifier.text); 91 } 92 93 override void visit(const Arguments) 94 { 95 } 96 97 override void visit(const IndexExpression ie) 98 { 99 if (ie.unaryExpression !is null) 100 visit(ie.unaryExpression); 101 } 102 103 alias visit = ASTVisitor.visit; 104 105 istring[] names; 106 } 107 108 final class ArgVisitor : ASTVisitor 109 { 110 override void visit(const ArgumentList al) 111 { 112 foreach (a; al.items) 113 { 114 auto u = cast(UnaryExpression) a; 115 if (u !is null) 116 visit(u); 117 else 118 { 119 args ~= istring.init; 120 lines ~= size_t.max; 121 columns ~= size_t.max; 122 } 123 } 124 } 125 126 override void visit(const UnaryExpression unary) 127 { 128 import dsymbol.string_interning : internString; 129 130 if (auto iot = unary.safeAccess.primaryExpression.identifierOrTemplateInstance.unwrap) 131 { 132 if (iot.identifier == tok!"") 133 return; 134 immutable t = iot.identifier; 135 lines ~= t.line; 136 columns ~= t.column; 137 args ~= internString(t.text); 138 } 139 } 140 141 alias visit = ASTVisitor.visit; 142 143 size_t[] lines; 144 size_t[] columns; 145 istring[] args; 146 } 147 148 const(DSymbol)*[] resolveSymbol(const Scope* sc, const istring[] symbolChain) 149 { 150 import std.array : empty; 151 152 const(DSymbol)*[] matchingSymbols = sc.getSymbolsByName(symbolChain[0]); 153 if (matchingSymbols.empty) 154 return null; 155 156 foreach (ref symbol; matchingSymbols) 157 { 158 inner: foreach (i; 1 .. symbolChain.length) 159 { 160 if (symbol.kind == CompletionKind.variableName 161 || symbol.kind == CompletionKind.memberVariableName 162 || symbol.kind == CompletionKind.functionName) 163 symbol = symbol.type; 164 if (symbol is null) 165 { 166 symbol = null; 167 break inner; 168 } 169 auto p = symbol.getPartsByName(symbolChain[i]); 170 if (p.empty) 171 { 172 symbol = null; 173 break inner; 174 } 175 symbol = p[0]; 176 } 177 } 178 return matchingSymbols; 179 } 180 181 struct ArgMismatch 182 { 183 size_t argIndex; 184 size_t paramIndex; 185 string name; 186 } 187 188 immutable(ArgMismatch[]) compareArgsToParams(const istring[] params, const istring[] args) pure 189 { 190 import std.exception : assumeUnique; 191 192 if (args.length != params.length) 193 return []; 194 ArgMismatch[] retVal; 195 foreach (i, arg; args) 196 { 197 if (arg is null || arg == params[i]) 198 continue; 199 foreach (j, param; params) 200 if (param == arg) 201 retVal ~= ArgMismatch(i, j, arg); 202 } 203 return assumeUnique(retVal); 204 } 205 206 string createWarningFromMismatch(const ArgMismatch mismatch) pure 207 { 208 import std.format : format; 209 210 return "Argument %d is named '%s', but this is the name of parameter %d".format( 211 mismatch.argIndex + 1, mismatch.name, mismatch.paramIndex + 1); 212 } 213 214 unittest 215 { 216 import dsymbol.string_interning : internString; 217 import std.algorithm.iteration : map; 218 import std.array : array; 219 import std.conv : to; 220 221 { 222 istring[] args = ["a", "b", "c"].map!internString().array(); 223 istring[] params = ["a", "b", "c"].map!internString().array(); 224 immutable res = compareArgsToParams(params, args); 225 assert(res == []); 226 } 227 228 { 229 istring[] args = ["a", "c", "b"].map!internString().array(); 230 istring[] params = ["a", "b", "c"].map!internString().array(); 231 immutable res = compareArgsToParams(params, args); 232 assert(res == [ArgMismatch(1, 2, "c"), ArgMismatch(2, 1, "b")], to!string(res)); 233 } 234 235 { 236 istring[] args = ["a", "c", "b"].map!internString().array(); 237 istring[] params = ["alpha", "bravo", "c"].map!internString().array(); 238 immutable res = compareArgsToParams(params, args); 239 assert(res == [ArgMismatch(1, 2, "c")]); 240 } 241 242 { 243 istring[] args = ["a", "b"].map!internString().array(); 244 istring[] params = [null, "b"].map!internString().array(); 245 immutable res = compareArgsToParams(params, args); 246 assert(res == []); 247 } 248 }