1 module workspaced.com.dcdext; 2 3 import dparse.ast; 4 import dparse.lexer; 5 import dparse.parser; 6 import dparse.rollback_allocator; 7 8 import core.thread; 9 10 import std.algorithm; 11 import std.array; 12 import std.ascii; 13 import std.conv; 14 import std.file; 15 import std.functional; 16 import std.json; 17 import std.range; 18 import std.string; 19 20 import workspaced.api; 21 import workspaced.com.dcd; 22 import workspaced.com.dfmt; 23 import workspaced.dparseext; 24 25 import workspaced.visitors.classifier; 26 import workspaced.visitors.methodfinder; 27 28 import painlessjson : SerializeIgnore; 29 30 public import workspaced.visitors.methodfinder : InterfaceDetails, FieldDetails, 31 MethodDetails, ArgumentInfo; 32 33 @component("dcdext") 34 class DCDExtComponent : ComponentWrapper 35 { 36 mixin DefaultComponentWrapper; 37 38 static immutable CodeRegionProtection[] mixableProtection = [ 39 CodeRegionProtection.public_ | CodeRegionProtection.default_, 40 CodeRegionProtection.package_, CodeRegionProtection.packageIdentifier, 41 CodeRegionProtection.protected_, CodeRegionProtection.private_ 42 ]; 43 44 /// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}` 45 void load() 46 { 47 if (!refInstance) 48 return; 49 50 config.stringBehavior = StringBehavior.source; 51 } 52 53 /// Extracts calltips help information at a given position. 54 /// The position must be within the arguments of the function and not 55 /// outside the parentheses or inside some child call. 56 /// 57 /// When generating the call parameters for a function definition, the position must be inside the normal parameters, 58 /// otherwise the template arguments will be put as normal arguments. 59 /// 60 /// Returns: the position of significant locations for parameter extraction. 61 /// Params: 62 /// code = code to analyze 63 /// position = byte offset where to check for function arguments 64 /// definition = true if this hints is a function definition (templates don't have an exclamation point '!') 65 CalltipsSupport extractCallParameters(scope const(char)[] code, int position, 66 bool definition = false) 67 { 68 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 69 if (!tokens.length) 70 return CalltipsSupport.init; 71 auto queuedToken = tokens.countUntil!(a => a.index >= position) - 1; 72 if (queuedToken == -2) 73 queuedToken = cast(ptrdiff_t) tokens.length - 1; 74 else if (queuedToken == -1) 75 return CalltipsSupport.init; 76 77 // TODO: refactor code to be more readable 78 // all this code does is: 79 // - go back all tokens until a starting ( is found. (with nested {} scope checks for delegates and () for calls) 80 // - abort if not found 81 // - set "isTemplate" if directly before the ( is a `!` token and an identifier 82 // - if inTemplate is true: 83 // - go forward to starting ( of normal arguments -- this code has checks if startParen is `!`, which currently can't be the case but might be useful 84 // - else not in template arguments, so 85 // - if before ( comes a ) we are definitely in a template, so track back until starting ( 86 // - otherwise check if it's even a template (single argument: `!`, then a token, then `(`) 87 // - determine function name & all parents (strips out index operators) 88 // - split template & function arguments 89 // - return all information 90 // it's reasonably readable with the variable names and that pseudo explanation there pretty much directly maps to the code, 91 // so it shouldn't be too hard of a problem, it's just a lot return values per step and taking in multiple returns from previous steps. 92 93 // describes if the target position is inside template arguments rather than function arguments (only works for calls and not for definition) 94 bool inTemplate; 95 int activeParameter; // counted commas 96 int depth, subDepth; 97 // contains opening parentheses location for arguments or exclamation point for templates. 98 auto startParen = queuedToken; 99 while (startParen >= 0) 100 { 101 const c = tokens[startParen]; 102 const p = startParen > 0 ? tokens[startParen - 1] : Token.init; 103 104 if (c.type == tok!"{") 105 { 106 if (subDepth == 0) 107 { 108 // we went too far, probably not inside a function (or we are in a delegate, where we don't want calltips) 109 return CalltipsSupport.init; 110 } 111 else 112 subDepth--; 113 } 114 else if (c.type == tok!"}") 115 { 116 subDepth++; 117 } 118 else if (subDepth == 0 && c.type == tok!";") 119 { 120 // this doesn't look like function arguments anymore 121 return CalltipsSupport.init; 122 } 123 else if (depth == 0 && !definition && c.type == tok!"!" && p.type == tok!"identifier") 124 { 125 inTemplate = true; 126 break; 127 } 128 else if (c.type == tok!")") 129 { 130 depth++; 131 } 132 else if (c.type == tok!"(") 133 { 134 if (depth == 0 && subDepth == 0) 135 { 136 if (startParen > 1 && p.type == tok!"!" && tokens[startParen - 2].type 137 == tok!"identifier") 138 { 139 startParen--; 140 inTemplate = true; 141 } 142 break; 143 } 144 else 145 depth--; 146 } 147 else if (depth == 0 && subDepth == 0 && c.type == tok!",") 148 { 149 activeParameter++; 150 } 151 startParen--; 152 } 153 154 if (startParen <= 0) 155 return CalltipsSupport.init; 156 157 auto templateOpen = inTemplate ? startParen : 0; 158 auto functionOpen = inTemplate ? 0 : startParen; 159 160 if (inTemplate) 161 { 162 // go forwards to function arguments 163 if (templateOpen + 2 < tokens.length && tokens[templateOpen + 1].type != tok!"(") 164 { 165 // single template arg (can only be one token) 166 // https://dlang.org/spec/grammar.html#TemplateSingleArgument 167 if (tokens[templateOpen + 2] == tok!"(") 168 functionOpen = templateOpen + 2; 169 } 170 else 171 { 172 if (tokens[templateOpen + 2].type != tok!"(") 173 return CalltipsSupport.init; // syntax error 174 175 functionOpen = findClosingParenForward(tokens, templateOpen + 2); 176 177 if (functionOpen >= tokens.length) 178 functionOpen = 0; 179 } 180 } 181 else 182 { 183 // go backwards to template arguments 184 if (functionOpen > 0 && tokens[functionOpen - 1].type == tok!")") 185 { 186 // multi template args 187 depth = 0; 188 subDepth = 0; 189 templateOpen = functionOpen - 1; 190 const minTokenIndex = definition ? 1 : 2; 191 while (templateOpen >= minTokenIndex) 192 { 193 const c = tokens[templateOpen]; 194 195 if (c == tok!")") 196 depth++; 197 else 198 { 199 if (depth == 1 && templateOpen > minTokenIndex && c.type == tok!"(") 200 { 201 if (definition 202 ? tokens[templateOpen - 1].type == tok!"identifier" : (tokens[templateOpen - 1].type == tok!"!" 203 && tokens[templateOpen - 2].type == tok!"identifier")) 204 break; 205 } 206 207 if (depth == 0) 208 { 209 templateOpen = 0; 210 break; 211 } 212 213 if (c == tok!"(") 214 depth--; 215 } 216 217 templateOpen--; 218 } 219 220 if (templateOpen < minTokenIndex) 221 templateOpen = 0; 222 } 223 else 224 { 225 // single template arg (can only be one token) or no template at all here 226 if (functionOpen > 2 && tokens[functionOpen - 2] == tok!"!" 227 && tokens[functionOpen - 3] == tok!"identifier") 228 { 229 templateOpen = functionOpen - 2; 230 } 231 } 232 } 233 234 bool hasTemplateParens = (definition && templateOpen) || (templateOpen 235 && templateOpen != functionOpen - 2); 236 237 depth = 0; 238 subDepth = 0; 239 bool inFuncName = true; 240 auto callStart = (templateOpen ? templateOpen : functionOpen) - 1; 241 auto funcNameStart = callStart; 242 while (callStart >= 0) 243 { 244 const c = tokens[callStart]; 245 const p = callStart > 0 ? tokens[callStart - 1] : Token.init; 246 247 if (c.type == tok!"]") 248 depth++; 249 else if (c.type == tok!"[") 250 { 251 if (depth == 0) 252 { 253 // this is some sort of `foo[(4` situation 254 return CalltipsSupport.init; 255 } 256 depth--; 257 } 258 else if (c.type == tok!")") 259 subDepth++; 260 else if (c.type == tok!"(") 261 { 262 if (subDepth == 0) 263 { 264 // this is some sort of `foo((4` situation 265 return CalltipsSupport.init; 266 } 267 subDepth--; 268 } 269 else if (depth == 0) 270 { 271 272 if (c.type.isCalltipable) 273 { 274 if (c.type == tok!"identifier" && p.type == tok!"." && (callStart < 2 275 || !tokens[callStart - 2].type.among!(tok!";", tok!",", 276 tok!"{", tok!"}", tok!"("))) 277 { 278 // member function, traverse further... 279 if (inFuncName) 280 { 281 funcNameStart = callStart; 282 inFuncName = false; 283 } 284 callStart--; 285 } 286 else 287 { 288 break; 289 } 290 } 291 else 292 { 293 // this is some sort of `4(5` or `if(4` situtation 294 return CalltipsSupport.init; 295 } 296 } 297 // we ignore stuff inside brackets and parens such as `foo[4](5).bar[6](a` 298 callStart--; 299 } 300 301 if (inFuncName) 302 funcNameStart = callStart; 303 304 auto templateClose = templateOpen ? (hasTemplateParens ? (functionOpen 305 ? functionOpen - 1 : findClosingParenForward(tokens, templateOpen + 1)) : templateOpen + 2) 306 : 0; 307 auto functionClose = functionOpen ? findClosingParenForward(tokens, functionOpen) : 0; 308 309 CalltipsSupport.Argument[] templateArgs; 310 if (templateOpen) 311 templateArgs = splitArgs(tokens[templateOpen + 1 .. templateClose]); 312 313 CalltipsSupport.Argument[] functionArgs; 314 if (functionOpen) 315 functionArgs = splitArgs(tokens[functionOpen + 1 .. functionClose]); 316 317 return CalltipsSupport([ 318 tokens.tokenIndex(templateOpen), 319 templateClose ? tokens.tokenEndIndex(templateClose) : 0 320 ], hasTemplateParens, templateArgs, [ 321 tokens.tokenIndex(functionOpen), 322 functionClose ? tokens.tokenEndIndex(functionClose) : 0 323 ], functionArgs, funcNameStart != callStart, tokens.tokenIndex(funcNameStart), 324 tokens.tokenIndex(callStart), inTemplate, activeParameter); 325 } 326 327 /// Finds the immediate surrounding code block at a position or returns CodeBlockInfo.init for none/module block. 328 /// See_Also: CodeBlockInfo 329 CodeBlockInfo getCodeBlockRange(scope const(char)[] code, int position) 330 { 331 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 332 auto parsed = parseModule(tokens, "getCodeBlockRange_input.d", &rba); 333 auto reader = new CodeBlockInfoFinder(position); 334 reader.visit(parsed); 335 return reader.block; 336 } 337 338 /// Inserts a generic method after the corresponding block inside the scope where position is. 339 /// If it can't find a good spot it will insert the code properly indented ata fitting location. 340 // make public once usable 341 private CodeReplacement[] insertCodeInContainer(string insert, scope const(char)[] code, 342 int position, bool insertInLastBlock = true, bool insertAtEnd = true) 343 { 344 auto container = getCodeBlockRange(code, position); 345 346 scope const(char)[] codeBlock = code[container.innerRange[0] .. container.innerRange[1]]; 347 348 scope tokensInsert = getTokensForParser(cast(ubyte[]) insert, config, 349 &workspaced.stringCache); 350 scope parsedInsert = parseModule(tokensInsert, "insertCode_insert.d", &rba); 351 352 scope insertReader = new CodeDefinitionClassifier(insert); 353 insertReader.visit(parsedInsert); 354 scope insertRegions = insertReader.regions.sort!"a.type < b.type".uniq.array; 355 356 scope tokens = getTokensForParser(cast(ubyte[]) codeBlock, config, &workspaced.stringCache); 357 scope parsed = parseModule(tokens, "insertCode_code.d", &rba); 358 359 scope reader = new CodeDefinitionClassifier(codeBlock); 360 reader.visit(parsed); 361 scope regions = reader.regions; 362 363 CodeReplacement[] ret; 364 365 foreach (CodeDefinitionClassifier.Region toInsert; insertRegions) 366 { 367 auto insertCode = insert[toInsert.region[0] .. toInsert.region[1]]; 368 scope existing = regions.enumerate.filter!(a => a.value.sameBlockAs(toInsert)); 369 if (existing.empty) 370 { 371 const checkProtection = CodeRegionProtection.init.reduce!"a | b"( 372 mixableProtection.filter!(a => (a & toInsert.protection) != 0)); 373 374 bool inIncompatible = false; 375 bool lastFit = false; 376 int fittingProtection = -1; 377 int firstStickyProtection = -1; 378 int regionAfterFitting = -1; 379 foreach (i, stickyProtection; regions) 380 { 381 if (stickyProtection.affectsFollowing 382 && stickyProtection.protection != CodeRegionProtection.init) 383 { 384 if (firstStickyProtection == -1) 385 firstStickyProtection = cast(int) i; 386 387 if ((stickyProtection.protection & checkProtection) != 0) 388 { 389 fittingProtection = cast(int) i; 390 lastFit = true; 391 if (!insertInLastBlock) 392 break; 393 } 394 else 395 { 396 if (lastFit) 397 { 398 regionAfterFitting = cast(int) i; 399 lastFit = false; 400 } 401 inIncompatible = true; 402 } 403 } 404 } 405 assert(firstStickyProtection != -1 || !inIncompatible); 406 assert(regionAfterFitting != -1 || fittingProtection == -1 || !inIncompatible); 407 408 if (inIncompatible) 409 { 410 int insertRegion = fittingProtection == -1 ? firstStickyProtection : regionAfterFitting; 411 insertCode = text(indent(insertCode, regions[insertRegion].minIndentation), "\n\n"); 412 auto len = cast(uint) insertCode.length; 413 414 toInsert.region[0] = regions[insertRegion].region[0]; 415 toInsert.region[1] = regions[insertRegion].region[0] + len; 416 foreach (ref r; regions[insertRegion .. $]) 417 { 418 r.region[0] += len; 419 r.region[1] += len; 420 } 421 } 422 else 423 { 424 auto lastRegion = regions.back; 425 insertCode = indent(insertCode, lastRegion.minIndentation).idup; 426 auto len = cast(uint) insertCode.length; 427 toInsert.region[0] = lastRegion.region[1]; 428 toInsert.region[1] = lastRegion.region[1] + len; 429 } 430 regions ~= toInsert; 431 ret ~= CodeReplacement([toInsert.region[0], toInsert.region[0]], insertCode); 432 } 433 else 434 { 435 auto target = insertInLastBlock ? existing.tail(1).front : existing.front; 436 437 insertCode = text("\n\n", indent(insertCode, regions[target.index].minIndentation)); 438 const codeLength = cast(int) insertCode.length; 439 440 if (insertAtEnd) 441 { 442 ret ~= CodeReplacement([ 443 target.value.region[1], target.value.region[1] 444 ], insertCode); 445 toInsert.region[0] = target.value.region[1]; 446 toInsert.region[1] = target.value.region[1] + codeLength; 447 regions[target.index].region[1] = toInsert.region[1]; 448 foreach (ref other; regions[target.index + 1 .. $]) 449 { 450 other.region[0] += codeLength; 451 other.region[1] += codeLength; 452 } 453 } 454 else 455 { 456 ret ~= CodeReplacement([ 457 target.value.region[0], target.value.region[0] 458 ], insertCode); 459 regions[target.index].region[1] += codeLength; 460 foreach (ref other; regions[target.index + 1 .. $]) 461 { 462 other.region[0] += codeLength; 463 other.region[1] += codeLength; 464 } 465 } 466 } 467 } 468 469 return ret; 470 } 471 472 /// Implements the interfaces or abstract classes of a specified class/interface. 473 /// Helper function which returns all functions as one block for most primitive use. 474 Future!string implement(scope const(char)[] code, int position, 475 bool formatCode = true, string[] formatArgs = []) 476 { 477 auto ret = new Future!string; 478 gthreads.create({ 479 mixin(traceTask); 480 try 481 { 482 auto impl = implementAllSync(code, position, formatCode, formatArgs); 483 484 auto buf = appender!string; 485 string lastBaseClass; 486 foreach (ref func; impl) 487 { 488 if (func.baseClass != lastBaseClass) 489 { 490 buf.put("// implement " ~ func.baseClass ~ "\n\n"); 491 lastBaseClass = func.baseClass; 492 } 493 494 buf.put(func.code); 495 buf.put("\n\n"); 496 } 497 ret.finish(buf.data.length > 2 ? buf.data[0 .. $ - 2] : buf.data); 498 } 499 catch (Throwable t) 500 { 501 ret.error(t); 502 } 503 }); 504 return ret; 505 } 506 507 /// Implements the interfaces or abstract classes of a specified class/interface. 508 /// The async implementation is preferred when used in background tasks to prevent disruption 509 /// of other services as a lot of code is parsed and processed multiple times for this function. 510 /// Params: 511 /// code = input file to parse and edit. 512 /// position = position of the superclass or interface to implement after the colon in a class definition. 513 /// formatCode = automatically calls dfmt on all function bodys when true. 514 /// formatArgs = sets the formatter arguments to pass to dfmt if formatCode is true. 515 /// snippetExtensions = if true, snippets according to the vscode documentation will be inserted in place of method content. See https://code.visualstudio.com/docs/editor/userdefinedsnippets#_creating-your-own-snippets 516 /// Returns: a list of newly implemented methods 517 Future!(ImplementedMethod[]) implementAll(scope const(char)[] code, int position, 518 bool formatCode = true, string[] formatArgs = [], bool snippetExtensions = false) 519 { 520 mixin( 521 gthreadsAsyncProxy!`implementAllSync(code, position, formatCode, formatArgs, snippetExtensions)`); 522 } 523 524 /// ditto 525 ImplementedMethod[] implementAllSync(scope const(char)[] code, int position, 526 bool formatCode = true, string[] formatArgs = [], bool snippetExtensions = false) 527 { 528 auto tree = describeInterfaceRecursiveSync(code, position); 529 auto availableVariables = tree.availableVariables; 530 531 string[] implementedMethods = tree.details 532 .methods 533 .filter!"!a.needsImplementation" 534 .map!"a.identifier" 535 .array; 536 537 int snippetIndex = 0; 538 // maintains snippet ids and their value in an AA so they can be replaced after formatting 539 string[string] snippetReplacements; 540 541 auto methods = appender!(ImplementedMethod[]); 542 void processTree(ref InterfaceTree tree) 543 { 544 auto details = tree.details; 545 if (details.methods.length) 546 { 547 foreach (fn; details.methods) 548 { 549 if (implementedMethods.canFind(fn.identifier)) 550 continue; 551 if (!fn.needsImplementation) 552 { 553 implementedMethods ~= fn.identifier; 554 continue; 555 } 556 557 //dfmt off 558 ImplementedMethod method = { 559 baseClass: details.name, 560 name: fn.name 561 }; 562 //dfmt on 563 auto buf = appender!string; 564 565 snippetIndex++; 566 bool writtenSnippet; 567 string snippetId; 568 auto snippetBuf = appender!string; 569 570 void startSnippet(bool withDefault = true) 571 { 572 if (writtenSnippet || !snippetExtensions) 573 return; 574 snippetId = format!`/+++__WORKSPACED_SNIPPET__%s__+++/`(snippetIndex); 575 buf.put(snippetId); 576 swap(buf, snippetBuf); 577 buf.put("${"); 578 buf.put(snippetIndex.to!string); 579 if (withDefault) 580 buf.put(":"); 581 writtenSnippet = true; 582 } 583 584 void endSnippet() 585 { 586 if (!writtenSnippet || !snippetExtensions) 587 return; 588 buf.put("}"); 589 590 swap(buf, snippetBuf); 591 snippetReplacements[snippetId] = snippetBuf.data; 592 } 593 594 if (details.needsOverride) 595 buf.put("override "); 596 buf.put(fn.signature[0 .. $ - 1]); 597 buf.put(" {"); 598 if (fn.optionalImplementation) 599 { 600 buf.put("\n\t"); 601 startSnippet(); 602 buf.put("// TODO: optional implementation\n"); 603 } 604 605 string propertySearch; 606 if (fn.signature.canFind("@property") && fn.arguments.length <= 1) 607 propertySearch = fn.name; 608 else if ((fn.name.startsWith("get") && fn.arguments.length == 0) 609 || (fn.name.startsWith("set") && fn.arguments.length == 1)) 610 propertySearch = fn.name[3 .. $]; 611 612 string foundProperty; 613 if (propertySearch) 614 { 615 // frontOrDefault 616 const matching = availableVariables.find!(a => fieldNameMatches(a.name, 617 propertySearch)); 618 if (!matching.empty) 619 foundProperty = matching.front.name; 620 } 621 622 if (foundProperty.length) 623 { 624 method.autoProperty = true; 625 buf.put("\n\t"); 626 startSnippet(); 627 if (fn.returnType != "void") 628 { 629 method.getter = true; 630 buf.put("return "); 631 } 632 633 if (fn.name.startsWith("set") || fn.arguments.length == 1) 634 { 635 method.setter = true; 636 buf.put(foundProperty ~ " = " ~ fn.arguments[0].name); 637 } 638 else 639 { 640 // neither getter nor setter, but we will just put the property here anyway 641 buf.put(foundProperty); 642 } 643 buf.put(";"); 644 endSnippet(); 645 buf.put("\n"); 646 } 647 else if (fn.hasBody) 648 { 649 method.callsSuper = true; 650 buf.put("\n\t"); 651 startSnippet(); 652 if (fn.returnType != "void") 653 buf.put("return "); 654 buf.put("super." ~ fn.name); 655 if (fn.arguments.length) 656 buf.put("(" ~ format("%(%s, %)", fn.arguments) 657 .translate(['\\': `\\`, '{': `\{`, '$': `\$`, '}': `\}`]) ~ ")"); 658 else if (fn.returnType == "void") 659 buf.put("()"); // make functions that don't return add (), otherwise they might be attributes and don't need that 660 buf.put(";"); 661 endSnippet(); 662 buf.put("\n"); 663 } 664 else if (fn.returnType != "void") 665 { 666 method.debugImpl = true; 667 buf.put("\n\t"); 668 if (snippetExtensions) 669 { 670 startSnippet(false); 671 buf.put('|'); 672 // choice snippet 673 674 if (fn.returnType.endsWith("[]")) 675 buf.put("return null; // TODO: implement"); 676 else 677 buf.put("return " ~ fn.returnType.translate([ 678 '\\': `\\`, 679 '{': `\{`, 680 '$': `\$`, 681 '}': `\}`, 682 '|': `\|`, 683 ',': `\,` 684 ]) ~ ".init; // TODO: implement"); 685 686 buf.put(','); 687 688 buf.put(`assert(false\, "Method ` ~ fn.name ~ ` not implemented");`); 689 690 buf.put('|'); 691 endSnippet(); 692 } 693 else 694 { 695 if (fn.isNothrowOrNogc) 696 { 697 if (fn.returnType.endsWith("[]")) 698 buf.put("return null; // TODO: implement"); 699 else 700 buf.put("return " ~ fn.returnType.translate([ 701 '\\': `\\`, 702 '{': `\{`, 703 '$': `\$`, 704 '}': `\}` 705 ]) ~ ".init; // TODO: implement"); 706 } 707 else 708 buf.put(`assert(false, "Method ` ~ fn.name ~ ` not implemented");`); 709 } 710 buf.put("\n"); 711 } 712 else if (snippetExtensions) 713 { 714 buf.put("\n\t"); 715 startSnippet(false); 716 endSnippet(); 717 buf.put("\n"); 718 } 719 720 buf.put("}"); 721 722 method.code = buf.data; 723 methods.put(method); 724 } 725 } 726 727 foreach (parent; tree.inherits) 728 processTree(parent); 729 } 730 731 processTree(tree); 732 733 if (formatCode && instance.has!DfmtComponent) 734 { 735 foreach (ref method; methods.data) 736 method.code = instance.get!DfmtComponent.formatSync(method.code, formatArgs).strip; 737 } 738 739 foreach (ref method; methods.data) 740 { 741 // TODO: replacing using aho-corasick would be far more efficient but there is nothing like that in phobos 742 foreach (key, value; snippetReplacements) 743 { 744 method.code = method.code.replace(key, value); 745 } 746 } 747 748 return methods.data; 749 } 750 751 /// Looks up a declaration of a type and then extracts information about it as class or interface. 752 InterfaceDetails lookupInterface(scope const(char)[] code, int position) 753 { 754 auto data = get!DCDComponent.findDeclaration(code, position).getBlocking; 755 string file = data.file; 756 int newPosition = data.position; 757 758 if (!file.length) 759 return InterfaceDetails.init; 760 761 auto newCode = code; 762 if (file != "stdin") 763 newCode = readText(file); 764 765 return getInterfaceDetails(file, newCode, newPosition); 766 } 767 768 /// Extracts information about a given class or interface at the given position. 769 InterfaceDetails getInterfaceDetails(string file, scope const(char)[] code, int position) 770 { 771 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 772 auto parsed = parseModule(tokens, file, &rba); 773 auto reader = new InterfaceMethodFinder(code, position); 774 reader.visit(parsed); 775 return reader.details; 776 } 777 778 Future!InterfaceTree describeInterfaceRecursive(scope const(char)[] code, int position) 779 { 780 mixin(gthreadsAsyncProxy!`describeInterfaceRecursiveSync(code, position)`); 781 } 782 783 InterfaceTree describeInterfaceRecursiveSync(scope const(char)[] code, int position) 784 { 785 auto baseInterface = getInterfaceDetails("stdin", code, position); 786 787 InterfaceTree tree = InterfaceTree(baseInterface); 788 789 InterfaceTree* treeByName(InterfaceTree* tree, string name) 790 { 791 if (tree.details.name == name) 792 return tree; 793 foreach (ref parent; tree.inherits) 794 { 795 InterfaceTree* t = treeByName(&parent, name); 796 if (t !is null) 797 return t; 798 } 799 return null; 800 } 801 802 void traverseTree(ref InterfaceTree sub) 803 { 804 foreach (i, parent; sub.details.parentPositions) 805 { 806 string parentName = sub.details.normalizedParents[i]; 807 if (treeByName(&tree, parentName) is null) 808 { 809 auto details = lookupInterface(sub.details.code, parent); 810 details.name = parentName; 811 sub.inherits ~= InterfaceTree(details); 812 } 813 } 814 foreach (ref inherit; sub.inherits) 815 traverseTree(inherit); 816 } 817 818 traverseTree(tree); 819 820 return tree; 821 } 822 823 private: 824 RollbackAllocator rba; 825 LexerConfig config; 826 } 827 828 /// 829 enum CodeRegionType : int 830 { 831 /// null region (unset) 832 init, 833 /// Imports inside the block 834 imports = 1 << 0, 835 /// Aliases `alias foo this;`, `alias Type = Other;` 836 aliases = 1 << 1, 837 /// Nested classes/structs/unions/etc. 838 types = 1 << 2, 839 /// Raw variables `Type name;` 840 fields = 1 << 3, 841 /// Normal constructors `this(Args args)` 842 ctor = 1 << 4, 843 /// Copy constructors `this(this)` 844 copyctor = 1 << 5, 845 /// Destructors `~this()` 846 dtor = 1 << 6, 847 /// Properties (functions annotated with `@property`) 848 properties = 1 << 7, 849 /// Regular functions 850 methods = 1 << 8, 851 } 852 853 /// 854 enum CodeRegionProtection : int 855 { 856 /// null protection (unset) 857 init, 858 /// default (unmarked) protection 859 default_ = 1 << 0, 860 /// public protection 861 public_ = 1 << 1, 862 /// package (automatic) protection 863 package_ = 1 << 2, 864 /// package (manual package name) protection 865 packageIdentifier = 1 << 3, 866 /// protected protection 867 protected_ = 1 << 4, 868 /// private protection 869 private_ = 1 << 5, 870 } 871 872 /// 873 enum CodeRegionStatic : int 874 { 875 /// null static (unset) 876 init, 877 /// non-static code 878 instanced = 1 << 0, 879 /// static code 880 static_ = 1 << 1, 881 } 882 883 /// Represents a class/interface/struct/union/template with body. 884 struct CodeBlockInfo 885 { 886 /// 887 enum Type : int 888 { 889 // keep the underlines in these values for range checking properly 890 891 /// 892 class_, 893 /// 894 interface_, 895 /// 896 struct_, 897 /// 898 union_, 899 /// 900 template_, 901 } 902 903 static immutable string[] typePrefixes = [ 904 "class ", "interface ", "struct ", "union ", "template " 905 ]; 906 907 /// 908 Type type; 909 /// 910 string name; 911 /// Outer range inside the code spanning curly braces and name but not type keyword. 912 uint[2] outerRange; 913 /// Inner range of body of the block touching, but not spanning curly braces. 914 uint[2] innerRange; 915 916 string prefix() @property 917 { 918 return typePrefixes[cast(int) type]; 919 } 920 } 921 922 /// 923 struct CalltipsSupport 924 { 925 /// 926 struct Argument 927 { 928 /// Ranges of type, name and value not including commas or parentheses, but being right next to them. For calls this is the only important and accurate value. 929 int[2] contentRange; 930 /// Range of just the type, or for templates also `alias` 931 int[2] typeRange; 932 /// Range of just the name 933 int[2] nameRange; 934 /// Range of just the default value 935 int[2] valueRange; 936 /// True if the type declaration is variadic (using ...), or without typeRange a completely variadic argument 937 bool variadic; 938 939 /// Creates Argument(range, range, range, 0) 940 static Argument templateType(int[2] range) 941 { 942 return Argument(range, range, range); 943 } 944 945 /// Creates Argument(range, 0, range, range) 946 static Argument templateValue(int[2] range) 947 { 948 return Argument(range, typeof(range).init, range, range); 949 } 950 951 /// Creates Argument(range, 0, 0, 0, true) 952 static Argument anyVariadic(int[2] range) 953 { 954 return Argument(range, typeof(range).init, typeof(range).init, typeof(range).init, true); 955 } 956 } 957 958 bool hasTemplate() @property 959 { 960 return hasTemplateParens || templateArgumentsRange != typeof(templateArgumentsRange).init; 961 } 962 963 /// Range starting before exclamation point until after closing bracket or before function opening bracket. 964 int[2] templateArgumentsRange; 965 /// 966 bool hasTemplateParens; 967 /// 968 Argument[] templateArgs; 969 /// Range starting before opening parentheses until after closing parentheses. 970 int[2] functionParensRange; 971 /// 972 Argument[] functionArgs; 973 /// True if the function is UFCS or a member function of some object or namespace. 974 /// False if this is a global function call. 975 bool hasParent; 976 /// Start of the function itself. 977 int functionStart; 978 /// Start of the whole call going up all call parents. (`foo.bar.function` having `foo.bar` as parents) 979 int parentStart; 980 /// True if cursor is in template parameters 981 bool inTemplateParameters; 982 /// Number of the active parameter (where the cursor is) or -1 if in none 983 int activeParameter = -1; 984 } 985 986 /// Represents one method automatically implemented off a base interface. 987 struct ImplementedMethod 988 { 989 /// Contains the interface or class name from where this method is implemented. 990 string baseClass; 991 /// The name of the function being implemented. 992 string name; 993 /// True if an automatic implementation calling the base class has been made. 994 bool callsSuper; 995 /// True if a default implementation that should definitely be changed (assert or for nogc/nothrow simple init return) has been implemented. 996 bool debugImpl; 997 /// True if the method has been detected as property and implemented as such. 998 bool autoProperty; 999 /// True if the method is either a getter or a setter but not both. Is none for non-autoProperty methods but also when a getter has been detected but the method returns void. 1000 bool getter, setter; 1001 /// Actual code to insert for this class without class indentation but optionally already formatted. 1002 string code; 1003 } 1004 1005 /// Contains details about an interface or class and all extended or implemented interfaces/classes recursively. 1006 struct InterfaceTree 1007 { 1008 /// Details of the template in question. 1009 InterfaceDetails details; 1010 /// All inherited classes in lexical order. 1011 InterfaceTree[] inherits; 1012 1013 @SerializeIgnore const(FieldDetails)[] availableVariables(bool onlyPublic = false) const 1014 { 1015 if (!inherits.length && !onlyPublic) 1016 return details.fields; 1017 1018 // start with private, add all the public ones later in traverseTree 1019 auto ret = appender!(typeof(return)); 1020 if (onlyPublic) 1021 ret.put(details.fields.filter!(a => !a.isPrivate)); 1022 else 1023 ret.put(details.fields); 1024 1025 foreach (sub; inherits) 1026 ret.put(sub.availableVariables(true)); 1027 1028 return ret.data; 1029 } 1030 } 1031 1032 private: 1033 1034 bool isCalltipable(IdType type) 1035 { 1036 return type == tok!"identifier" || type == tok!"assert" || type == tok!"import" 1037 || type == tok!"mixin" || type == tok!"super" || type == tok!"this" || type == tok!"__traits"; 1038 } 1039 1040 int[2] tokenRange(const Token token) 1041 { 1042 return [cast(int) token.index, cast(int)(token.index + token.tokenText.length)]; 1043 } 1044 1045 int tokenEnd(const Token token) 1046 { 1047 return cast(int)(token.index + token.tokenText.length); 1048 } 1049 1050 int tokenIndex(const(Token)[] tokens, ptrdiff_t i) 1051 { 1052 if (i > 0 && i == tokens.length) 1053 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].tokenText.length); 1054 return i >= 0 ? cast(int) tokens[i].index : 0; 1055 } 1056 1057 int tokenEndIndex(const(Token)[] tokens, ptrdiff_t i) 1058 { 1059 if (i > 0 && i == tokens.length) 1060 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length); 1061 return i >= 0 ? cast(int)(tokens[i].index + tokens[i].tokenText.length) : 0; 1062 } 1063 1064 ptrdiff_t findClosingParenForward(const(Token)[] tokens, ptrdiff_t open) 1065 in(tokens[open].type == tok!"(", 1066 "Calling findClosingParenForward must be done on a ( token and not on a " ~ str( 1067 tokens[open].type) ~ "token!") 1068 { 1069 if (open >= tokens.length || open < 0) 1070 return open; 1071 1072 open++; 1073 1074 int depth = 1; 1075 int subDepth = 0; 1076 while (open < tokens.length) 1077 { 1078 const c = tokens[open]; 1079 1080 if (c == tok!"(") 1081 depth++; 1082 else if (c == tok!"{") 1083 subDepth++; 1084 else if (c == tok!"}") 1085 { 1086 if (subDepth == 0) 1087 break; 1088 subDepth--; 1089 } 1090 else 1091 { 1092 if (c == tok!";" && subDepth == 0) 1093 break; 1094 1095 if (c == tok!")") 1096 depth--; 1097 1098 if (depth == 0) 1099 break; 1100 } 1101 1102 open++; 1103 } 1104 return open; 1105 } 1106 1107 CalltipsSupport.Argument[] splitArgs(const(Token)[] tokens) 1108 { 1109 auto ret = appender!(CalltipsSupport.Argument[]); 1110 size_t start = 0; 1111 size_t valueStart = 0; 1112 1113 int depth, subDepth; 1114 const targetDepth = tokens.length > 0 && tokens[0].type == tok!"(" ? 1 : 0; 1115 bool gotValue; 1116 1117 void putArg(size_t end) 1118 { 1119 if (start >= end || start >= tokens.length) 1120 return; 1121 1122 CalltipsSupport.Argument arg; 1123 1124 auto typename = tokens[start .. end]; 1125 arg.contentRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1126 if (typename.length == 1) 1127 { 1128 auto t = typename[0]; 1129 if (t.type == tok!"identifier" || t.type.isBasicType) 1130 arg = CalltipsSupport.Argument.templateType(t.tokenRange); 1131 else if (t.type == tok!"...") 1132 arg = CalltipsSupport.Argument.anyVariadic(t.tokenRange); 1133 else 1134 arg = CalltipsSupport.Argument.templateValue(t.tokenRange); 1135 } 1136 else 1137 { 1138 if (gotValue && valueStart > start && valueStart <= end) 1139 { 1140 typename = tokens[start .. valueStart]; 1141 auto val = tokens[valueStart .. end]; 1142 if (val.length) 1143 arg.valueRange = [cast(int) val[0].index, val[$ - 1].tokenEnd]; 1144 } 1145 1146 else if (typename.length == 1) 1147 { 1148 auto t = typename[0]; 1149 if (t.type == tok!"identifier" || t.type.isBasicType) 1150 arg.typeRange = arg.nameRange = t.tokenRange; 1151 else 1152 arg.typeRange = t.tokenRange; 1153 } 1154 else if (typename.length) 1155 { 1156 if (typename[$ - 1].type == tok!"identifier") 1157 { 1158 arg.nameRange = typename[$ - 1].tokenRange; 1159 typename = typename[0 .. $ - 1]; 1160 } 1161 else if (typename[$ - 1].type == tok!"...") 1162 { 1163 arg.variadic = true; 1164 if (typename.length > 1 && typename[$ - 2].type == tok!"identifier") 1165 { 1166 arg.nameRange = typename[$ - 2].tokenRange; 1167 typename = typename[0 .. $ - 2]; 1168 } 1169 else 1170 typename = typename[0 .. 0]; 1171 } 1172 1173 if (typename.length) 1174 arg.typeRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1175 } 1176 } 1177 1178 ret.put(arg); 1179 1180 gotValue = false; 1181 start = end + 1; 1182 } 1183 1184 foreach (i, token; tokens) 1185 { 1186 if (token.type == tok!"{") 1187 subDepth++; 1188 else if (token.type == tok!"}") 1189 { 1190 if (subDepth == 0) 1191 break; 1192 subDepth--; 1193 } 1194 else if (token.type == tok!"(" || token.type == tok!"[") 1195 depth++; 1196 else if (token.type == tok!")" || token.type == tok!"]") 1197 { 1198 if (depth <= targetDepth) 1199 break; 1200 depth--; 1201 } 1202 1203 if (depth == targetDepth) 1204 { 1205 if (token.type == tok!",") 1206 putArg(i); 1207 else if (token.type == tok!":" || token.type == tok!"=") 1208 { 1209 if (!gotValue) 1210 { 1211 valueStart = i + 1; 1212 gotValue = true; 1213 } 1214 } 1215 } 1216 } 1217 putArg(tokens.length); 1218 1219 return ret.data; 1220 } 1221 1222 auto indent(scope const(char)[] code, string indentation) 1223 { 1224 return code.lineSplitter!(KeepTerminator.yes) 1225 .map!(a => a.length ? indentation ~ a : a) 1226 .join; 1227 } 1228 1229 bool fieldNameMatches(string field, in char[] expected) 1230 { 1231 import std.uni : sicmp; 1232 1233 if (field.startsWith("_")) 1234 field = field[1 .. $]; 1235 else if (field.startsWith("m_")) 1236 field = field[2 .. $]; 1237 else if (field.length >= 2 && field[0] == 'm' && field[1].isUpper) 1238 field = field[1 .. $]; 1239 1240 return field.sicmp(expected) == 0; 1241 } 1242 1243 final class CodeBlockInfoFinder : ASTVisitor 1244 { 1245 this(int targetPosition) 1246 { 1247 this.targetPosition = targetPosition; 1248 } 1249 1250 override void visit(const ClassDeclaration dec) 1251 { 1252 visitContainer(dec.name, CodeBlockInfo.Type.class_, dec.structBody); 1253 } 1254 1255 override void visit(const InterfaceDeclaration dec) 1256 { 1257 visitContainer(dec.name, CodeBlockInfo.Type.interface_, dec.structBody); 1258 } 1259 1260 override void visit(const StructDeclaration dec) 1261 { 1262 visitContainer(dec.name, CodeBlockInfo.Type.struct_, dec.structBody); 1263 } 1264 1265 override void visit(const UnionDeclaration dec) 1266 { 1267 visitContainer(dec.name, CodeBlockInfo.Type.union_, dec.structBody); 1268 } 1269 1270 override void visit(const TemplateDeclaration dec) 1271 { 1272 if (cast(int) targetPosition >= cast(int) dec.name.index && targetPosition < dec.endLocation) 1273 { 1274 block = CodeBlockInfo.init; 1275 block.type = CodeBlockInfo.Type.template_; 1276 block.name = dec.name.text; 1277 block.outerRange = [ 1278 cast(uint) dec.name.index, cast(uint) dec.endLocation + 1 1279 ]; 1280 block.innerRange = [ 1281 cast(uint) dec.startLocation + 1, cast(uint) dec.endLocation 1282 ]; 1283 dec.accept(this); 1284 } 1285 } 1286 1287 private void visitContainer(const Token name, CodeBlockInfo.Type type, const StructBody structBody) 1288 { 1289 if (!structBody) 1290 return; 1291 if (cast(int) targetPosition >= cast(int) name.index && targetPosition < structBody.endLocation) 1292 { 1293 block = CodeBlockInfo.init; 1294 block.type = type; 1295 block.name = name.text; 1296 block.outerRange = [ 1297 cast(uint) name.index, cast(uint) structBody.endLocation + 1 1298 ]; 1299 block.innerRange = [ 1300 cast(uint) structBody.startLocation + 1, cast(uint) structBody.endLocation 1301 ]; 1302 structBody.accept(this); 1303 } 1304 } 1305 1306 alias visit = ASTVisitor.visit; 1307 1308 CodeBlockInfo block; 1309 int targetPosition; 1310 } 1311 1312 version (unittest) static immutable string SimpleClassTestCode = q{ 1313 module foo; 1314 1315 class FooBar 1316 { 1317 public: 1318 int i; // default instanced fields 1319 string s; 1320 long l; 1321 1322 public this() // public instanced ctor 1323 { 1324 i = 4; 1325 } 1326 1327 protected: 1328 int x; // protected instanced field 1329 1330 private: 1331 static const int foo() @nogc nothrow pure @system // private static methods 1332 { 1333 if (s == "a") 1334 { 1335 i = 5; 1336 } 1337 } 1338 1339 static void bar1() {} 1340 1341 void bar2() {} // private instanced methods 1342 void bar3() {} 1343 1344 struct Something { string bar; } 1345 1346 FooBar.Something somefunc() { return Something.init; } 1347 Something somefunc2() { return Something.init; } 1348 }}; 1349 1350 unittest 1351 { 1352 scope backend = new WorkspaceD(); 1353 auto workspace = makeTemporaryTestingWorkspace; 1354 auto instance = backend.addInstance(workspace.directory); 1355 backend.register!DCDExtComponent; 1356 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1357 1358 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 123) == CodeBlockInfo(CodeBlockInfo.Type.class_, 1359 "FooBar", [20, SimpleClassTestCode.length], [ 1360 28, SimpleClassTestCode.length - 1 1361 ])); 1362 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 19) == CodeBlockInfo.init); 1363 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 20) != CodeBlockInfo.init); 1364 1365 auto replacements = dcdext.insertCodeInContainer("void foo()\n{\n\twriteln();\n}", 1366 SimpleClassTestCode, 123); 1367 1368 // TODO: make insertCodeInContainer work properly? 1369 } 1370 1371 unittest 1372 { 1373 import std.conv; 1374 1375 scope backend = new WorkspaceD(); 1376 auto workspace = makeTemporaryTestingWorkspace; 1377 auto instance = backend.addInstance(workspace.directory); 1378 backend.register!DCDExtComponent; 1379 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1380 1381 auto extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg\n\nfoo(); int y;", 23); 1382 assert(!extract.hasTemplate); 1383 assert(extract.parentStart == 7); 1384 assert(extract.functionStart == 11); 1385 assert(extract.functionParensRange[0] == 14); 1386 assert(extract.functionParensRange[1] <= 31); 1387 assert(extract.functionArgs.length == 2); 1388 assert(extract.functionArgs[0].contentRange == [15, 16]); 1389 assert(extract.functionArgs[1].contentRange[0] == 18); 1390 assert(extract.functionArgs[1].contentRange[1] <= 31); 1391 1392 extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg)\n\nfoo(); int y;", 23); 1393 assert(!extract.hasTemplate); 1394 assert(extract.parentStart == 7); 1395 assert(extract.functionStart == 11); 1396 assert(extract.functionParensRange == [14, 24]); 1397 assert(extract.functionArgs.length == 2); 1398 assert(extract.functionArgs[0].contentRange == [15, 16]); 1399 assert(extract.functionArgs[1].contentRange == [18, 23]); 1400 1401 extract = dcdext.extractCallParameters("void foo()", 9, true); 1402 assert(extract != CalltipsSupport.init); 1403 extract = dcdext.extractCallParameters("void foo()", 10, true); 1404 assert(extract == CalltipsSupport.init); 1405 1406 // caused segfault once, doesn't return anything important 1407 extract = dcdext.extractCallParameters(`SomeType!(int,"int_")foo(T,Args...)(T a,T b,string[string] map,Other!"(" stuff1,SomeType!(double,")double")myType,Other!"(" stuff,Other!")")`, 1408 140, true); 1409 assert(extract == CalltipsSupport.init); 1410 1411 extract = dcdext.extractCallParameters( 1412 `auto bar(int foo, Button, my.Callback cb, ..., int[] arr ...)`, 60, true); 1413 assert(extract != CalltipsSupport.init); 1414 assert(!extract.hasTemplate); 1415 assert(!extract.inTemplateParameters); 1416 assert(extract.activeParameter == 4); 1417 assert(extract.functionStart == 5); 1418 assert(extract.parentStart == 5); 1419 assert(extract.functionParensRange == [8, 61]); 1420 assert(extract.functionArgs.length == 5); 1421 assert(extract.functionArgs[0].contentRange == [9, 16]); 1422 assert(extract.functionArgs[0].typeRange == [9, 12]); 1423 assert(extract.functionArgs[0].nameRange == [13, 16]); 1424 assert(extract.functionArgs[1].contentRange == [18, 24]); 1425 assert(extract.functionArgs[1].typeRange == [18, 24]); 1426 assert(extract.functionArgs[1].nameRange == [18, 24]); 1427 assert(extract.functionArgs[2].contentRange == [26, 40]); 1428 assert(extract.functionArgs[2].typeRange == [26, 37]); 1429 assert(extract.functionArgs[2].nameRange == [38, 40]); 1430 assert(extract.functionArgs[3].contentRange == [42, 45]); 1431 assert(extract.functionArgs[3].variadic); 1432 assert(extract.functionArgs[4].contentRange == [47, 60]); 1433 assert(extract.functionArgs[4].typeRange == [47, 52]); 1434 assert(extract.functionArgs[4].nameRange == [53, 56]); 1435 assert(extract.functionArgs[4].variadic); 1436 1437 extract = dcdext.extractCallParameters(q{SomeType!(int, "int_") foo(T, Args...)(T a, T b, string[string] map, Other!"(" stuff1, SomeType!(double, ")double") myType, Other!"(" stuff, Other!")")}, 1438 150, true); 1439 assert(extract != CalltipsSupport.init); 1440 assert(extract.hasTemplate); 1441 assert(extract.templateArgumentsRange == [26, 38]); 1442 assert(extract.templateArgs.length == 2); 1443 assert(extract.templateArgs[0].contentRange == [27, 28]); 1444 assert(extract.templateArgs[0].nameRange == [27, 28]); 1445 assert(extract.templateArgs[1].contentRange == [30, 37]); 1446 assert(extract.templateArgs[1].nameRange == [30, 34]); 1447 assert(extract.functionStart == 23); 1448 assert(extract.parentStart == 23); 1449 assert(extract.functionParensRange == [38, 151]); 1450 assert(extract.functionArgs.length == 7); 1451 assert(extract.functionArgs[0].contentRange == [39, 42]); 1452 assert(extract.functionArgs[0].typeRange == [39, 40]); 1453 assert(extract.functionArgs[0].nameRange == [41, 42]); 1454 assert(extract.functionArgs[1].contentRange == [44, 47]); 1455 assert(extract.functionArgs[1].typeRange == [44, 45]); 1456 assert(extract.functionArgs[1].nameRange == [46, 47]); 1457 assert(extract.functionArgs[2].contentRange == [49, 67]); 1458 assert(extract.functionArgs[2].typeRange == [49, 63]); 1459 assert(extract.functionArgs[2].nameRange == [64, 67]); 1460 assert(extract.functionArgs[3].contentRange == [69, 85]); 1461 assert(extract.functionArgs[3].typeRange == [69, 78]); 1462 assert(extract.functionArgs[3].nameRange == [79, 85]); 1463 assert(extract.functionArgs[4].contentRange == [87, 122]); 1464 assert(extract.functionArgs[4].typeRange == [87, 115]); 1465 assert(extract.functionArgs[4].nameRange == [116, 122]); 1466 assert(extract.functionArgs[5].contentRange == [124, 139]); 1467 assert(extract.functionArgs[5].typeRange == [124, 133]); 1468 assert(extract.functionArgs[5].nameRange == [134, 139]); 1469 assert(extract.functionArgs[6].contentRange == [141, 150]); 1470 assert(extract.functionArgs[6].typeRange == [141, 150]); 1471 1472 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4`, 44); 1473 assert(extract != CalltipsSupport.init); 1474 assert(!extract.hasTemplate); 1475 assert(extract.activeParameter == 0); 1476 assert(extract.functionStart == 34); 1477 assert(extract.parentStart == 34); 1478 assert(extract.functionArgs.length == 1); 1479 assert(extract.functionArgs[0].contentRange == [43, 44]); 1480 1481 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4, f(4)`, 50); 1482 assert(extract != CalltipsSupport.init); 1483 assert(!extract.hasTemplate); 1484 assert(extract.activeParameter == 1); 1485 assert(extract.functionStart == 34); 1486 assert(extract.parentStart == 34); 1487 assert(extract.functionArgs.length == 2); 1488 assert(extract.functionArgs[0].contentRange == [43, 44]); 1489 assert(extract.functionArgs[1].contentRange == [46, 50]); 1490 1491 extract = dcdext.extractCallParameters(q{some_garbage(code); before(this); funcCall(4, ["a"], JSONValue(["b": JSONValue("c")]), recursive(func, call!s()), "texts )\"(too"}, 1492 129); 1493 assert(extract != CalltipsSupport.init); 1494 assert(!extract.hasTemplate); 1495 assert(extract.functionStart == 34); 1496 assert(extract.parentStart == 34); 1497 assert(extract.functionArgs.length == 5); 1498 assert(extract.functionArgs[0].contentRange == [43, 44]); 1499 assert(extract.functionArgs[1].contentRange == [46, 51]); 1500 assert(extract.functionArgs[2].contentRange == [53, 85]); 1501 assert(extract.functionArgs[3].contentRange == [87, 112]); 1502 assert(extract.functionArgs[4].contentRange == [114, 129]); 1503 } 1504 1505 unittest 1506 { 1507 scope backend = new WorkspaceD(); 1508 auto workspace = makeTemporaryTestingWorkspace; 1509 auto instance = backend.addInstance(workspace.directory); 1510 backend.register!DCDExtComponent; 1511 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1512 1513 auto info = dcdext.describeInterfaceRecursiveSync(SimpleClassTestCode, 23); 1514 assert(info.details.name == "FooBar"); 1515 assert(info.details.blockRange == [27, 554]); 1516 assert(info.details.referencedTypes.length == 2); 1517 assert(info.details.referencedTypes[0].name == "Something"); 1518 assert(info.details.referencedTypes[0].location == 455); 1519 assert(info.details.referencedTypes[1].name == "string"); 1520 assert(info.details.referencedTypes[1].location == 74); 1521 1522 assert(info.details.fields.length == 4); 1523 assert(info.details.fields[0].name == "i"); 1524 assert(info.details.fields[1].name == "s"); 1525 assert(info.details.fields[2].name == "l"); 1526 assert(info.details.fields[3].name == "x"); 1527 1528 assert(info.details.types.length == 1); 1529 assert(info.details.types[0].type == TypeDetails.Type.struct_); 1530 assert(info.details.types[0].name == ["FooBar", "Something"]); 1531 assert(info.details.types[0].nameLocation == 420); 1532 1533 assert(info.details.methods.length == 6); 1534 assert(info.details.methods[0].name == "foo"); 1535 assert( 1536 info.details.methods[0].signature 1537 == "private static const int foo() @nogc nothrow pure @system;"); 1538 assert(info.details.methods[0].returnType == "int"); 1539 assert(info.details.methods[0].isNothrowOrNogc); 1540 assert(info.details.methods[0].hasBody); 1541 assert(!info.details.methods[0].needsImplementation); 1542 assert(!info.details.methods[0].optionalImplementation); 1543 assert(info.details.methods[0].definitionRange == [222, 286]); 1544 assert(info.details.methods[0].blockRange == [286, 324]); 1545 1546 assert(info.details.methods[1].name == "bar1"); 1547 assert(info.details.methods[1].signature == "private static void bar1();"); 1548 assert(info.details.methods[1].returnType == "void"); 1549 assert(!info.details.methods[1].isNothrowOrNogc); 1550 assert(info.details.methods[1].hasBody); 1551 assert(!info.details.methods[1].needsImplementation); 1552 assert(!info.details.methods[1].optionalImplementation); 1553 assert(info.details.methods[1].definitionRange == [334, 346]); 1554 assert(info.details.methods[1].blockRange == [346, 348]); 1555 1556 assert(info.details.methods[2].name == "bar2"); 1557 assert(info.details.methods[2].signature == "private void bar2();"); 1558 assert(info.details.methods[2].returnType == "void"); 1559 assert(!info.details.methods[2].isNothrowOrNogc); 1560 assert(info.details.methods[2].hasBody); 1561 assert(!info.details.methods[2].needsImplementation); 1562 assert(!info.details.methods[2].optionalImplementation); 1563 assert(info.details.methods[2].definitionRange == [351, 363]); 1564 assert(info.details.methods[2].blockRange == [363, 365]); 1565 1566 assert(info.details.methods[3].name == "bar3"); 1567 assert(info.details.methods[3].signature == "private void bar3();"); 1568 assert(info.details.methods[3].returnType == "void"); 1569 assert(!info.details.methods[3].isNothrowOrNogc); 1570 assert(info.details.methods[3].hasBody); 1571 assert(!info.details.methods[3].needsImplementation); 1572 assert(!info.details.methods[3].optionalImplementation); 1573 assert(info.details.methods[3].definitionRange == [396, 408]); 1574 assert(info.details.methods[3].blockRange == [408, 410]); 1575 1576 assert(info.details.methods[4].name == "somefunc"); 1577 assert(info.details.methods[4].signature == "private FooBar.Something somefunc();"); 1578 assert(info.details.methods[4].returnType == "FooBar.Something"); 1579 assert(!info.details.methods[4].isNothrowOrNogc); 1580 assert(info.details.methods[4].hasBody); 1581 assert(!info.details.methods[4].needsImplementation); 1582 assert(!info.details.methods[4].optionalImplementation); 1583 assert(info.details.methods[4].definitionRange == [448, 476]); 1584 assert(info.details.methods[4].blockRange == [476, 502]); 1585 1586 // test normalization of types 1587 assert(info.details.methods[5].name == "somefunc2"); 1588 assert(info.details.methods[5].signature == "private FooBar.Something somefunc2();", 1589 info.details.methods[5].signature); 1590 assert(info.details.methods[5].returnType == "FooBar.Something"); 1591 assert(!info.details.methods[5].isNothrowOrNogc); 1592 assert(info.details.methods[5].hasBody); 1593 assert(!info.details.methods[5].needsImplementation); 1594 assert(!info.details.methods[5].optionalImplementation); 1595 assert(info.details.methods[5].definitionRange == [504, 526]); 1596 assert(info.details.methods[5].blockRange == [526, 552]); 1597 } 1598 1599 unittest 1600 { 1601 string testCode = q{package interface Foo0 1602 { 1603 string stringMethod(); 1604 Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c); 1605 void normalMethod(); 1606 int attributeSuffixMethod() nothrow @property @nogc; 1607 private 1608 { 1609 void middleprivate1(); 1610 void middleprivate2(); 1611 } 1612 extern(C) @property @nogc ref immutable int attributePrefixMethod() const; 1613 final void alreadyImplementedMethod() {} 1614 deprecated("foo") void deprecatedMethod() {} 1615 static void staticMethod() {} 1616 protected void protectedMethod(); 1617 private: 1618 void barfoo(); 1619 }}; 1620 1621 scope backend = new WorkspaceD(); 1622 auto workspace = makeTemporaryTestingWorkspace; 1623 auto instance = backend.addInstance(workspace.directory); 1624 backend.register!DCDExtComponent; 1625 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1626 1627 auto info = dcdext.describeInterfaceRecursiveSync(testCode, 20); 1628 assert(info.details.name == "Foo0"); 1629 assert(info.details.blockRange == [23, 523]); 1630 assert(info.details.referencedTypes.length == 3); 1631 assert(info.details.referencedTypes[0].name == "Array"); 1632 assert(info.details.referencedTypes[0].location == 70); 1633 assert(info.details.referencedTypes[1].name == "Tuple"); 1634 assert(info.details.referencedTypes[1].location == 50); 1635 assert(info.details.referencedTypes[2].name == "string"); 1636 assert(info.details.referencedTypes[2].location == 26); 1637 1638 assert(info.details.fields.length == 0); 1639 1640 assert(info.details.methods[0 .. 4].all!"!a.hasBody"); 1641 assert(info.details.methods[0 .. 4].all!"a.needsImplementation"); 1642 assert(info.details.methods.all!"!a.optionalImplementation"); 1643 1644 assert(info.details.methods.length == 12); 1645 assert(info.details.methods[0].name == "stringMethod"); 1646 assert(info.details.methods[0].signature == "string stringMethod();"); 1647 assert(info.details.methods[0].returnType == "string"); 1648 assert(!info.details.methods[0].isNothrowOrNogc); 1649 1650 assert(info.details.methods[1].name == "advancedMethod"); 1651 assert(info.details.methods[1].signature 1652 == "Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c);"); 1653 assert(info.details.methods[1].returnType == "Tuple!(int, string, Array!bool)[][]"); 1654 assert(!info.details.methods[1].isNothrowOrNogc); 1655 1656 assert(info.details.methods[2].name == "normalMethod"); 1657 assert(info.details.methods[2].signature == "void normalMethod();"); 1658 assert(info.details.methods[2].returnType == "void"); 1659 1660 assert(info.details.methods[3].name == "attributeSuffixMethod"); 1661 assert(info.details.methods[3].signature == "int attributeSuffixMethod() nothrow @property @nogc;"); 1662 assert(info.details.methods[3].returnType == "int"); 1663 assert(info.details.methods[3].isNothrowOrNogc); 1664 1665 assert(info.details.methods[4].name == "middleprivate1"); 1666 assert(info.details.methods[4].signature == "private void middleprivate1();"); 1667 assert(info.details.methods[4].returnType == "void"); 1668 1669 assert(info.details.methods[5].name == "middleprivate2"); 1670 1671 assert(info.details.methods[6].name == "attributePrefixMethod"); 1672 assert(info.details.methods[6].signature 1673 == "extern (C) @property @nogc ref immutable int attributePrefixMethod() const;"); 1674 assert(info.details.methods[6].returnType == "int"); 1675 assert(info.details.methods[6].isNothrowOrNogc); 1676 1677 assert(info.details.methods[7].name == "alreadyImplementedMethod"); 1678 assert(info.details.methods[7].signature == "void alreadyImplementedMethod();"); 1679 assert(info.details.methods[7].returnType == "void"); 1680 assert(!info.details.methods[7].needsImplementation); 1681 assert(info.details.methods[7].hasBody); 1682 1683 assert(info.details.methods[8].name == "deprecatedMethod"); 1684 assert(info.details.methods[8].signature == `deprecated("foo") void deprecatedMethod();`); 1685 assert(info.details.methods[8].returnType == "void"); 1686 assert(info.details.methods[8].needsImplementation); 1687 assert(info.details.methods[8].hasBody); 1688 1689 assert(info.details.methods[9].name == "staticMethod"); 1690 assert(info.details.methods[9].signature == `static void staticMethod();`); 1691 assert(info.details.methods[9].returnType == "void"); 1692 assert(!info.details.methods[9].needsImplementation); 1693 assert(info.details.methods[9].hasBody); 1694 1695 assert(info.details.methods[10].name == "protectedMethod"); 1696 assert(info.details.methods[10].signature == `protected void protectedMethod();`); 1697 assert(info.details.methods[10].returnType == "void"); 1698 assert(info.details.methods[10].needsImplementation); 1699 assert(!info.details.methods[10].hasBody); 1700 1701 assert(info.details.methods[11].name == "barfoo"); 1702 assert(info.details.methods[11].signature == `private void barfoo();`); 1703 assert(info.details.methods[11].returnType == "void"); 1704 assert(!info.details.methods[11].needsImplementation); 1705 assert(!info.details.methods[11].hasBody); 1706 }