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