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!`/***__CODED_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 /// Other tokens 1041 private enum dynamicTokens = [ 1042 "specialTokenSequence", "comment", "identifier", "scriptLine", "whitespace", 1043 "doubleLiteral", "floatLiteral", "idoubleLiteral", "ifloatLiteral", 1044 "intLiteral", "longLiteral", "realLiteral", "irealLiteral", "uintLiteral", 1045 "ulongLiteral", "characterLiteral", "dstringLiteral", "stringLiteral", 1046 "wstringLiteral" 1047 ]; 1048 1049 string tokenText(const Token token) 1050 { 1051 switch (token.type) 1052 { 1053 static foreach (T; dynamicTokens) 1054 { 1055 case tok!T: 1056 } 1057 return token.text; 1058 default: 1059 return str(token.type); 1060 } 1061 } 1062 1063 int[2] tokenRange(const Token token) 1064 { 1065 return [cast(int) token.index, cast(int)(token.index + token.tokenText.length)]; 1066 } 1067 1068 int tokenEnd(const Token token) 1069 { 1070 return cast(int)(token.index + token.tokenText.length); 1071 } 1072 1073 int tokenIndex(const(Token)[] tokens, ptrdiff_t i) 1074 { 1075 if (i > 0 && i == tokens.length) 1076 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].tokenText.length); 1077 return i >= 0 ? cast(int) tokens[i].index : 0; 1078 } 1079 1080 int tokenEndIndex(const(Token)[] tokens, ptrdiff_t i) 1081 { 1082 if (i > 0 && i == tokens.length) 1083 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length); 1084 return i >= 0 ? cast(int)(tokens[i].index + tokens[i].tokenText.length) : 0; 1085 } 1086 1087 ptrdiff_t findClosingParenForward(const(Token)[] tokens, ptrdiff_t open) 1088 in(tokens[open].type == tok!"(", 1089 "Calling findClosingParenForward must be done on a ( token and not on a " ~ str( 1090 tokens[open].type) ~ "token!") 1091 { 1092 if (open >= tokens.length || open < 0) 1093 return open; 1094 1095 open++; 1096 1097 int depth = 1; 1098 int subDepth = 0; 1099 while (open < tokens.length) 1100 { 1101 const c = tokens[open]; 1102 1103 if (c == tok!"(") 1104 depth++; 1105 else if (c == tok!"{") 1106 subDepth++; 1107 else if (c == tok!"}") 1108 { 1109 if (subDepth == 0) 1110 break; 1111 subDepth--; 1112 } 1113 else 1114 { 1115 if (c == tok!";" && subDepth == 0) 1116 break; 1117 1118 if (c == tok!")") 1119 depth--; 1120 1121 if (depth == 0) 1122 break; 1123 } 1124 1125 open++; 1126 } 1127 return open; 1128 } 1129 1130 CalltipsSupport.Argument[] splitArgs(const(Token)[] tokens) 1131 { 1132 auto ret = appender!(CalltipsSupport.Argument[]); 1133 size_t start = 0; 1134 size_t valueStart = 0; 1135 1136 int depth, subDepth; 1137 const targetDepth = tokens.length > 0 && tokens[0].type == tok!"(" ? 1 : 0; 1138 bool gotValue; 1139 1140 void putArg(size_t end) 1141 { 1142 if (start >= end || start >= tokens.length) 1143 return; 1144 1145 CalltipsSupport.Argument arg; 1146 1147 auto typename = tokens[start .. end]; 1148 arg.contentRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1149 if (typename.length == 1) 1150 { 1151 auto t = typename[0]; 1152 if (t.type == tok!"identifier" || t.type.isBasicType) 1153 arg = CalltipsSupport.Argument.templateType(t.tokenRange); 1154 else if (t.type == tok!"...") 1155 arg = CalltipsSupport.Argument.anyVariadic(t.tokenRange); 1156 else 1157 arg = CalltipsSupport.Argument.templateValue(t.tokenRange); 1158 } 1159 else 1160 { 1161 if (gotValue && valueStart > start && valueStart <= end) 1162 { 1163 typename = tokens[start .. valueStart]; 1164 auto val = tokens[valueStart .. end]; 1165 if (val.length) 1166 arg.valueRange = [cast(int) val[0].index, val[$ - 1].tokenEnd]; 1167 } 1168 1169 else if (typename.length == 1) 1170 { 1171 auto t = typename[0]; 1172 if (t.type == tok!"identifier" || t.type.isBasicType) 1173 arg.typeRange = arg.nameRange = t.tokenRange; 1174 else 1175 arg.typeRange = t.tokenRange; 1176 } 1177 else if (typename.length) 1178 { 1179 if (typename[$ - 1].type == tok!"identifier") 1180 { 1181 arg.nameRange = typename[$ - 1].tokenRange; 1182 typename = typename[0 .. $ - 1]; 1183 } 1184 else if (typename[$ - 1].type == tok!"...") 1185 { 1186 arg.variadic = true; 1187 if (typename.length > 1 && typename[$ - 2].type == tok!"identifier") 1188 { 1189 arg.nameRange = typename[$ - 2].tokenRange; 1190 typename = typename[0 .. $ - 2]; 1191 } 1192 else 1193 typename = typename[0 .. 0]; 1194 } 1195 1196 if (typename.length) 1197 arg.typeRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1198 } 1199 } 1200 1201 ret.put(arg); 1202 1203 gotValue = false; 1204 start = end + 1; 1205 } 1206 1207 foreach (i, token; tokens) 1208 { 1209 if (token.type == tok!"{") 1210 subDepth++; 1211 else if (token.type == tok!"}") 1212 { 1213 if (subDepth == 0) 1214 break; 1215 subDepth--; 1216 } 1217 else if (token.type == tok!"(" || token.type == tok!"[") 1218 depth++; 1219 else if (token.type == tok!")" || token.type == tok!"]") 1220 { 1221 if (depth <= targetDepth) 1222 break; 1223 depth--; 1224 } 1225 1226 if (depth == targetDepth) 1227 { 1228 if (token.type == tok!",") 1229 putArg(i); 1230 else if (token.type == tok!":" || token.type == tok!"=") 1231 { 1232 if (!gotValue) 1233 { 1234 valueStart = i + 1; 1235 gotValue = true; 1236 } 1237 } 1238 } 1239 } 1240 putArg(tokens.length); 1241 1242 return ret.data; 1243 } 1244 1245 auto indent(scope const(char)[] code, string indentation) 1246 { 1247 return code.lineSplitter!(KeepTerminator.yes) 1248 .map!(a => a.length ? indentation ~ a : a) 1249 .join; 1250 } 1251 1252 bool fieldNameMatches(string field, in char[] expected) 1253 { 1254 import std.uni : sicmp; 1255 1256 if (field.startsWith("_")) 1257 field = field[1 .. $]; 1258 else if (field.startsWith("m_")) 1259 field = field[2 .. $]; 1260 else if (field.length >= 2 && field[0] == 'm' && field[1].isUpper) 1261 field = field[1 .. $]; 1262 1263 return field.sicmp(expected) == 0; 1264 } 1265 1266 final class CodeBlockInfoFinder : ASTVisitor 1267 { 1268 this(int targetPosition) 1269 { 1270 this.targetPosition = targetPosition; 1271 } 1272 1273 override void visit(const ClassDeclaration dec) 1274 { 1275 visitContainer(dec.name, CodeBlockInfo.Type.class_, dec.structBody); 1276 } 1277 1278 override void visit(const InterfaceDeclaration dec) 1279 { 1280 visitContainer(dec.name, CodeBlockInfo.Type.interface_, dec.structBody); 1281 } 1282 1283 override void visit(const StructDeclaration dec) 1284 { 1285 visitContainer(dec.name, CodeBlockInfo.Type.struct_, dec.structBody); 1286 } 1287 1288 override void visit(const UnionDeclaration dec) 1289 { 1290 visitContainer(dec.name, CodeBlockInfo.Type.union_, dec.structBody); 1291 } 1292 1293 override void visit(const TemplateDeclaration dec) 1294 { 1295 if (cast(int) targetPosition >= cast(int) dec.name.index && targetPosition < dec.endLocation) 1296 { 1297 block = CodeBlockInfo.init; 1298 block.type = CodeBlockInfo.Type.template_; 1299 block.name = dec.name.text; 1300 block.outerRange = [ 1301 cast(uint) dec.name.index, cast(uint) dec.endLocation + 1 1302 ]; 1303 block.innerRange = [ 1304 cast(uint) dec.startLocation + 1, cast(uint) dec.endLocation 1305 ]; 1306 dec.accept(this); 1307 } 1308 } 1309 1310 private void visitContainer(const Token name, CodeBlockInfo.Type type, const StructBody structBody) 1311 { 1312 if (!structBody) 1313 return; 1314 if (cast(int) targetPosition >= cast(int) name.index && targetPosition < structBody.endLocation) 1315 { 1316 block = CodeBlockInfo.init; 1317 block.type = type; 1318 block.name = name.text; 1319 block.outerRange = [ 1320 cast(uint) name.index, cast(uint) structBody.endLocation + 1 1321 ]; 1322 block.innerRange = [ 1323 cast(uint) structBody.startLocation + 1, cast(uint) structBody.endLocation 1324 ]; 1325 structBody.accept(this); 1326 } 1327 } 1328 1329 alias visit = ASTVisitor.visit; 1330 1331 CodeBlockInfo block; 1332 int targetPosition; 1333 } 1334 1335 version (unittest) static immutable string SimpleClassTestCode = q{ 1336 module foo; 1337 1338 class FooBar 1339 { 1340 public: 1341 int i; // default instanced fields 1342 string s; 1343 long l; 1344 1345 public this() // public instanced ctor 1346 { 1347 i = 4; 1348 } 1349 1350 protected: 1351 int x; // protected instanced field 1352 1353 private: 1354 static const int foo() @nogc nothrow pure @system // private static methods 1355 { 1356 if (s == "a") 1357 { 1358 i = 5; 1359 } 1360 } 1361 1362 static void bar1() {} 1363 1364 void bar2() {} // private instanced methods 1365 void bar3() {} 1366 1367 struct Something { string bar; } 1368 1369 FooBar.Something somefunc() { return Something.init; } 1370 Something somefunc2() { return Something.init; } 1371 }}; 1372 1373 unittest 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 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 123) == CodeBlockInfo(CodeBlockInfo.Type.class_, 1382 "FooBar", [20, SimpleClassTestCode.length], [ 1383 28, SimpleClassTestCode.length - 1 1384 ])); 1385 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 19) == CodeBlockInfo.init); 1386 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 20) != CodeBlockInfo.init); 1387 1388 auto replacements = dcdext.insertCodeInContainer("void foo()\n{\n\twriteln();\n}", 1389 SimpleClassTestCode, 123); 1390 1391 // TODO: make insertCodeInContainer work properly? 1392 } 1393 1394 unittest 1395 { 1396 import std.conv; 1397 1398 scope backend = new WorkspaceD(); 1399 auto workspace = makeTemporaryTestingWorkspace; 1400 auto instance = backend.addInstance(workspace.directory); 1401 backend.register!DCDExtComponent; 1402 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1403 1404 auto extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg\n\nfoo(); int y;", 23); 1405 assert(!extract.hasTemplate); 1406 assert(extract.parentStart == 7); 1407 assert(extract.functionStart == 11); 1408 assert(extract.functionParensRange[0] == 14); 1409 assert(extract.functionParensRange[1] <= 31); 1410 assert(extract.functionArgs.length == 2); 1411 assert(extract.functionArgs[0].contentRange == [15, 16]); 1412 assert(extract.functionArgs[1].contentRange[0] == 18); 1413 assert(extract.functionArgs[1].contentRange[1] <= 31); 1414 1415 extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg)\n\nfoo(); int y;", 23); 1416 assert(!extract.hasTemplate); 1417 assert(extract.parentStart == 7); 1418 assert(extract.functionStart == 11); 1419 assert(extract.functionParensRange == [14, 24]); 1420 assert(extract.functionArgs.length == 2); 1421 assert(extract.functionArgs[0].contentRange == [15, 16]); 1422 assert(extract.functionArgs[1].contentRange == [18, 23]); 1423 1424 extract = dcdext.extractCallParameters("void foo()", 9, true); 1425 assert(extract != CalltipsSupport.init); 1426 extract = dcdext.extractCallParameters("void foo()", 10, true); 1427 assert(extract == CalltipsSupport.init); 1428 1429 // caused segfault once, doesn't return anything important 1430 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!")")`, 1431 140, true); 1432 assert(extract == CalltipsSupport.init); 1433 1434 extract = dcdext.extractCallParameters( 1435 `auto bar(int foo, Button, my.Callback cb, ..., int[] arr ...)`, 60, true); 1436 assert(extract != CalltipsSupport.init); 1437 assert(!extract.hasTemplate); 1438 assert(!extract.inTemplateParameters); 1439 assert(extract.activeParameter == 4); 1440 assert(extract.functionStart == 5); 1441 assert(extract.parentStart == 5); 1442 assert(extract.functionParensRange == [8, 61]); 1443 assert(extract.functionArgs.length == 5); 1444 assert(extract.functionArgs[0].contentRange == [9, 16]); 1445 assert(extract.functionArgs[0].typeRange == [9, 12]); 1446 assert(extract.functionArgs[0].nameRange == [13, 16]); 1447 assert(extract.functionArgs[1].contentRange == [18, 24]); 1448 assert(extract.functionArgs[1].typeRange == [18, 24]); 1449 assert(extract.functionArgs[1].nameRange == [18, 24]); 1450 assert(extract.functionArgs[2].contentRange == [26, 40]); 1451 assert(extract.functionArgs[2].typeRange == [26, 37]); 1452 assert(extract.functionArgs[2].nameRange == [38, 40]); 1453 assert(extract.functionArgs[3].contentRange == [42, 45]); 1454 assert(extract.functionArgs[3].variadic); 1455 assert(extract.functionArgs[4].contentRange == [47, 60]); 1456 assert(extract.functionArgs[4].typeRange == [47, 52]); 1457 assert(extract.functionArgs[4].nameRange == [53, 56]); 1458 assert(extract.functionArgs[4].variadic); 1459 1460 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!")")}, 1461 150, true); 1462 assert(extract != CalltipsSupport.init); 1463 assert(extract.hasTemplate); 1464 assert(extract.templateArgumentsRange == [26, 38]); 1465 assert(extract.templateArgs.length == 2); 1466 assert(extract.templateArgs[0].contentRange == [27, 28]); 1467 assert(extract.templateArgs[0].nameRange == [27, 28]); 1468 assert(extract.templateArgs[1].contentRange == [30, 37]); 1469 assert(extract.templateArgs[1].nameRange == [30, 34]); 1470 assert(extract.functionStart == 23); 1471 assert(extract.parentStart == 23); 1472 assert(extract.functionParensRange == [38, 151]); 1473 assert(extract.functionArgs.length == 7); 1474 assert(extract.functionArgs[0].contentRange == [39, 42]); 1475 assert(extract.functionArgs[0].typeRange == [39, 40]); 1476 assert(extract.functionArgs[0].nameRange == [41, 42]); 1477 assert(extract.functionArgs[1].contentRange == [44, 47]); 1478 assert(extract.functionArgs[1].typeRange == [44, 45]); 1479 assert(extract.functionArgs[1].nameRange == [46, 47]); 1480 assert(extract.functionArgs[2].contentRange == [49, 67]); 1481 assert(extract.functionArgs[2].typeRange == [49, 63]); 1482 assert(extract.functionArgs[2].nameRange == [64, 67]); 1483 assert(extract.functionArgs[3].contentRange == [69, 85]); 1484 assert(extract.functionArgs[3].typeRange == [69, 78]); 1485 assert(extract.functionArgs[3].nameRange == [79, 85]); 1486 assert(extract.functionArgs[4].contentRange == [87, 122]); 1487 assert(extract.functionArgs[4].typeRange == [87, 115]); 1488 assert(extract.functionArgs[4].nameRange == [116, 122]); 1489 assert(extract.functionArgs[5].contentRange == [124, 139]); 1490 assert(extract.functionArgs[5].typeRange == [124, 133]); 1491 assert(extract.functionArgs[5].nameRange == [134, 139]); 1492 assert(extract.functionArgs[6].contentRange == [141, 150]); 1493 assert(extract.functionArgs[6].typeRange == [141, 150]); 1494 1495 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4`, 44); 1496 assert(extract != CalltipsSupport.init); 1497 assert(!extract.hasTemplate); 1498 assert(extract.activeParameter == 0); 1499 assert(extract.functionStart == 34); 1500 assert(extract.parentStart == 34); 1501 assert(extract.functionArgs.length == 1); 1502 assert(extract.functionArgs[0].contentRange == [43, 44]); 1503 1504 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4, f(4)`, 50); 1505 assert(extract != CalltipsSupport.init); 1506 assert(!extract.hasTemplate); 1507 assert(extract.activeParameter == 1); 1508 assert(extract.functionStart == 34); 1509 assert(extract.parentStart == 34); 1510 assert(extract.functionArgs.length == 2); 1511 assert(extract.functionArgs[0].contentRange == [43, 44]); 1512 assert(extract.functionArgs[1].contentRange == [46, 50]); 1513 1514 extract = dcdext.extractCallParameters(q{some_garbage(code); before(this); funcCall(4, ["a"], JSONValue(["b": JSONValue("c")]), recursive(func, call!s()), "texts )\"(too"}, 1515 129); 1516 assert(extract != CalltipsSupport.init); 1517 assert(!extract.hasTemplate); 1518 assert(extract.functionStart == 34); 1519 assert(extract.parentStart == 34); 1520 assert(extract.functionArgs.length == 5); 1521 assert(extract.functionArgs[0].contentRange == [43, 44]); 1522 assert(extract.functionArgs[1].contentRange == [46, 51]); 1523 assert(extract.functionArgs[2].contentRange == [53, 85]); 1524 assert(extract.functionArgs[3].contentRange == [87, 112]); 1525 assert(extract.functionArgs[4].contentRange == [114, 129]); 1526 } 1527 1528 unittest 1529 { 1530 scope backend = new WorkspaceD(); 1531 auto workspace = makeTemporaryTestingWorkspace; 1532 auto instance = backend.addInstance(workspace.directory); 1533 backend.register!DCDExtComponent; 1534 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1535 1536 auto info = dcdext.describeInterfaceRecursiveSync(SimpleClassTestCode, 23); 1537 assert(info.details.name == "FooBar"); 1538 assert(info.details.blockRange == [27, 554]); 1539 assert(info.details.referencedTypes.length == 2); 1540 assert(info.details.referencedTypes[0].name == "Something"); 1541 assert(info.details.referencedTypes[0].location == 455); 1542 assert(info.details.referencedTypes[1].name == "string"); 1543 assert(info.details.referencedTypes[1].location == 74); 1544 1545 assert(info.details.fields.length == 4); 1546 assert(info.details.fields[0].name == "i"); 1547 assert(info.details.fields[1].name == "s"); 1548 assert(info.details.fields[2].name == "l"); 1549 assert(info.details.fields[3].name == "x"); 1550 1551 assert(info.details.types.length == 1); 1552 assert(info.details.types[0].type == TypeDetails.Type.struct_); 1553 assert(info.details.types[0].name == ["FooBar", "Something"]); 1554 assert(info.details.types[0].nameLocation == 420); 1555 1556 assert(info.details.methods.length == 6); 1557 assert(info.details.methods[0].name == "foo"); 1558 assert( 1559 info.details.methods[0].signature 1560 == "private static const int foo() @nogc nothrow pure @system;"); 1561 assert(info.details.methods[0].returnType == "int"); 1562 assert(info.details.methods[0].isNothrowOrNogc); 1563 assert(info.details.methods[0].hasBody); 1564 assert(!info.details.methods[0].needsImplementation); 1565 assert(!info.details.methods[0].optionalImplementation); 1566 assert(info.details.methods[0].definitionRange == [222, 286]); 1567 assert(info.details.methods[0].blockRange == [286, 324]); 1568 1569 assert(info.details.methods[1].name == "bar1"); 1570 assert(info.details.methods[1].signature == "private static void bar1();"); 1571 assert(info.details.methods[1].returnType == "void"); 1572 assert(!info.details.methods[1].isNothrowOrNogc); 1573 assert(info.details.methods[1].hasBody); 1574 assert(!info.details.methods[1].needsImplementation); 1575 assert(!info.details.methods[1].optionalImplementation); 1576 assert(info.details.methods[1].definitionRange == [334, 346]); 1577 assert(info.details.methods[1].blockRange == [346, 348]); 1578 1579 assert(info.details.methods[2].name == "bar2"); 1580 assert(info.details.methods[2].signature == "private void bar2();"); 1581 assert(info.details.methods[2].returnType == "void"); 1582 assert(!info.details.methods[2].isNothrowOrNogc); 1583 assert(info.details.methods[2].hasBody); 1584 assert(!info.details.methods[2].needsImplementation); 1585 assert(!info.details.methods[2].optionalImplementation); 1586 assert(info.details.methods[2].definitionRange == [351, 363]); 1587 assert(info.details.methods[2].blockRange == [363, 365]); 1588 1589 assert(info.details.methods[3].name == "bar3"); 1590 assert(info.details.methods[3].signature == "private void bar3();"); 1591 assert(info.details.methods[3].returnType == "void"); 1592 assert(!info.details.methods[3].isNothrowOrNogc); 1593 assert(info.details.methods[3].hasBody); 1594 assert(!info.details.methods[3].needsImplementation); 1595 assert(!info.details.methods[3].optionalImplementation); 1596 assert(info.details.methods[3].definitionRange == [396, 408]); 1597 assert(info.details.methods[3].blockRange == [408, 410]); 1598 1599 assert(info.details.methods[4].name == "somefunc"); 1600 assert(info.details.methods[4].signature == "private FooBar.Something somefunc();"); 1601 assert(info.details.methods[4].returnType == "FooBar.Something"); 1602 assert(!info.details.methods[4].isNothrowOrNogc); 1603 assert(info.details.methods[4].hasBody); 1604 assert(!info.details.methods[4].needsImplementation); 1605 assert(!info.details.methods[4].optionalImplementation); 1606 assert(info.details.methods[4].definitionRange == [448, 476]); 1607 assert(info.details.methods[4].blockRange == [476, 502]); 1608 1609 // test normalization of types 1610 assert(info.details.methods[5].name == "somefunc2"); 1611 assert(info.details.methods[5].signature == "private FooBar.Something somefunc2();", 1612 info.details.methods[5].signature); 1613 assert(info.details.methods[5].returnType == "FooBar.Something"); 1614 assert(!info.details.methods[5].isNothrowOrNogc); 1615 assert(info.details.methods[5].hasBody); 1616 assert(!info.details.methods[5].needsImplementation); 1617 assert(!info.details.methods[5].optionalImplementation); 1618 assert(info.details.methods[5].definitionRange == [504, 526]); 1619 assert(info.details.methods[5].blockRange == [526, 552]); 1620 } 1621 1622 unittest 1623 { 1624 string testCode = q{package interface Foo0 1625 { 1626 string stringMethod(); 1627 Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c); 1628 void normalMethod(); 1629 int attributeSuffixMethod() nothrow @property @nogc; 1630 private 1631 { 1632 void middleprivate1(); 1633 void middleprivate2(); 1634 } 1635 extern(C) @property @nogc ref immutable int attributePrefixMethod() const; 1636 final void alreadyImplementedMethod() {} 1637 deprecated("foo") void deprecatedMethod() {} 1638 static void staticMethod() {} 1639 protected void protectedMethod(); 1640 private: 1641 void barfoo(); 1642 }}; 1643 1644 scope backend = new WorkspaceD(); 1645 auto workspace = makeTemporaryTestingWorkspace; 1646 auto instance = backend.addInstance(workspace.directory); 1647 backend.register!DCDExtComponent; 1648 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1649 1650 auto info = dcdext.describeInterfaceRecursiveSync(testCode, 20); 1651 assert(info.details.name == "Foo0"); 1652 assert(info.details.blockRange == [23, 523]); 1653 assert(info.details.referencedTypes.length == 3); 1654 assert(info.details.referencedTypes[0].name == "Array"); 1655 assert(info.details.referencedTypes[0].location == 70); 1656 assert(info.details.referencedTypes[1].name == "Tuple"); 1657 assert(info.details.referencedTypes[1].location == 50); 1658 assert(info.details.referencedTypes[2].name == "string"); 1659 assert(info.details.referencedTypes[2].location == 26); 1660 1661 assert(info.details.fields.length == 0); 1662 1663 assert(info.details.methods[0 .. 4].all!"!a.hasBody"); 1664 assert(info.details.methods[0 .. 4].all!"a.needsImplementation"); 1665 assert(info.details.methods.all!"!a.optionalImplementation"); 1666 1667 assert(info.details.methods.length == 12); 1668 assert(info.details.methods[0].name == "stringMethod"); 1669 assert(info.details.methods[0].signature == "string stringMethod();"); 1670 assert(info.details.methods[0].returnType == "string"); 1671 assert(!info.details.methods[0].isNothrowOrNogc); 1672 1673 assert(info.details.methods[1].name == "advancedMethod"); 1674 assert(info.details.methods[1].signature 1675 == "Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c);"); 1676 assert(info.details.methods[1].returnType == "Tuple!(int, string, Array!bool)[][]"); 1677 assert(!info.details.methods[1].isNothrowOrNogc); 1678 1679 assert(info.details.methods[2].name == "normalMethod"); 1680 assert(info.details.methods[2].signature == "void normalMethod();"); 1681 assert(info.details.methods[2].returnType == "void"); 1682 1683 assert(info.details.methods[3].name == "attributeSuffixMethod"); 1684 assert(info.details.methods[3].signature == "int attributeSuffixMethod() nothrow @property @nogc;"); 1685 assert(info.details.methods[3].returnType == "int"); 1686 assert(info.details.methods[3].isNothrowOrNogc); 1687 1688 assert(info.details.methods[4].name == "middleprivate1"); 1689 assert(info.details.methods[4].signature == "private void middleprivate1();"); 1690 assert(info.details.methods[4].returnType == "void"); 1691 1692 assert(info.details.methods[5].name == "middleprivate2"); 1693 1694 assert(info.details.methods[6].name == "attributePrefixMethod"); 1695 assert(info.details.methods[6].signature 1696 == "extern (C) @property @nogc ref immutable int attributePrefixMethod() const;"); 1697 assert(info.details.methods[6].returnType == "int"); 1698 assert(info.details.methods[6].isNothrowOrNogc); 1699 1700 assert(info.details.methods[7].name == "alreadyImplementedMethod"); 1701 assert(info.details.methods[7].signature == "void alreadyImplementedMethod();"); 1702 assert(info.details.methods[7].returnType == "void"); 1703 assert(!info.details.methods[7].needsImplementation); 1704 assert(info.details.methods[7].hasBody); 1705 1706 assert(info.details.methods[8].name == "deprecatedMethod"); 1707 assert(info.details.methods[8].signature == `deprecated("foo") void deprecatedMethod();`); 1708 assert(info.details.methods[8].returnType == "void"); 1709 assert(info.details.methods[8].needsImplementation); 1710 assert(info.details.methods[8].hasBody); 1711 1712 assert(info.details.methods[9].name == "staticMethod"); 1713 assert(info.details.methods[9].signature == `static void staticMethod();`); 1714 assert(info.details.methods[9].returnType == "void"); 1715 assert(!info.details.methods[9].needsImplementation); 1716 assert(info.details.methods[9].hasBody); 1717 1718 assert(info.details.methods[10].name == "protectedMethod"); 1719 assert(info.details.methods[10].signature == `protected void protectedMethod();`); 1720 assert(info.details.methods[10].returnType == "void"); 1721 assert(info.details.methods[10].needsImplementation); 1722 assert(!info.details.methods[10].hasBody); 1723 1724 assert(info.details.methods[11].name == "barfoo"); 1725 assert(info.details.methods[11].signature == `private void barfoo();`); 1726 assert(info.details.methods[11].returnType == "void"); 1727 assert(!info.details.methods[11].needsImplementation); 1728 assert(!info.details.methods[11].hasBody); 1729 }