1 /// Finds methods in a specified interface or class location. 2 module workspaced.visitors.methodfinder; 3 4 import workspaced.visitors.attributes; 5 6 import workspaced.dparseext; 7 8 import dparse.ast; 9 import dparse.formatter; 10 import dparse.lexer; 11 12 import std.algorithm; 13 import std.array; 14 import std.range; 15 import std.string; 16 17 /// Information about an argument in a method defintion. 18 struct ArgumentInfo 19 { 20 /// The whole definition of the argument including everything related to it as formatted code string. 21 string signature; 22 /// The type of the argument. 23 string type; 24 /// The name of the argument. 25 string name; 26 27 /// Returns just the name. 28 string toString() const 29 { 30 return name; 31 } 32 } 33 34 /// Information about a method definition. 35 struct MethodDetails 36 { 37 /// The name of the method. 38 string name; 39 /// The type definition of the method without body, abstract or final. 40 string signature; 41 /// The return type of the method. 42 string returnType; 43 /// All (regular) arguments passable into this method. 44 ArgumentInfo[] arguments; 45 /// 46 bool isNothrowOrNogc; 47 /// True if this function has an implementation. 48 bool hasBody; 49 /// True when the container is an interface or (optionally implicit) abstract class or when in class not having a body. 50 bool needsImplementation; 51 /// True when in a class and method doesn't have a body. 52 bool optionalImplementation; 53 /// Range starting at return type, going until last token before opening curly brace. 54 size_t[2] definitionRange; 55 /// Range containing the starting and ending braces of the body. 56 size_t[2] blockRange; 57 58 /// Signature without any attributes, constraints or parameter details other than types. 59 /// Used to differentiate a method from others without computing the mangle. 60 /// Returns: `"<type> <name>(<argument types>)"` 61 string identifier() 62 { 63 return format("%s %s(%(%s,%))", returnType, name, arguments.map!"a.type"); 64 } 65 } 66 67 /// 68 struct FieldDetails 69 { 70 /// 71 string name, type; 72 /// 73 bool isPrivate; 74 } 75 76 /// 77 struct TypeDetails 78 { 79 enum Type 80 { 81 none, 82 class_, 83 interface_, 84 enum_, 85 struct_, 86 union_, 87 alias_, 88 template_, 89 } 90 91 /// Name in last element, all parents in previous elements. 92 string[] name; 93 /// 94 size_t nameLocation; 95 /// 96 Type type; 97 } 98 99 /// 100 struct ReferencedType 101 { 102 /// Referenced type name, might be longer than actual written name because of normalization of parents. 103 string name; 104 /// Location of name which will start right before the last identifier of the type in a dot chain. 105 size_t location; 106 } 107 108 /// Information about an interface or class 109 struct InterfaceDetails 110 { 111 /// Entire code of the file 112 const(char)[] code; 113 /// True if this is a class and therefore need to override methods using $(D override). 114 bool needsOverride; 115 /// Name of the interface or class. 116 string name; 117 /// Plain old variable fields in this container. 118 FieldDetails[] fields; 119 /// All methods defined in this container. 120 MethodDetails[] methods; 121 /// A list of nested types and locations defined in this interface/class. 122 TypeDetails[] types; 123 // reserved for future use with templates 124 string[] parents; 125 /// Name of all base classes or interfaces. Should use normalizedParents, 126 string[] normalizedParents; 127 /// Absolute code position after the colon where the corresponding parent name starts. 128 int[] parentPositions; 129 /// Range containing the starting and ending braces of the body. 130 size_t[2] blockRange; 131 /// A (name-based) sorted set of referenced types with first occurences of every type or alias not including built-in types, but including object.d types and aliases. 132 ReferencedType[] referencedTypes; 133 134 /// Returns true if there are no non-whitespace characters inside the block. 135 bool isEmpty() const 136 { 137 return !code.substr(blockRange).strip.length; 138 } 139 } 140 141 class InterfaceMethodFinder : AttributesVisitor 142 { 143 this(const(char)[] code, int targetPosition) 144 { 145 this.code = code; 146 details.code = code; 147 this.targetPosition = targetPosition; 148 } 149 150 override void visit(const StructDeclaration dec) 151 { 152 if (inTarget) 153 return; 154 else 155 super.visit(dec); 156 } 157 158 override void visit(const UnionDeclaration dec) 159 { 160 if (inTarget) 161 return; 162 else 163 super.visit(dec); 164 } 165 166 override void visit(const EnumDeclaration dec) 167 { 168 if (inTarget) 169 return; 170 else 171 super.visit(dec); 172 } 173 174 override void visit(const ClassDeclaration dec) 175 { 176 if (inTarget) 177 return; 178 179 auto c = context.save(); 180 context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text); 181 visitInterface(dec.name, dec.baseClassList, dec.structBody, true); 182 context.restore(c); 183 } 184 185 override void visit(const InterfaceDeclaration dec) 186 { 187 if (inTarget) 188 return; 189 190 auto c = context.save(); 191 context.pushContainer(ASTContext.ContainerAttribute.Type.interface_, dec.name.text); 192 visitInterface(dec.name, dec.baseClassList, dec.structBody, false); 193 context.restore(c); 194 } 195 196 private void visitInterface(const Token name, const BaseClassList baseClassList, 197 const StructBody structBody, bool needsOverride) 198 { 199 if (!structBody) 200 return; 201 if (inTarget) 202 return; // ignore nested 203 204 if (targetPosition >= name.index && targetPosition < structBody.endLocation) 205 { 206 details.blockRange = [structBody.startLocation, structBody.endLocation + 1]; 207 details.name = name.text; 208 if (baseClassList) 209 foreach (base; baseClassList.items) 210 { 211 if (!base.type2 || !base.type2.typeIdentifierPart 212 || !base.type2.typeIdentifierPart.identifierOrTemplateInstance) 213 continue; 214 // TODO: template support! 215 details.parents ~= astToString(base.type2); 216 details.normalizedParents ~= astToString(base.type2); 217 details.parentPositions ~= cast( 218 int) base.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier.index + 1; 219 } 220 details.needsOverride = needsOverride; 221 inTarget = true; 222 structBody.accept(new NestedTypeFinder(&details, details.name)); 223 super.visit(structBody); 224 inTarget = false; 225 } 226 } 227 228 override void visit(const FunctionDeclaration dec) 229 { 230 if (!inTarget) 231 return; 232 233 size_t[2] definitionRange = [dec.name.index, 0]; 234 size_t[2] blockRange; 235 236 if (dec.returnType !is null && dec.returnType.tokens.length > 0) 237 definitionRange[0] = dec.returnType.tokens[0].index; 238 239 if (dec.functionBody !is null && dec.functionBody.tokens.length > 0) 240 { 241 definitionRange[1] = dec.functionBody.tokens[0].index; 242 blockRange = [ 243 dec.functionBody.tokens[0].index, dec.functionBody.tokens[$ - 1].index + 1 244 ]; 245 } 246 else if (dec.parameters !is null && dec.parameters.tokens.length > 0) 247 definitionRange[1] = dec.parameters.tokens[$ - 1].index 248 + dec.parameters.tokens[$ - 1].text.length; 249 250 auto origBody = (cast() dec).functionBody; 251 const hasBody = !!origBody && origBody.missingFunctionBody is null; 252 auto origComment = (cast() dec).comment; 253 const implLevel = context.requiredImplementationLevel; 254 const optionalImplementation = implLevel == 1 && !hasBody; 255 const needsImplementation = implLevel == 9 || optionalImplementation; 256 (cast() dec).functionBody = null; 257 (cast() dec).comment = null; 258 scope (exit) 259 { 260 (cast() dec).functionBody = origBody; 261 (cast() dec).comment = origComment; 262 } 263 auto t = appender!string; 264 formatTypeTransforming(t, dec, &resolveType); 265 string method = context.localFormattedAttributes.chain([t.data.strip]) 266 .filter!(a => a.length > 0 && !a.among!("abstract", "final")).join(" "); 267 ArgumentInfo[] arguments; 268 if (dec.parameters) 269 foreach (arg; dec.parameters.parameters) 270 arguments ~= ArgumentInfo(astToString(arg), astToString(arg.type), arg.name.text); 271 string returnType = dec.returnType ? resolveType(astToString(dec.returnType)) : "void"; 272 273 // now visit to populate isNothrow, isNogc (before it would add to the localFormattedAttributes string) 274 // also fills in used types 275 super.visit(dec); 276 277 details.methods ~= MethodDetails(dec.name.text, method, returnType, arguments, context.isNothrowInContainer 278 || context.isNogcInContainer, hasBody, needsImplementation, 279 optionalImplementation, definitionRange, blockRange); 280 } 281 282 override void visit(const FunctionBody) 283 { 284 } 285 286 override void visit(const VariableDeclaration variable) 287 { 288 if (!inTarget) 289 return; 290 if (!variable.type) 291 return; 292 string type = astToString(variable.type); 293 auto isPrivate = context.protectionType == tok!"private"; 294 295 foreach (decl; variable.declarators) 296 details.fields ~= FieldDetails(decl.name.text, type, isPrivate); 297 298 if (variable.type) 299 variable.type.accept(this); // to fill in types 300 } 301 302 override void visit(const TypeIdentifierPart type) 303 { 304 if (!inTarget) 305 return; 306 307 if (type.identifierOrTemplateInstance && !type.typeIdentifierPart) 308 { 309 auto tok = type.identifierOrTemplateInstance.templateInstance 310 ? type.identifierOrTemplateInstance.templateInstance.identifier 311 : type.identifierOrTemplateInstance.identifier; 312 313 usedType(ReferencedType(tok.text, tok.index)); 314 } 315 316 super.visit(type); 317 } 318 319 alias visit = AttributesVisitor.visit; 320 321 protected void usedType(ReferencedType type) 322 { 323 // this is a simple sorted set insert 324 auto sorted = assumeSorted!"a.name < b.name"(details.referencedTypes).trisect(type); 325 if (sorted[1].length) 326 return; // exists already 327 details.referencedTypes.insertInPlace(sorted[0].length, type); 328 } 329 330 string resolveType(const(char)[] inType) 331 { 332 auto parts = inType.splitter('.'); 333 string[] best; 334 foreach (type; details.types) 335 if ((!best.length || type.name.length < best.length) && type.name.endsWith(parts)) 336 best = type.name; 337 338 if (best.length) 339 return best.join("."); 340 else 341 return inType.idup; 342 } 343 344 const(char)[] code; 345 bool inTarget; 346 int targetPosition; 347 InterfaceDetails details; 348 } 349 350 class NestedTypeFinder : ASTVisitor 351 { 352 this(InterfaceDetails* details, string start) 353 { 354 this.details = details; 355 this.nested = [start]; 356 } 357 358 override void visit(const StructDeclaration dec) 359 { 360 handleType(TypeDetails.Type.struct_, dec.name.text, dec.name.index, dec); 361 } 362 363 override void visit(const UnionDeclaration dec) 364 { 365 handleType(TypeDetails.Type.union_, dec.name.text, dec.name.index, dec); 366 } 367 368 override void visit(const EnumDeclaration dec) 369 { 370 handleType(TypeDetails.Type.enum_, dec.name.text, dec.name.index, dec); 371 } 372 373 override void visit(const ClassDeclaration dec) 374 { 375 handleType(TypeDetails.Type.class_, dec.name.text, dec.name.index, dec); 376 } 377 378 override void visit(const InterfaceDeclaration dec) 379 { 380 handleType(TypeDetails.Type.interface_, dec.name.text, dec.name.index, dec); 381 } 382 383 override void visit(const TemplateDeclaration dec) 384 { 385 handleType(TypeDetails.Type.template_, dec.name.text, dec.name.index, dec); 386 } 387 388 override void visit(const AliasDeclaration dec) 389 { 390 if (dec && dec.declaratorIdentifierList) 391 foreach (ident; dec.declaratorIdentifierList.identifiers) 392 details.types ~= TypeDetails(nested ~ ident.text, ident.index, TypeDetails.Type.alias_); 393 } 394 395 void handleType(T)(TypeDetails.Type type, string name, size_t location, T node) 396 { 397 pushNestedType(type, name, location); 398 super.visit(node); 399 popNestedType(); 400 } 401 402 override void visit(const FunctionBody) 403 { 404 } 405 406 alias visit = ASTVisitor.visit; 407 408 protected void pushNestedType(TypeDetails.Type type, string name, size_t index) 409 { 410 nested ~= name; 411 details.types ~= TypeDetails(nested, index, type); 412 } 413 414 protected void popNestedType() 415 { 416 nested.length--; 417 } 418 419 string[] nested; 420 InterfaceDetails* details; 421 } 422 423 void formatTypeTransforming(Sink, T)(Sink sink, T node, string delegate(const(char)[]) translateType, 424 bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWith = 4) 425 { 426 TypeTransformingFormatter!Sink formatter = new TypeTransformingFormatter!(Sink)(sink, 427 useTabs, style, indentWith); 428 formatter.translateType = translateType; 429 formatter.format(node); 430 } 431 432 /// 433 class TypeTransformingFormatter(Sink) : Formatter!Sink 434 { 435 string delegate(const(char)[]) translateType; 436 Appender!(char[]) tempBuffer; 437 bool useTempBuffer; 438 439 this(Sink sink, bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWidth = 4) 440 { 441 super(sink, useTabs, style, indentWidth); 442 tempBuffer = appender!(char[]); 443 } 444 445 override void put(string s) 446 { 447 if (useTempBuffer) 448 tempBuffer.put(s); 449 else 450 super.put(s); 451 } 452 453 protected void flushTempBuffer() 454 { 455 if (!useTempBuffer || tempBuffer.data.empty) 456 return; 457 458 useTempBuffer = false; 459 put(translateType(tempBuffer.data)); 460 tempBuffer.clear(); 461 } 462 463 override void format(const TypeIdentifierPart type) 464 { 465 useTempBuffer = true; 466 467 if (type.dot) 468 { 469 put("."); 470 } 471 if (type.identifierOrTemplateInstance) 472 { 473 format(type.identifierOrTemplateInstance); 474 } 475 if (type.indexer) 476 { 477 flushTempBuffer(); 478 put("["); 479 format(type.indexer); 480 put("]"); 481 } 482 if (type.typeIdentifierPart) 483 { 484 put("."); 485 format(type.typeIdentifierPart); 486 } 487 else 488 { 489 flushTempBuffer(); 490 } 491 } 492 493 override void format(const IdentifierOrTemplateInstance identifierOrTemplateInstance) 494 { 495 with (identifierOrTemplateInstance) 496 { 497 format(identifier); 498 if (templateInstance) 499 { 500 flushTempBuffer(); 501 format(templateInstance); 502 } 503 } 504 } 505 506 alias format = Formatter!Sink.format; 507 }