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.meta; 18 import std.range; 19 import std.string; 20 21 import workspaced.api; 22 import workspaced.com.dcd; 23 import workspaced.com.dfmt; 24 import workspaced.dparseext; 25 26 import workspaced.visitors.classifier; 27 import workspaced.visitors.methodfinder; 28 29 public import workspaced.visitors.methodfinder : InterfaceDetails, FieldDetails, 30 MethodDetails, ArgumentInfo; 31 32 @component("dcdext") 33 class DCDExtComponent : ComponentWrapper 34 { 35 mixin DefaultComponentWrapper; 36 37 static immutable CodeRegionProtection[] mixableProtection = [ 38 CodeRegionProtection.public_ | CodeRegionProtection.default_, 39 CodeRegionProtection.package_, CodeRegionProtection.packageIdentifier, 40 CodeRegionProtection.protected_, CodeRegionProtection.private_ 41 ]; 42 43 /// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}` 44 void load() 45 { 46 if (!refInstance) 47 return; 48 49 config.stringBehavior = StringBehavior.source; 50 } 51 52 /// Extracts calltips help information at a given position. 53 /// The position must be within the arguments of the function and not 54 /// outside the parentheses or inside some child call. 55 /// 56 /// When generating the call parameters for a function definition, the position must be inside the normal parameters, 57 /// otherwise the template arguments will be put as normal arguments. 58 /// 59 /// Returns: the position of significant locations for parameter extraction. 60 /// Params: 61 /// code = code to analyze 62 /// position = byte offset where to check for function arguments 63 /// definition = true if this hints is a function definition (templates don't have an exclamation point '!') 64 CalltipsSupport extractCallParameters(scope const(char)[] code, int position, 65 bool definition = false) 66 { 67 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 68 if (!tokens.length) 69 return CalltipsSupport.init; 70 // TODO: can probably use tokenIndexAtByteIndex here 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 typeof(return); 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 Related[] highlightRelated(scope const(char)[] code, int position) 849 { 850 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 851 if (!tokens.length) 852 return null; 853 auto token = tokens.tokenIndexAtByteIndex(position); 854 if (token >= tokens.length || !tokens[token].isLikeIdentifier) 855 return null; 856 857 Related[] ret; 858 859 switch (tokens[token].type) 860 { 861 case tok!"static": 862 if (token + 1 < tokens.length) 863 { 864 if (tokens[token + 1].type == tok!"if") 865 { 866 token++; 867 goto case tok!"if"; 868 } 869 else if (tokens[token + 1].type == tok!"foreach" || tokens[token + 1].type == tok!"foreach_reverse") 870 { 871 token++; 872 goto case tok!"for"; 873 } 874 } 875 goto default; 876 case tok!"if": 877 case tok!"else": 878 // if lister 879 auto finder = new IfFinder(); 880 finder.target = tokens[token].index; 881 RollbackAllocator rba; 882 auto parsed = parseModule(tokens, "stdin", &rba); 883 finder.visit(parsed); 884 foreach (ifToken; finder.foundIf) 885 ret ~= Related(Related.Type.controlFlow, [ifToken.index, ifToken.index + ifToken.tokenText.length]); 886 break; 887 case tok!"for": 888 case tok!"foreach": 889 case tok!"foreach_reverse": 890 case tok!"while": 891 case tok!"do": 892 case tok!"break": 893 case tok!"continue": 894 // loop and switch matcher 895 // special case for switch 896 auto finder = new BreakFinder(); 897 finder.target = tokens[token].index; 898 finder.isBreak = tokens[token].type == tok!"break"; 899 finder.isLoop = !(tokens[token].type == tok!"break" || tokens[token].type == tok!"continue"); 900 if (token + 1 < tokens.length && tokens[token + 1].type == tok!"identifier") 901 finder.label = tokens[token + 1].text; 902 RollbackAllocator rba; 903 auto parsed = parseModule(tokens, "stdin", &rba); 904 finder.visit(parsed); 905 906 if (finder.isLoop && finder.foundBlock.length) 907 { 908 auto retFinder = new ReverseReturnFinder(); 909 retFinder.target = finder.target; 910 retFinder.visit(parsed); 911 finder.foundBlock ~= retFinder.returns; 912 finder.foundBlock.sort!"a.index < b.index"; 913 } 914 915 foreach (blockToken; finder.foundBlock) 916 ret ~= Related(Related.Type.controlFlow, [blockToken.index, blockToken.index + blockToken.tokenText.length]); 917 break; 918 case tok!"switch": 919 case tok!"case": 920 case tok!"default": 921 // switch/case lister 922 auto finder = new SwitchFinder(); 923 finder.target = tokens[token].index; 924 RollbackAllocator rba; 925 auto parsed = parseModule(tokens, "stdin", &rba); 926 finder.visit(parsed); 927 foreach (switchToken; finder.foundSwitch) 928 ret ~= Related(Related.Type.controlFlow, [switchToken.index, switchToken.index + switchToken.tokenText.length]); 929 break; 930 case tok!"return": 931 // return effect lister 932 auto finder = new ReturnFinder(); 933 finder.target = tokens[token].index; 934 RollbackAllocator rba; 935 auto parsed = parseModule(tokens, "stdin", &rba); 936 finder.visit(parsed); 937 foreach (switchToken; finder.related) 938 ret ~= Related(Related.Type.controlFlow, [switchToken.index, switchToken.index + switchToken.tokenText.length]); 939 break; 940 default: 941 // exact token / string matcher 942 auto currentText = tokens[token].tokenText; 943 foreach (i, tok; tokens) 944 { 945 if (tok.type == tokens[token].type && tok.text == tokens[token].text) 946 ret ~= Related(Related.Type.exactToken, [tok.index, tok.index + currentText.length]); 947 else if (tok.type.isSomeString && tok.evaluateExpressionString == currentText) 948 ret ~= Related(Related.Type.exactString, [tok.index, tok.index + tok.text.length]); 949 } 950 break; 951 } 952 953 return ret; 954 } 955 956 /// Formats DCD definitions (symbol declarations) in a readable format. 957 /// For functions this formats each argument in a separate line. 958 /// For other symbols the definition is returned as-is. 959 string formatDefinitionBlock(string definition) 960 { 961 // DCD definition help contains calltips for functions, which always end 962 // with ) 963 if (!definition.endsWith(")")) 964 return definition; 965 966 auto tokens = getTokensForParser(cast(const(ubyte)[]) definition ~ ';', 967 config, &workspaced.stringCache); 968 if (!tokens.length) 969 return definition; 970 971 RollbackAllocator rba; 972 auto parser = new Parser(); 973 parser.fileName = "stdin"; 974 parser.tokens = tokens; 975 parser.messageFunction = null; 976 parser.messageDelegate = null; 977 parser.allocator = &rba; 978 const Declaration decl = parser.parseDeclaration( 979 false, // strict 980 true // must be declaration (for constructor) 981 ); 982 if (!decl) 983 return definition; 984 985 const FunctionDeclaration funcdecl = decl.functionDeclaration; 986 const Constructor ctor = decl.constructor; 987 if (!funcdecl && !ctor) 988 return definition; 989 990 auto ret = appender!string(); 991 ret.reserve(definition.length); 992 993 if (funcdecl) 994 ret.put(definition[0 .. funcdecl.name.index + funcdecl.name.text.length]); 995 else if (ctor) 996 ret.put("this"); 997 998 const templateParameters = funcdecl ? funcdecl.templateParameters : ctor.templateParameters; 999 if (templateParameters && templateParameters.templateParameterList) 1000 { 1001 const params = templateParameters.templateParameterList.items; 1002 ret.put("(\n"); 1003 foreach (i, param; params) 1004 { 1005 assert(param.tokens.length, "no tokens for template parameter?!"); 1006 const start = param.tokens[0].index; 1007 const end = param.tokens[$ - 1].index + tokenText(param.tokens[$ - 1]).length; 1008 const hasNext = i + 1 < params.length; 1009 ret.put("\t"); 1010 ret.put(definition[start .. end]); 1011 if (hasNext) 1012 ret.put(","); 1013 ret.put("\n"); 1014 } 1015 ret.put(")"); 1016 } 1017 1018 const parameters = funcdecl ? funcdecl.parameters : ctor.parameters; 1019 if (parameters && (parameters.parameters.length || parameters.hasVarargs)) 1020 { 1021 const params = parameters.parameters; 1022 ret.put("(\n"); 1023 foreach (i, param; params) 1024 { 1025 assert(param.tokens.length, "no tokens for parameter?!"); 1026 const start = param.tokens[0].index; 1027 const end = param.tokens[$ - 1].index + tokenText(param.tokens[$ - 1]).length; 1028 const hasNext = parameters.hasVarargs || i + 1 < params.length; 1029 ret.put("\t"); 1030 ret.put(definition[start .. end]); 1031 if (hasNext) 1032 ret.put(","); 1033 ret.put("\n"); 1034 } 1035 if (parameters.hasVarargs) 1036 ret.put("\t...\n"); 1037 ret.put(")"); 1038 } 1039 else 1040 { 1041 ret.put("()"); 1042 } 1043 1044 return ret.data; 1045 } 1046 1047 private: 1048 LexerConfig config; 1049 } 1050 1051 /// 1052 enum CodeRegionType : int 1053 { 1054 /// null region (unset) 1055 init, 1056 /// Imports inside the block 1057 imports = 1 << 0, 1058 /// Aliases `alias foo this;`, `alias Type = Other;` 1059 aliases = 1 << 1, 1060 /// Nested classes/structs/unions/etc. 1061 types = 1 << 2, 1062 /// Raw variables `Type name;` 1063 fields = 1 << 3, 1064 /// Normal constructors `this(Args args)` 1065 ctor = 1 << 4, 1066 /// Copy constructors `this(this)` 1067 copyctor = 1 << 5, 1068 /// Destructors `~this()` 1069 dtor = 1 << 6, 1070 /// Properties (functions annotated with `@property`) 1071 properties = 1 << 7, 1072 /// Regular functions 1073 methods = 1 << 8, 1074 } 1075 1076 /// 1077 enum CodeRegionProtection : int 1078 { 1079 /// null protection (unset) 1080 init, 1081 /// default (unmarked) protection 1082 default_ = 1 << 0, 1083 /// public protection 1084 public_ = 1 << 1, 1085 /// package (automatic) protection 1086 package_ = 1 << 2, 1087 /// package (manual package name) protection 1088 packageIdentifier = 1 << 3, 1089 /// protected protection 1090 protected_ = 1 << 4, 1091 /// private protection 1092 private_ = 1 << 5, 1093 } 1094 1095 /// 1096 enum CodeRegionStatic : int 1097 { 1098 /// null static (unset) 1099 init, 1100 /// non-static code 1101 instanced = 1 << 0, 1102 /// static code 1103 static_ = 1 << 1, 1104 } 1105 1106 /// Represents a class/interface/struct/union/template with body. 1107 struct CodeBlockInfo 1108 { 1109 /// 1110 enum Type : int 1111 { 1112 // keep the underlines in these values for range checking properly 1113 1114 /// 1115 class_, 1116 /// 1117 interface_, 1118 /// 1119 struct_, 1120 /// 1121 union_, 1122 /// 1123 template_, 1124 } 1125 1126 static immutable string[] typePrefixes = [ 1127 "class ", "interface ", "struct ", "union ", "template " 1128 ]; 1129 1130 /// 1131 Type type; 1132 /// 1133 string name; 1134 /// Outer range inside the code spanning curly braces and name but not type keyword. 1135 uint[2] outerRange; 1136 /// Inner range of body of the block touching, but not spanning curly braces. 1137 uint[2] innerRange; 1138 1139 string prefix() @property 1140 { 1141 return typePrefixes[cast(int) type]; 1142 } 1143 } 1144 1145 /// 1146 struct CalltipsSupport 1147 { 1148 /// 1149 struct Argument 1150 { 1151 /// 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. 1152 int[2] contentRange; 1153 /// Range of just the type, or for templates also `alias` 1154 int[2] typeRange; 1155 /// Range of just the name 1156 int[2] nameRange; 1157 /// Range of just the default value 1158 int[2] valueRange; 1159 /// True if the type declaration is variadic (using ...), or without typeRange a completely variadic argument 1160 bool variadic; 1161 1162 /// Creates Argument(range, range, range, 0) 1163 static Argument templateType(int[2] range) 1164 { 1165 return Argument(range, range, range); 1166 } 1167 1168 /// Creates Argument(range, 0, range, range) 1169 static Argument templateValue(int[2] range) 1170 { 1171 return Argument(range, typeof(range).init, range, range); 1172 } 1173 1174 /// Creates Argument(range, 0, 0, 0, true) 1175 static Argument anyVariadic(int[2] range) 1176 { 1177 return Argument(range, typeof(range).init, typeof(range).init, typeof(range).init, true); 1178 } 1179 } 1180 1181 bool hasTemplate() @property 1182 { 1183 return hasTemplateParens || templateArgumentsRange != typeof(templateArgumentsRange).init; 1184 } 1185 1186 /// Range starting before exclamation point until after closing bracket or before function opening bracket. 1187 int[2] templateArgumentsRange; 1188 /// 1189 bool hasTemplateParens; 1190 /// 1191 Argument[] templateArgs; 1192 /// Range starting before opening parentheses until after closing parentheses. 1193 int[2] functionParensRange; 1194 /// 1195 Argument[] functionArgs; 1196 /// True if the function is UFCS or a member function of some object or namespace. 1197 /// False if this is a global function call. 1198 bool hasParent; 1199 /// Start of the function itself. 1200 int functionStart; 1201 /// Start of the whole call going up all call parents. (`foo.bar.function` having `foo.bar` as parents) 1202 int parentStart; 1203 /// True if cursor is in template parameters 1204 bool inTemplateParameters; 1205 /// Number of the active parameter (where the cursor is) or -1 if in none 1206 int activeParameter = -1; 1207 } 1208 1209 /// Represents one method automatically implemented off a base interface. 1210 struct ImplementedMethod 1211 { 1212 /// Contains the interface or class name from where this method is implemented. 1213 string baseClass; 1214 /// The name of the function being implemented. 1215 string name; 1216 /// True if an automatic implementation calling the base class has been made. 1217 bool callsSuper; 1218 /// True if a default implementation that should definitely be changed (assert or for nogc/nothrow simple init return) has been implemented. 1219 bool debugImpl; 1220 /// True if the method has been detected as property and implemented as such. 1221 bool autoProperty; 1222 /// 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. 1223 bool getter, setter; 1224 /// Actual code to insert for this class without class indentation but optionally already formatted. 1225 string code; 1226 } 1227 1228 /// Contains details about an interface or class and all extended or implemented interfaces/classes recursively. 1229 struct InterfaceTree 1230 { 1231 /// Details of the template in question. 1232 InterfaceDetails details; 1233 /// All inherited classes in lexical order. 1234 InterfaceTree[] inherits; 1235 1236 const(FieldDetails)[] availableVariables(bool onlyPublic = false) const 1237 { 1238 if (!inherits.length && !onlyPublic) 1239 return details.fields; 1240 1241 // start with private, add all the public ones later in traverseTree 1242 auto ret = appender!(typeof(return)); 1243 if (onlyPublic) 1244 ret.put(details.fields.filter!(a => !a.isPrivate)); 1245 else 1246 ret.put(details.fields); 1247 1248 foreach (sub; inherits) 1249 ret.put(sub.availableVariables(true)); 1250 1251 return ret.data; 1252 } 1253 } 1254 1255 /// Represents one selection for things related to the queried cursor position. 1256 struct Related 1257 { 1258 /// 1259 enum Type 1260 { 1261 /// token is the same as the selected token (except for non-text tokens) 1262 exactToken, 1263 /// string content is exactly equal to identifier text 1264 exactString, 1265 /// token is related to control flow: 1266 /// - all if/else keywords when checking any of them 1267 /// - loop/switch keyword when checking a break/continue 1268 controlFlow 1269 } 1270 1271 /// The type of the related selection. 1272 Type type; 1273 /// Byte range [from-inclusive, to-exclusive] of the related selection. 1274 size_t[2] range; 1275 } 1276 1277 private: 1278 1279 bool isCalltipable(IdType type) 1280 { 1281 return type == tok!"identifier" || type == tok!"assert" || type == tok!"import" 1282 || type == tok!"mixin" || type == tok!"super" || type == tok!"this" || type == tok!"__traits"; 1283 } 1284 1285 int[2] tokenRange(const Token token) 1286 { 1287 return [cast(int) token.index, cast(int)(token.index + token.tokenText.length)]; 1288 } 1289 1290 int tokenEnd(const Token token) 1291 { 1292 return cast(int)(token.index + token.tokenText.length); 1293 } 1294 1295 int tokenIndex(const(Token)[] tokens, ptrdiff_t i) 1296 { 1297 if (i > 0 && i == tokens.length) 1298 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].tokenText.length); 1299 return i >= 0 ? cast(int) tokens[i].index : 0; 1300 } 1301 1302 int tokenEndIndex(const(Token)[] tokens, ptrdiff_t i) 1303 { 1304 if (i > 0 && i == tokens.length) 1305 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length); 1306 return i >= 0 ? cast(int)(tokens[i].index + tokens[i].tokenText.length) : 0; 1307 } 1308 1309 /// Returns the index of the closing parentheses in tokens starting at the opening parentheses which is must be at tokens[open]. 1310 ptrdiff_t findClosingParenForward(const(Token)[] tokens, ptrdiff_t open, string what = null) 1311 in(tokens[open].type == tok!"(", 1312 "Calling findClosingParenForward must be done on a ( token and not on a " ~ str( 1313 tokens[open].type) ~ " token! " ~ what) 1314 { 1315 if (open >= tokens.length || open < 0) 1316 return open; 1317 1318 open++; 1319 1320 int depth = 1; 1321 int subDepth = 0; 1322 while (open < tokens.length) 1323 { 1324 const c = tokens[open]; 1325 1326 if (c == tok!"(") 1327 depth++; 1328 else if (c == tok!"{") 1329 subDepth++; 1330 else if (c == tok!"}") 1331 { 1332 if (subDepth == 0) 1333 break; 1334 subDepth--; 1335 } 1336 else 1337 { 1338 if (c == tok!";" && subDepth == 0) 1339 break; 1340 1341 if (c == tok!")") 1342 depth--; 1343 1344 if (depth == 0) 1345 break; 1346 } 1347 1348 open++; 1349 } 1350 return open; 1351 } 1352 1353 CalltipsSupport.Argument[] splitArgs(const(Token)[] tokens) 1354 { 1355 auto ret = appender!(CalltipsSupport.Argument[]); 1356 size_t start = 0; 1357 size_t valueStart = 0; 1358 1359 int depth, subDepth; 1360 const targetDepth = tokens.length > 0 && tokens[0].type == tok!"(" ? 1 : 0; 1361 bool gotValue; 1362 1363 void putArg(size_t end) 1364 { 1365 if (start >= end || start >= tokens.length) 1366 return; 1367 1368 CalltipsSupport.Argument arg; 1369 1370 auto typename = tokens[start .. end]; 1371 arg.contentRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1372 if (typename.length == 1) 1373 { 1374 auto t = typename[0]; 1375 if (t.type == tok!"identifier" || t.type.isBasicType) 1376 arg = CalltipsSupport.Argument.templateType(t.tokenRange); 1377 else if (t.type == tok!"...") 1378 arg = CalltipsSupport.Argument.anyVariadic(t.tokenRange); 1379 else 1380 arg = CalltipsSupport.Argument.templateValue(t.tokenRange); 1381 } 1382 else 1383 { 1384 if (gotValue && valueStart > start && valueStart <= end) 1385 { 1386 typename = tokens[start .. valueStart]; 1387 auto val = tokens[valueStart .. end]; 1388 if (val.length) 1389 arg.valueRange = [cast(int) val[0].index, val[$ - 1].tokenEnd]; 1390 } 1391 1392 else if (typename.length == 1) 1393 { 1394 auto t = typename[0]; 1395 if (t.type == tok!"identifier" || t.type.isBasicType) 1396 arg.typeRange = arg.nameRange = t.tokenRange; 1397 else 1398 arg.typeRange = t.tokenRange; 1399 } 1400 else if (typename.length) 1401 { 1402 if (typename[$ - 1].type == tok!"identifier") 1403 { 1404 arg.nameRange = typename[$ - 1].tokenRange; 1405 typename = typename[0 .. $ - 1]; 1406 } 1407 else if (typename[$ - 1].type == tok!"...") 1408 { 1409 arg.variadic = true; 1410 if (typename.length > 1 && typename[$ - 2].type == tok!"identifier") 1411 { 1412 arg.nameRange = typename[$ - 2].tokenRange; 1413 typename = typename[0 .. $ - 2]; 1414 } 1415 else 1416 typename = typename[0 .. 0]; 1417 } 1418 1419 if (typename.length) 1420 arg.typeRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1421 } 1422 } 1423 1424 ret.put(arg); 1425 1426 gotValue = false; 1427 start = end + 1; 1428 } 1429 1430 foreach (i, token; tokens) 1431 { 1432 if (token.type == tok!"{") 1433 subDepth++; 1434 else if (token.type == tok!"}") 1435 { 1436 if (subDepth == 0) 1437 break; 1438 subDepth--; 1439 } 1440 else if (token.type == tok!"(" || token.type == tok!"[") 1441 depth++; 1442 else if (token.type == tok!")" || token.type == tok!"]") 1443 { 1444 if (depth <= targetDepth) 1445 break; 1446 depth--; 1447 } 1448 1449 if (depth == targetDepth) 1450 { 1451 if (token.type == tok!",") 1452 putArg(i); 1453 else if (token.type == tok!":" || token.type == tok!"=") 1454 { 1455 if (!gotValue) 1456 { 1457 valueStart = i + 1; 1458 gotValue = true; 1459 } 1460 } 1461 } 1462 } 1463 putArg(tokens.length); 1464 1465 return ret.data; 1466 } 1467 1468 auto indent(scope const(char)[] code, string indentation) 1469 { 1470 return code.lineSplitter!(KeepTerminator.yes) 1471 .map!(a => a.length ? indentation ~ a : a) 1472 .join; 1473 } 1474 1475 bool fieldNameMatches(string field, in char[] expected) 1476 { 1477 import std.uni : sicmp; 1478 1479 if (field.startsWith("_")) 1480 field = field[1 .. $]; 1481 else if (field.startsWith("m_")) 1482 field = field[2 .. $]; 1483 else if (field.length >= 2 && field[0] == 'm' && field[1].isUpper) 1484 field = field[1 .. $]; 1485 1486 return field.sicmp(expected) == 0; 1487 } 1488 1489 final class CodeBlockInfoFinder : ASTVisitor 1490 { 1491 this(int targetPosition) 1492 { 1493 this.targetPosition = targetPosition; 1494 } 1495 1496 override void visit(const ClassDeclaration dec) 1497 { 1498 visitContainer(dec.name, CodeBlockInfo.Type.class_, dec.structBody); 1499 } 1500 1501 override void visit(const InterfaceDeclaration dec) 1502 { 1503 visitContainer(dec.name, CodeBlockInfo.Type.interface_, dec.structBody); 1504 } 1505 1506 override void visit(const StructDeclaration dec) 1507 { 1508 visitContainer(dec.name, CodeBlockInfo.Type.struct_, dec.structBody); 1509 } 1510 1511 override void visit(const UnionDeclaration dec) 1512 { 1513 visitContainer(dec.name, CodeBlockInfo.Type.union_, dec.structBody); 1514 } 1515 1516 override void visit(const TemplateDeclaration dec) 1517 { 1518 if (cast(int) targetPosition >= cast(int) dec.name.index && targetPosition < dec.endLocation) 1519 { 1520 block = CodeBlockInfo.init; 1521 block.type = CodeBlockInfo.Type.template_; 1522 block.name = dec.name.text; 1523 block.outerRange = [ 1524 cast(uint) dec.name.index, cast(uint) dec.endLocation + 1 1525 ]; 1526 block.innerRange = [ 1527 cast(uint) dec.startLocation + 1, cast(uint) dec.endLocation 1528 ]; 1529 dec.accept(this); 1530 } 1531 } 1532 1533 private void visitContainer(const Token name, CodeBlockInfo.Type type, const StructBody structBody) 1534 { 1535 if (!structBody) 1536 return; 1537 if (cast(int) targetPosition >= cast(int) name.index && targetPosition < structBody.endLocation) 1538 { 1539 block = CodeBlockInfo.init; 1540 block.type = type; 1541 block.name = name.text; 1542 block.outerRange = [ 1543 cast(uint) name.index, cast(uint) structBody.endLocation + 1 1544 ]; 1545 block.innerRange = [ 1546 cast(uint) structBody.startLocation + 1, cast(uint) structBody.endLocation 1547 ]; 1548 structBody.accept(this); 1549 } 1550 } 1551 1552 alias visit = ASTVisitor.visit; 1553 1554 CodeBlockInfo block; 1555 int targetPosition; 1556 } 1557 1558 version (unittest) static immutable string SimpleClassTestCode = q{ 1559 module foo; 1560 1561 class FooBar 1562 { 1563 public: 1564 int i; // default instanced fields 1565 string s; 1566 long l; 1567 1568 public this() // public instanced ctor 1569 { 1570 i = 4; 1571 } 1572 1573 protected: 1574 int x; // protected instanced field 1575 1576 private: 1577 static const int foo() @nogc nothrow pure @system // private static methods 1578 { 1579 if (s == "a") 1580 { 1581 i = 5; 1582 } 1583 } 1584 1585 static void bar1() {} 1586 1587 void bar2() {} // private instanced methods 1588 void bar3() {} 1589 1590 struct Something { string bar; } 1591 1592 FooBar.Something somefunc() { return Something.init; } 1593 Something somefunc2() { return Something.init; } 1594 }}.replace("\r\n", "\n"); 1595 1596 unittest 1597 { 1598 scope backend = new WorkspaceD(); 1599 auto workspace = makeTemporaryTestingWorkspace; 1600 auto instance = backend.addInstance(workspace.directory); 1601 backend.register!DCDExtComponent; 1602 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1603 1604 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 123) == CodeBlockInfo(CodeBlockInfo.Type.class_, 1605 "FooBar", [20, SimpleClassTestCode.length], [ 1606 28, SimpleClassTestCode.length - 1 1607 ])); 1608 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 19) == CodeBlockInfo.init); 1609 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 20) != CodeBlockInfo.init); 1610 1611 auto replacements = dcdext.insertCodeInContainer("void foo()\n{\n\twriteln();\n}", 1612 SimpleClassTestCode, 123); 1613 1614 // TODO: make insertCodeInContainer work properly? 1615 } 1616 1617 unittest 1618 { 1619 import std.conv; 1620 1621 scope backend = new WorkspaceD(); 1622 auto workspace = makeTemporaryTestingWorkspace; 1623 auto instance = backend.addInstance(workspace.directory); 1624 backend.register!DCDExtComponent; 1625 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1626 1627 auto extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg\n\nfoo(); int y;", 23); 1628 assert(!extract.hasTemplate); 1629 assert(extract.parentStart == 7); 1630 assert(extract.functionStart == 11); 1631 assert(extract.functionParensRange[0] == 14); 1632 assert(extract.functionParensRange[1] <= 31); 1633 assert(extract.functionArgs.length == 2); 1634 assert(extract.functionArgs[0].contentRange == [15, 16]); 1635 assert(extract.functionArgs[1].contentRange[0] == 18); 1636 assert(extract.functionArgs[1].contentRange[1] <= 31); 1637 1638 extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg)\n\nfoo(); int y;", 23); 1639 assert(!extract.hasTemplate); 1640 assert(extract.parentStart == 7); 1641 assert(extract.functionStart == 11); 1642 assert(extract.functionParensRange == [14, 24]); 1643 assert(extract.functionArgs.length == 2); 1644 assert(extract.functionArgs[0].contentRange == [15, 16]); 1645 assert(extract.functionArgs[1].contentRange == [18, 23]); 1646 1647 extract = dcdext.extractCallParameters("void foo()", 9, true); 1648 assert(extract != CalltipsSupport.init); 1649 extract = dcdext.extractCallParameters("void foo()", 10, true); 1650 assert(extract == CalltipsSupport.init); 1651 1652 // caused segfault once, doesn't return anything important 1653 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!")")`, 1654 140, true); 1655 assert(extract == CalltipsSupport.init); 1656 1657 extract = dcdext.extractCallParameters( 1658 `auto bar(int foo, Button, my.Callback cb, ..., int[] arr ...)`, 60, true); 1659 assert(extract != CalltipsSupport.init); 1660 assert(!extract.hasTemplate); 1661 assert(!extract.inTemplateParameters); 1662 assert(extract.activeParameter == 4); 1663 assert(extract.functionStart == 5); 1664 assert(extract.parentStart == 5); 1665 assert(extract.functionParensRange == [8, 61]); 1666 assert(extract.functionArgs.length == 5); 1667 assert(extract.functionArgs[0].contentRange == [9, 16]); 1668 assert(extract.functionArgs[0].typeRange == [9, 12]); 1669 assert(extract.functionArgs[0].nameRange == [13, 16]); 1670 assert(extract.functionArgs[1].contentRange == [18, 24]); 1671 assert(extract.functionArgs[1].typeRange == [18, 24]); 1672 assert(extract.functionArgs[1].nameRange == [18, 24]); 1673 assert(extract.functionArgs[2].contentRange == [26, 40]); 1674 assert(extract.functionArgs[2].typeRange == [26, 37]); 1675 assert(extract.functionArgs[2].nameRange == [38, 40]); 1676 assert(extract.functionArgs[3].contentRange == [42, 45]); 1677 assert(extract.functionArgs[3].variadic); 1678 assert(extract.functionArgs[4].contentRange == [47, 60]); 1679 assert(extract.functionArgs[4].typeRange == [47, 52]); 1680 assert(extract.functionArgs[4].nameRange == [53, 56]); 1681 assert(extract.functionArgs[4].variadic); 1682 1683 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!")")}, 1684 150, true); 1685 assert(extract != CalltipsSupport.init); 1686 assert(extract.hasTemplate); 1687 assert(extract.templateArgumentsRange == [26, 38]); 1688 assert(extract.templateArgs.length == 2); 1689 assert(extract.templateArgs[0].contentRange == [27, 28]); 1690 assert(extract.templateArgs[0].nameRange == [27, 28]); 1691 assert(extract.templateArgs[1].contentRange == [30, 37]); 1692 assert(extract.templateArgs[1].nameRange == [30, 34]); 1693 assert(extract.functionStart == 23); 1694 assert(extract.parentStart == 23); 1695 assert(extract.functionParensRange == [38, 151]); 1696 assert(extract.functionArgs.length == 7); 1697 assert(extract.functionArgs[0].contentRange == [39, 42]); 1698 assert(extract.functionArgs[0].typeRange == [39, 40]); 1699 assert(extract.functionArgs[0].nameRange == [41, 42]); 1700 assert(extract.functionArgs[1].contentRange == [44, 47]); 1701 assert(extract.functionArgs[1].typeRange == [44, 45]); 1702 assert(extract.functionArgs[1].nameRange == [46, 47]); 1703 assert(extract.functionArgs[2].contentRange == [49, 67]); 1704 assert(extract.functionArgs[2].typeRange == [49, 63]); 1705 assert(extract.functionArgs[2].nameRange == [64, 67]); 1706 assert(extract.functionArgs[3].contentRange == [69, 85]); 1707 assert(extract.functionArgs[3].typeRange == [69, 78]); 1708 assert(extract.functionArgs[3].nameRange == [79, 85]); 1709 assert(extract.functionArgs[4].contentRange == [87, 122]); 1710 assert(extract.functionArgs[4].typeRange == [87, 115]); 1711 assert(extract.functionArgs[4].nameRange == [116, 122]); 1712 assert(extract.functionArgs[5].contentRange == [124, 139]); 1713 assert(extract.functionArgs[5].typeRange == [124, 133]); 1714 assert(extract.functionArgs[5].nameRange == [134, 139]); 1715 assert(extract.functionArgs[6].contentRange == [141, 150]); 1716 assert(extract.functionArgs[6].typeRange == [141, 150]); 1717 1718 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4`, 44); 1719 assert(extract != CalltipsSupport.init); 1720 assert(!extract.hasTemplate); 1721 assert(extract.activeParameter == 0); 1722 assert(extract.functionStart == 34); 1723 assert(extract.parentStart == 34); 1724 assert(extract.functionArgs.length == 1); 1725 assert(extract.functionArgs[0].contentRange == [43, 44]); 1726 1727 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4, f(4)`, 50); 1728 assert(extract != CalltipsSupport.init); 1729 assert(!extract.hasTemplate); 1730 assert(extract.activeParameter == 1); 1731 assert(extract.functionStart == 34); 1732 assert(extract.parentStart == 34); 1733 assert(extract.functionArgs.length == 2); 1734 assert(extract.functionArgs[0].contentRange == [43, 44]); 1735 assert(extract.functionArgs[1].contentRange == [46, 50]); 1736 1737 extract = dcdext.extractCallParameters(q{some_garbage(code); before(this); funcCall(4, ["a"], JSONValue(["b": JSONValue("c")]), recursive(func, call!s()), "texts )\"(too"}, 1738 129); 1739 assert(extract != CalltipsSupport.init); 1740 assert(!extract.hasTemplate); 1741 assert(extract.functionStart == 34); 1742 assert(extract.parentStart == 34); 1743 assert(extract.functionArgs.length == 5); 1744 assert(extract.functionArgs[0].contentRange == [43, 44]); 1745 assert(extract.functionArgs[1].contentRange == [46, 51]); 1746 assert(extract.functionArgs[2].contentRange == [53, 85]); 1747 assert(extract.functionArgs[3].contentRange == [87, 112]); 1748 assert(extract.functionArgs[4].contentRange == [114, 129]); 1749 1750 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`, 1751 127); 1752 assert(extract == CalltipsSupport.init); 1753 } 1754 1755 unittest 1756 { 1757 scope backend = new WorkspaceD(); 1758 auto workspace = makeTemporaryTestingWorkspace; 1759 auto instance = backend.addInstance(workspace.directory); 1760 backend.register!DCDExtComponent; 1761 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1762 1763 auto info = dcdext.describeInterfaceRecursiveSync(SimpleClassTestCode, 23); 1764 assert(info.details.name == "FooBar"); 1765 assert(info.details.blockRange == [27, 554]); 1766 assert(info.details.referencedTypes.length == 2); 1767 assert(info.details.referencedTypes[0].name == "Something"); 1768 assert(info.details.referencedTypes[0].location == 455); 1769 assert(info.details.referencedTypes[1].name == "string"); 1770 assert(info.details.referencedTypes[1].location == 74); 1771 1772 assert(info.details.fields.length == 4); 1773 assert(info.details.fields[0].name == "i"); 1774 assert(info.details.fields[1].name == "s"); 1775 assert(info.details.fields[2].name == "l"); 1776 assert(info.details.fields[3].name == "x"); 1777 1778 assert(info.details.types.length == 1); 1779 assert(info.details.types[0].type == TypeDetails.Type.struct_); 1780 assert(info.details.types[0].name == ["FooBar", "Something"]); 1781 assert(info.details.types[0].nameLocation == 420); 1782 1783 assert(info.details.methods.length == 6); 1784 assert(info.details.methods[0].name == "foo"); 1785 assert( 1786 info.details.methods[0].signature 1787 == "private static const int foo() @nogc nothrow pure @system;"); 1788 assert(info.details.methods[0].returnType == "int"); 1789 assert(info.details.methods[0].isNothrowOrNogc); 1790 assert(info.details.methods[0].hasBody); 1791 assert(!info.details.methods[0].needsImplementation); 1792 assert(!info.details.methods[0].optionalImplementation); 1793 assert(info.details.methods[0].definitionRange == [222, 286]); 1794 assert(info.details.methods[0].blockRange == [286, 324]); 1795 1796 assert(info.details.methods[1].name == "bar1"); 1797 assert(info.details.methods[1].signature == "private static void bar1();"); 1798 assert(info.details.methods[1].returnType == "void"); 1799 assert(!info.details.methods[1].isNothrowOrNogc); 1800 assert(info.details.methods[1].hasBody); 1801 assert(!info.details.methods[1].needsImplementation); 1802 assert(!info.details.methods[1].optionalImplementation); 1803 assert(info.details.methods[1].definitionRange == [334, 346]); 1804 assert(info.details.methods[1].blockRange == [346, 348]); 1805 1806 assert(info.details.methods[2].name == "bar2"); 1807 assert(info.details.methods[2].signature == "private void bar2();"); 1808 assert(info.details.methods[2].returnType == "void"); 1809 assert(!info.details.methods[2].isNothrowOrNogc); 1810 assert(info.details.methods[2].hasBody); 1811 assert(!info.details.methods[2].needsImplementation); 1812 assert(!info.details.methods[2].optionalImplementation); 1813 assert(info.details.methods[2].definitionRange == [351, 363]); 1814 assert(info.details.methods[2].blockRange == [363, 365]); 1815 1816 assert(info.details.methods[3].name == "bar3"); 1817 assert(info.details.methods[3].signature == "private void bar3();"); 1818 assert(info.details.methods[3].returnType == "void"); 1819 assert(!info.details.methods[3].isNothrowOrNogc); 1820 assert(info.details.methods[3].hasBody); 1821 assert(!info.details.methods[3].needsImplementation); 1822 assert(!info.details.methods[3].optionalImplementation); 1823 assert(info.details.methods[3].definitionRange == [396, 408]); 1824 assert(info.details.methods[3].blockRange == [408, 410]); 1825 1826 assert(info.details.methods[4].name == "somefunc"); 1827 assert(info.details.methods[4].signature == "private FooBar.Something somefunc();"); 1828 assert(info.details.methods[4].returnType == "FooBar.Something"); 1829 assert(!info.details.methods[4].isNothrowOrNogc); 1830 assert(info.details.methods[4].hasBody); 1831 assert(!info.details.methods[4].needsImplementation); 1832 assert(!info.details.methods[4].optionalImplementation); 1833 assert(info.details.methods[4].definitionRange == [448, 476]); 1834 assert(info.details.methods[4].blockRange == [476, 502]); 1835 1836 // test normalization of types 1837 assert(info.details.methods[5].name == "somefunc2"); 1838 assert(info.details.methods[5].signature == "private FooBar.Something somefunc2();", 1839 info.details.methods[5].signature); 1840 assert(info.details.methods[5].returnType == "FooBar.Something"); 1841 assert(!info.details.methods[5].isNothrowOrNogc); 1842 assert(info.details.methods[5].hasBody); 1843 assert(!info.details.methods[5].needsImplementation); 1844 assert(!info.details.methods[5].optionalImplementation); 1845 assert(info.details.methods[5].definitionRange == [504, 526]); 1846 assert(info.details.methods[5].blockRange == [526, 552]); 1847 } 1848 1849 unittest 1850 { 1851 string testCode = q{package interface Foo0 1852 { 1853 string stringMethod(); 1854 Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c); 1855 void normalMethod(); 1856 int attributeSuffixMethod() nothrow @property @nogc; 1857 private 1858 { 1859 void middleprivate1(); 1860 void middleprivate2(); 1861 } 1862 extern(C) @property @nogc ref immutable int attributePrefixMethod() const; 1863 final void alreadyImplementedMethod() {} 1864 deprecated("foo") void deprecatedMethod() {} 1865 static void staticMethod() {} 1866 protected void protectedMethod(); 1867 private: 1868 void barfoo(); 1869 }}.replace("\r\n", "\n"); 1870 1871 scope backend = new WorkspaceD(); 1872 auto workspace = makeTemporaryTestingWorkspace; 1873 auto instance = backend.addInstance(workspace.directory); 1874 backend.register!DCDExtComponent; 1875 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1876 1877 auto info = dcdext.describeInterfaceRecursiveSync(testCode, 20); 1878 assert(info.details.name == "Foo0"); 1879 assert(info.details.blockRange == [23, 523]); 1880 assert(info.details.referencedTypes.length == 3); 1881 assert(info.details.referencedTypes[0].name == "Array"); 1882 assert(info.details.referencedTypes[0].location == 70); 1883 assert(info.details.referencedTypes[1].name == "Tuple"); 1884 assert(info.details.referencedTypes[1].location == 50); 1885 assert(info.details.referencedTypes[2].name == "string"); 1886 assert(info.details.referencedTypes[2].location == 26); 1887 1888 assert(info.details.fields.length == 0); 1889 1890 assert(info.details.methods[0 .. 4].all!"!a.hasBody"); 1891 assert(info.details.methods[0 .. 4].all!"a.needsImplementation"); 1892 assert(info.details.methods.all!"!a.optionalImplementation"); 1893 1894 assert(info.details.methods.length == 12); 1895 assert(info.details.methods[0].name == "stringMethod"); 1896 assert(info.details.methods[0].signature == "string stringMethod();"); 1897 assert(info.details.methods[0].returnType == "string"); 1898 assert(!info.details.methods[0].isNothrowOrNogc); 1899 1900 assert(info.details.methods[1].name == "advancedMethod"); 1901 assert(info.details.methods[1].signature 1902 == "Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c);"); 1903 assert(info.details.methods[1].returnType == "Tuple!(int, string, Array!bool)[][]"); 1904 assert(!info.details.methods[1].isNothrowOrNogc); 1905 1906 assert(info.details.methods[2].name == "normalMethod"); 1907 assert(info.details.methods[2].signature == "void normalMethod();"); 1908 assert(info.details.methods[2].returnType == "void"); 1909 1910 assert(info.details.methods[3].name == "attributeSuffixMethod"); 1911 assert(info.details.methods[3].signature == "int attributeSuffixMethod() nothrow @property @nogc;"); 1912 assert(info.details.methods[3].returnType == "int"); 1913 assert(info.details.methods[3].isNothrowOrNogc); 1914 1915 assert(info.details.methods[4].name == "middleprivate1"); 1916 assert(info.details.methods[4].signature == "private void middleprivate1();"); 1917 assert(info.details.methods[4].returnType == "void"); 1918 1919 assert(info.details.methods[5].name == "middleprivate2"); 1920 1921 assert(info.details.methods[6].name == "attributePrefixMethod"); 1922 assert(info.details.methods[6].signature 1923 == "extern (C) @property @nogc ref immutable int attributePrefixMethod() const;"); 1924 assert(info.details.methods[6].returnType == "int"); 1925 assert(info.details.methods[6].isNothrowOrNogc); 1926 1927 assert(info.details.methods[7].name == "alreadyImplementedMethod"); 1928 assert(info.details.methods[7].signature == "void alreadyImplementedMethod();"); 1929 assert(info.details.methods[7].returnType == "void"); 1930 assert(!info.details.methods[7].needsImplementation); 1931 assert(info.details.methods[7].hasBody); 1932 1933 assert(info.details.methods[8].name == "deprecatedMethod"); 1934 assert(info.details.methods[8].signature == `deprecated("foo") void deprecatedMethod();`); 1935 assert(info.details.methods[8].returnType == "void"); 1936 assert(info.details.methods[8].needsImplementation); 1937 assert(info.details.methods[8].hasBody); 1938 1939 assert(info.details.methods[9].name == "staticMethod"); 1940 assert(info.details.methods[9].signature == `static void staticMethod();`); 1941 assert(info.details.methods[9].returnType == "void"); 1942 assert(!info.details.methods[9].needsImplementation); 1943 assert(info.details.methods[9].hasBody); 1944 1945 assert(info.details.methods[10].name == "protectedMethod"); 1946 assert(info.details.methods[10].signature == `protected void protectedMethod();`); 1947 assert(info.details.methods[10].returnType == "void"); 1948 assert(info.details.methods[10].needsImplementation); 1949 assert(!info.details.methods[10].hasBody); 1950 1951 assert(info.details.methods[11].name == "barfoo"); 1952 assert(info.details.methods[11].signature == `private void barfoo();`); 1953 assert(info.details.methods[11].returnType == "void"); 1954 assert(!info.details.methods[11].needsImplementation); 1955 assert(!info.details.methods[11].hasBody); 1956 } 1957 1958 unittest 1959 { 1960 string testCode = q{module hello; 1961 1962 interface MyInterface 1963 { 1964 void foo(); 1965 } 1966 1967 class ImplA : MyInterface 1968 { 1969 1970 } 1971 1972 class ImplB : MyInterface 1973 { 1974 void foo() {} 1975 } 1976 }.replace("\r\n", "\n"); 1977 1978 scope backend = new WorkspaceD(); 1979 auto workspace = makeTemporaryTestingWorkspace; 1980 auto instance = backend.addInstance(workspace.directory); 1981 backend.register!DCDExtComponent; 1982 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1983 1984 auto info = dcdext.getInterfaceDetails("stdin", testCode, 72); 1985 1986 assert(info.blockRange == [81, 85]); 1987 } 1988 1989 unittest 1990 { 1991 scope backend = new WorkspaceD(); 1992 auto workspace = makeTemporaryTestingWorkspace; 1993 auto instance = backend.addInstance(workspace.directory); 1994 backend.register!DCDExtComponent; 1995 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1996 1997 assert(dcdext.formatDefinitionBlock("Foo!(int, string) x") == "Foo!(int, string) x"); 1998 assert(dcdext.formatDefinitionBlock("void foo()") == "void foo()"); 1999 assert(dcdext.formatDefinitionBlock("void foo(string x)") == "void foo(\n\tstring x\n)"); 2000 assert(dcdext.formatDefinitionBlock("void foo(string x,)") == "void foo(\n\tstring x\n)"); 2001 assert(dcdext.formatDefinitionBlock("void foo(string x, int y)") == "void foo(\n\tstring x,\n\tint y\n)"); 2002 assert(dcdext.formatDefinitionBlock("void foo(string, int)") == "void foo(\n\tstring,\n\tint\n)"); 2003 assert(dcdext.formatDefinitionBlock("this(string, int)") == "this(\n\tstring,\n\tint\n)"); 2004 assert(dcdext.formatDefinitionBlock("auto foo(string, int)") == "auto foo(\n\tstring,\n\tint\n)"); 2005 assert(dcdext.formatDefinitionBlock("ComplexTemplate!(int, 'a', string, Nested!(Foo)) foo(string, int)") 2006 == "ComplexTemplate!(int, 'a', string, Nested!(Foo)) foo(\n\tstring,\n\tint\n)"); 2007 assert(dcdext.formatDefinitionBlock("auto foo(T, V)(string, int)") == "auto foo(\n\tT,\n\tV\n)(\n\tstring,\n\tint\n)"); 2008 assert(dcdext.formatDefinitionBlock("auto foo(string, int f, ...)") == "auto foo(\n\tstring,\n\tint f,\n\t...\n)"); 2009 } 2010 2011 final class IfFinder : ASTVisitor 2012 { 2013 Token[] currentIf, foundIf; 2014 2015 size_t target; 2016 2017 alias visit = ASTVisitor.visit; 2018 2019 static foreach (If; AliasSeq!(IfStatement, ConditionalStatement)) 2020 override void visit(const If ifStatement) 2021 { 2022 if (foundIf.length) 2023 return; 2024 2025 auto lastIf = currentIf; 2026 scope (exit) 2027 currentIf = lastIf; 2028 2029 currentIf = [ifStatement.tokens[0]]; 2030 2031 static auto thenStatement(const If v) 2032 { 2033 static if (is(If == IfStatement)) 2034 return v.thenStatement; 2035 else 2036 return v.trueStatement; 2037 } 2038 2039 static auto elseStatement(const If v) 2040 { 2041 static if (is(If == IfStatement)) 2042 return v.elseStatement; 2043 else 2044 return v.falseStatement; 2045 } 2046 2047 if (thenStatement(ifStatement)) 2048 thenStatement(ifStatement).accept(this); 2049 2050 const(BaseNode) elseStmt = elseStatement(ifStatement); 2051 while (elseStmt) 2052 { 2053 auto elseToken = elseStmt.tokens.ptr - 1; 2054 2055 // possible from if declarations 2056 if (elseToken.type == tok!"{" || elseToken.type == tok!":") 2057 elseToken--; 2058 2059 if (elseToken.type == tok!"else") 2060 { 2061 if (!currentIf.length || currentIf[$ - 1] != *elseToken) 2062 currentIf ~= *elseToken; 2063 } 2064 2065 if (auto elseIf = cast(IfStatement) elseStmt) 2066 { 2067 currentIf ~= elseIf.tokens[0]; 2068 elseIf.accept(this); 2069 cast()elseStmt = elseIf.elseStatement; 2070 } 2071 else if (auto elseStaticIf = cast(ConditionalStatement) elseStmt) 2072 { 2073 currentIf ~= elseStaticIf.tokens[0]; 2074 currentIf ~= elseStaticIf.tokens[1]; 2075 elseStaticIf.accept(this); 2076 cast()elseStmt = elseStaticIf.falseStatement; 2077 } 2078 else if (auto declOrStatement = cast(DeclarationOrStatement) elseStmt) 2079 { 2080 if (declOrStatement.statement && declOrStatement.statement.statementNoCaseNoDefault) 2081 { 2082 if (declOrStatement.statement.statementNoCaseNoDefault.conditionalStatement) 2083 { 2084 cast()elseStmt = declOrStatement.statement.statementNoCaseNoDefault.conditionalStatement; 2085 } 2086 else if (declOrStatement.statement.statementNoCaseNoDefault.ifStatement) 2087 { 2088 cast()elseStmt = declOrStatement.statement.statementNoCaseNoDefault.ifStatement; 2089 } 2090 else 2091 { 2092 elseStmt.accept(this); 2093 cast()elseStmt = null; 2094 } 2095 } 2096 else if (declOrStatement.declaration && declOrStatement.declaration.conditionalDeclaration) 2097 { 2098 auto cond = declOrStatement.declaration.conditionalDeclaration; 2099 if (cond.trueDeclarations.length) 2100 { 2101 auto ifSearch = cond.trueDeclarations[0].tokens.ptr; 2102 while (!ifSearch.type.among!(tok!"if", tok!";", tok!"}", tok!"module")) 2103 ifSearch--; 2104 2105 if (ifSearch.type == tok!"if") 2106 { 2107 if ((ifSearch - 1).type == tok!"static") 2108 currentIf ~= *(ifSearch - 1); 2109 currentIf ~= *ifSearch; 2110 } 2111 } 2112 2113 if (cond.hasElse && cond.falseDeclarations.length == 1) 2114 { 2115 elseStmt.accept(this); 2116 cast()elseStmt = cast()cond.falseDeclarations[0]; 2117 } 2118 else 2119 { 2120 elseStmt.accept(this); 2121 cast()elseStmt = null; 2122 } 2123 } 2124 else 2125 { 2126 elseStmt.accept(this); 2127 cast()elseStmt = null; 2128 } 2129 } 2130 else 2131 { 2132 elseStmt.accept(this); 2133 cast()elseStmt = null; 2134 } 2135 } 2136 2137 saveIfMatching(); 2138 } 2139 2140 void saveIfMatching() 2141 { 2142 if (foundIf.length) 2143 return; 2144 2145 foreach (v; currentIf) 2146 if (v.index == target) 2147 { 2148 foundIf = currentIf; 2149 return; 2150 } 2151 } 2152 } 2153 2154 unittest 2155 { 2156 scope backend = new WorkspaceD(); 2157 auto workspace = makeTemporaryTestingWorkspace; 2158 auto instance = backend.addInstance(workspace.directory); 2159 backend.register!DCDExtComponent; 2160 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2161 2162 assert(dcdext.highlightRelated(`void foo() 2163 { 2164 if (true) {} 2165 else static if (true) {} 2166 else if (true) {} 2167 else {} 2168 2169 if (true) {} 2170 else static if (true) {} 2171 else {} 2172 }`, 35) == [ 2173 Related(Related.Type.controlFlow, [14, 16]), 2174 Related(Related.Type.controlFlow, [28, 32]), 2175 Related(Related.Type.controlFlow, [33, 39]), 2176 Related(Related.Type.controlFlow, [40, 42]), 2177 Related(Related.Type.controlFlow, [54, 58]), 2178 Related(Related.Type.controlFlow, [59, 61]), 2179 Related(Related.Type.controlFlow, [73, 77]), 2180 ]); 2181 2182 assert(dcdext.highlightRelated(`void foo() 2183 { 2184 if (true) {} 2185 else static if (true) {} 2186 else if (true) {} 2187 else {} 2188 2189 if (true) {} 2190 else static if (true) { int a; } 2191 else { int b;} 2192 }`, 83) == [ 2193 Related(Related.Type.controlFlow, [83, 85]), 2194 Related(Related.Type.controlFlow, [97, 101]), 2195 Related(Related.Type.controlFlow, [102, 108]), 2196 Related(Related.Type.controlFlow, [109, 111]), 2197 Related(Related.Type.controlFlow, [131, 135]), 2198 ]); 2199 } 2200 2201 final class SwitchFinder : ASTVisitor 2202 { 2203 Token[] currentSwitch, foundSwitch; 2204 const(Statement) currentStatement; 2205 2206 size_t target; 2207 2208 alias visit = ASTVisitor.visit; 2209 2210 override void visit(const SwitchStatement stmt) 2211 { 2212 if (foundSwitch.length) 2213 return; 2214 2215 auto lastSwitch = currentSwitch; 2216 scope (exit) 2217 currentSwitch = lastSwitch; 2218 2219 currentSwitch = [stmt.tokens[0]]; 2220 stmt.accept(this); 2221 2222 saveIfMatching(); 2223 } 2224 2225 override void visit(const CaseRangeStatement stmt) 2226 { 2227 if (currentStatement) 2228 { 2229 auto curr = currentStatement.tokens[0]; 2230 if (curr.type == tok!"case") 2231 currentSwitch ~= curr; 2232 } 2233 auto last = *(stmt.high.tokens.ptr - 1); 2234 if (last.type == tok!"case") 2235 currentSwitch ~= last; 2236 stmt.accept(this); 2237 } 2238 2239 override void visit(const CaseStatement stmt) 2240 { 2241 if (currentStatement) 2242 { 2243 auto curr = currentStatement.tokens[0]; 2244 if (curr.type == tok!"case") 2245 currentSwitch ~= curr; 2246 } 2247 stmt.accept(this); 2248 } 2249 2250 override void visit(const DefaultStatement stmt) 2251 { 2252 currentSwitch ~= stmt.tokens[0]; 2253 stmt.accept(this); 2254 } 2255 2256 override void visit(const Statement stmt) 2257 { 2258 auto last = currentStatement; 2259 scope (exit) 2260 cast()currentStatement = cast()last; 2261 cast()currentStatement = cast()stmt; 2262 stmt.accept(this); 2263 } 2264 2265 void saveIfMatching() 2266 { 2267 if (foundSwitch.length) 2268 return; 2269 2270 foreach (v; currentSwitch) 2271 if (v.index == target) 2272 { 2273 foundSwitch = currentSwitch; 2274 return; 2275 } 2276 } 2277 } 2278 2279 unittest 2280 { 2281 scope backend = new WorkspaceD(); 2282 auto workspace = makeTemporaryTestingWorkspace; 2283 auto instance = backend.addInstance(workspace.directory); 2284 backend.register!DCDExtComponent; 2285 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2286 2287 assert(dcdext.highlightRelated(`void foo() 2288 { 2289 switch (foo) 2290 { 2291 case 1: .. case 3: 2292 break; 2293 case 5: 2294 switch (bar) 2295 { 2296 case 6: 2297 break; 2298 default: 2299 break; 2300 } 2301 break; 2302 default: 2303 break; 2304 } 2305 }`.normLF, 35) == [ 2306 Related(Related.Type.controlFlow, [14, 20]), 2307 Related(Related.Type.controlFlow, [32, 36]), 2308 Related(Related.Type.controlFlow, [43, 47]), 2309 Related(Related.Type.controlFlow, [63, 67]), 2310 Related(Related.Type.controlFlow, [154, 161]), 2311 ]); 2312 } 2313 2314 final class BreakFinder : ASTVisitor 2315 { 2316 Token[] currentBlock, foundBlock; 2317 const(Statement) currentStatement; 2318 bool inSwitch; 2319 2320 size_t target; 2321 bool isBreak; // else continue if not loop 2322 bool isLoop; // checking loop token (instead of break/continue) 2323 string label; 2324 2325 alias visit = ASTVisitor.visit; 2326 2327 override void visit(const LabeledStatement stmt) 2328 { 2329 if (foundBlock.length) 2330 return; 2331 2332 if (label.length && label == stmt.identifier.text) 2333 { 2334 foundBlock = [stmt.identifier]; 2335 return; 2336 } 2337 2338 stmt.accept(this); 2339 } 2340 2341 override void visit(const SwitchStatement stmt) 2342 { 2343 if (foundBlock.length) 2344 return; 2345 2346 bool wasSwitch = inSwitch; 2347 scope (exit) 2348 inSwitch = wasSwitch; 2349 inSwitch = true; 2350 2351 if (isBreak) 2352 { 2353 auto lastSwitch = currentBlock; 2354 scope (exit) 2355 currentBlock = lastSwitch; 2356 2357 currentBlock = [stmt.tokens[0]]; 2358 stmt.accept(this); 2359 2360 saveIfMatching(); 2361 } 2362 else 2363 { 2364 stmt.accept(this); 2365 } 2366 } 2367 2368 static foreach (LoopT; AliasSeq!(ForeachStatement, StaticForeachDeclaration, 2369 StaticForeachStatement, ForStatement, WhileStatement)) 2370 override void visit(const LoopT stmt) 2371 { 2372 if (foundBlock.length) 2373 return; 2374 2375 auto lastSwitch = currentBlock; 2376 scope (exit) 2377 currentBlock = lastSwitch; 2378 2379 currentBlock = [stmt.tokens[0]]; 2380 stmt.accept(this); 2381 2382 saveIfMatching(); 2383 } 2384 2385 override void visit(const DoStatement stmt) 2386 { 2387 if (foundBlock.length) 2388 return; 2389 2390 auto lastSwitch = currentBlock; 2391 scope (exit) 2392 currentBlock = lastSwitch; 2393 2394 currentBlock = [stmt.tokens[0]]; 2395 auto whileTok = *(stmt.expression.tokens.ptr - 2); 2396 stmt.accept(this); 2397 if (whileTok.type == tok!"while") 2398 currentBlock ~= whileTok; 2399 2400 saveIfMatching(); 2401 } 2402 2403 static foreach (IgnoreT; AliasSeq!(FunctionBody, FunctionDeclaration, StructBody)) 2404 override void visit(const IgnoreT stmt) 2405 { 2406 if (foundBlock.length) 2407 return; 2408 2409 auto lastSwitch = currentBlock; 2410 scope (exit) 2411 currentBlock = lastSwitch; 2412 2413 currentBlock = null; 2414 stmt.accept(this); 2415 } 2416 2417 override void visit(const CaseRangeStatement stmt) 2418 { 2419 if (isBreak) 2420 { 2421 if (currentStatement) 2422 { 2423 auto curr = currentStatement.tokens[0]; 2424 if (curr.type == tok!"case") 2425 currentBlock ~= curr; 2426 } 2427 auto last = *(stmt.high.tokens.ptr - 1); 2428 if (last.type == tok!"case") 2429 currentBlock ~= last; 2430 } 2431 stmt.accept(this); 2432 } 2433 2434 override void visit(const CaseStatement stmt) 2435 { 2436 if (currentStatement && isBreak) 2437 { 2438 auto curr = currentStatement.tokens[0]; 2439 if (curr.type == tok!"case") 2440 currentBlock ~= curr; 2441 } 2442 stmt.accept(this); 2443 } 2444 2445 override void visit(const DefaultStatement stmt) 2446 { 2447 if (isBreak) 2448 currentBlock ~= stmt.tokens[0]; 2449 stmt.accept(this); 2450 } 2451 2452 override void visit(const Statement stmt) 2453 { 2454 auto last = currentStatement; 2455 scope (exit) 2456 cast()currentStatement = cast()last; 2457 cast()currentStatement = cast()stmt; 2458 stmt.accept(this); 2459 } 2460 2461 override void visit(const BreakStatement stmt) 2462 { 2463 if (stmt.tokens[0].index == target || isLoop) 2464 if (isBreak) 2465 currentBlock ~= stmt.tokens[0]; 2466 stmt.accept(this); 2467 } 2468 2469 override void visit(const ContinueStatement stmt) 2470 { 2471 // break token: 2472 // continue in switch: ignore 2473 // continue outside switch: include 2474 // other token: 2475 // continue in switch: include 2476 // continue outside switch: include 2477 if (stmt.tokens[0].index == target || isLoop) 2478 if (!(isBreak && inSwitch)) 2479 currentBlock ~= stmt.tokens[0]; 2480 stmt.accept(this); 2481 } 2482 2483 void saveIfMatching() 2484 { 2485 if (foundBlock.length || label.length) 2486 return; 2487 2488 foreach (v; currentBlock) 2489 if (v.index == target) 2490 { 2491 foundBlock = currentBlock; 2492 return; 2493 } 2494 } 2495 } 2496 2497 class ReverseReturnFinder : ASTVisitor 2498 { 2499 Token[] returns; 2500 size_t target; 2501 bool record; 2502 2503 alias visit = ASTVisitor.visit; 2504 2505 static foreach (DeclT; AliasSeq!(Declaration, Statement)) 2506 override void visit(const DeclT stmt) 2507 { 2508 if (returns.length && !record) 2509 return; 2510 2511 bool matches = stmt.tokens.length && stmt.tokens[0].index == target; 2512 if (matches) 2513 record = true; 2514 stmt.accept(this); 2515 if (matches) 2516 record = false; 2517 } 2518 2519 override void visit(const ReturnStatement ret) 2520 { 2521 if (record) 2522 returns ~= ret.tokens[0]; 2523 ret.accept(this); 2524 } 2525 } 2526 2527 unittest 2528 { 2529 scope backend = new WorkspaceD(); 2530 auto workspace = makeTemporaryTestingWorkspace; 2531 auto instance = backend.addInstance(workspace.directory); 2532 backend.register!DCDExtComponent; 2533 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2534 2535 assert(dcdext.highlightRelated(`void foo() 2536 { 2537 while (true) 2538 { 2539 foreach (a; b) 2540 { 2541 switch (a) 2542 { 2543 case 1: 2544 break; 2545 case 2: 2546 continue; 2547 default: 2548 return; 2549 } 2550 } 2551 } 2552 }`.normLF, 88) == [ 2553 Related(Related.Type.controlFlow, [54, 60]), 2554 Related(Related.Type.controlFlow, [73, 77]), 2555 Related(Related.Type.controlFlow, [85, 90]), 2556 Related(Related.Type.controlFlow, [95, 99]), 2557 Related(Related.Type.controlFlow, [120, 127]), 2558 ]); 2559 2560 assert(dcdext.highlightRelated(`void foo() 2561 { 2562 while (true) 2563 { 2564 foreach (a; b) 2565 { 2566 switch (a) 2567 { 2568 case 1: 2569 break; 2570 case 2: 2571 continue; 2572 default: 2573 return; 2574 } 2575 } 2576 } 2577 }`.normLF, 111) == [ 2578 Related(Related.Type.controlFlow, [32, 39]), 2579 Related(Related.Type.controlFlow, [107, 115]), 2580 ]); 2581 2582 assert(dcdext.highlightRelated(`void foo() 2583 { 2584 while (true) 2585 { 2586 foreach (a; b) 2587 { 2588 switch (a) 2589 { 2590 case 1: 2591 break; 2592 case 2: 2593 continue; 2594 default: 2595 return; 2596 } 2597 } 2598 } 2599 }`.normLF, 15) == [ 2600 Related(Related.Type.controlFlow, [14, 19]), 2601 Related(Related.Type.controlFlow, [133, 139]), 2602 ]); 2603 } 2604 2605 class ReturnFinder : ASTVisitor 2606 { 2607 Token[] returns; 2608 Token[] currentScope; 2609 bool inTargetBlock; 2610 Token[] related; 2611 size_t target; 2612 2613 alias visit = ASTVisitor.visit; 2614 2615 static foreach (DeclT; AliasSeq!(FunctionBody)) 2616 override void visit(const DeclT stmt) 2617 { 2618 if (inTargetBlock || related.length) 2619 return; 2620 2621 auto lastScope = currentScope; 2622 scope (exit) 2623 currentScope = lastScope; 2624 currentScope = null; 2625 2626 auto lastReturns = returns; 2627 scope (exit) 2628 returns = lastReturns; 2629 returns = null; 2630 2631 stmt.accept(this); 2632 if (inTargetBlock) 2633 { 2634 related ~= returns; 2635 2636 related.sort!"a.index < b.index"; 2637 } 2638 } 2639 2640 static foreach (ScopeT; AliasSeq!(SwitchStatement, ForeachStatement, 2641 StaticForeachDeclaration, StaticForeachStatement, ForStatement, WhileStatement)) 2642 override void visit(const ScopeT stmt) 2643 { 2644 auto lastScope = currentScope; 2645 scope (exit) 2646 currentScope = lastScope; 2647 currentScope ~= stmt.tokens[0]; 2648 2649 stmt.accept(this); 2650 } 2651 2652 override void visit(const DoStatement stmt) 2653 { 2654 auto lastScope = currentScope; 2655 scope (exit) 2656 currentScope = lastScope; 2657 currentScope ~= stmt.tokens[0]; 2658 2659 auto whileTok = *(stmt.expression.tokens.ptr - 2); 2660 if (whileTok.type == tok!"while") 2661 currentScope ~= whileTok; 2662 2663 stmt.accept(this); 2664 } 2665 2666 override void visit(const ReturnStatement ret) 2667 { 2668 returns ~= ret.tokens[0]; 2669 if (target == ret.tokens[0].index) 2670 { 2671 inTargetBlock = true; 2672 related ~= currentScope; 2673 } 2674 ret.accept(this); 2675 } 2676 } 2677 2678 unittest 2679 { 2680 scope backend = new WorkspaceD(); 2681 auto workspace = makeTemporaryTestingWorkspace; 2682 auto instance = backend.addInstance(workspace.directory); 2683 backend.register!DCDExtComponent; 2684 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2685 2686 assert(dcdext.highlightRelated(`void foo() 2687 { 2688 foreach (a; b) 2689 return; 2690 2691 void bar() 2692 { 2693 return; 2694 } 2695 2696 bar(); 2697 2698 return; 2699 }`.normLF, 33) == [ 2700 Related(Related.Type.controlFlow, [14, 21]), 2701 Related(Related.Type.controlFlow, [31, 37]), 2702 Related(Related.Type.controlFlow, [79, 85]), 2703 ]); 2704 }