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 import painlessjson : SerializeIgnore; 30 31 public import workspaced.visitors.methodfinder : InterfaceDetails, FieldDetails, 32 MethodDetails, ArgumentInfo; 33 34 @component("dcdext") 35 class DCDExtComponent : ComponentWrapper 36 { 37 mixin DefaultComponentWrapper; 38 39 static immutable CodeRegionProtection[] mixableProtection = [ 40 CodeRegionProtection.public_ | CodeRegionProtection.default_, 41 CodeRegionProtection.package_, CodeRegionProtection.packageIdentifier, 42 CodeRegionProtection.protected_, CodeRegionProtection.private_ 43 ]; 44 45 /// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}` 46 void load() 47 { 48 if (!refInstance) 49 return; 50 51 config.stringBehavior = StringBehavior.source; 52 } 53 54 /// Extracts calltips help information at a given position. 55 /// The position must be within the arguments of the function and not 56 /// outside the parentheses or inside some child call. 57 /// 58 /// When generating the call parameters for a function definition, the position must be inside the normal parameters, 59 /// otherwise the template arguments will be put as normal arguments. 60 /// 61 /// Returns: the position of significant locations for parameter extraction. 62 /// Params: 63 /// code = code to analyze 64 /// position = byte offset where to check for function arguments 65 /// definition = true if this hints is a function definition (templates don't have an exclamation point '!') 66 CalltipsSupport extractCallParameters(scope const(char)[] code, int position, 67 bool definition = false) 68 { 69 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 70 if (!tokens.length) 71 return CalltipsSupport.init; 72 // TODO: can probably use tokenIndexAtByteIndex here 73 auto queuedToken = tokens.countUntil!(a => a.index >= position) - 1; 74 if (queuedToken == -2) 75 queuedToken = cast(ptrdiff_t) tokens.length - 1; 76 else if (queuedToken == -1) 77 return CalltipsSupport.init; 78 79 // TODO: refactor code to be more readable 80 // all this code does is: 81 // - go back all tokens until a starting ( is found. (with nested {} scope checks for delegates and () for calls) 82 // - abort if not found 83 // - set "isTemplate" if directly before the ( is a `!` token and an identifier 84 // - if inTemplate is true: 85 // - 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 86 // - else not in template arguments, so 87 // - if before ( comes a ) we are definitely in a template, so track back until starting ( 88 // - otherwise check if it's even a template (single argument: `!`, then a token, then `(`) 89 // - determine function name & all parents (strips out index operators) 90 // - split template & function arguments 91 // - return all information 92 // it's reasonably readable with the variable names and that pseudo explanation there pretty much directly maps to the code, 93 // 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. 94 95 /// describes if the target position is inside template arguments rather than function arguments (only works for calls and not for definition) 96 bool inTemplate; 97 int activeParameter; // counted commas 98 int depth, subDepth; 99 /// contains opening parentheses location for arguments or exclamation point for templates. 100 auto startParen = queuedToken; 101 while (startParen >= 0) 102 { 103 const c = tokens[startParen]; 104 const p = startParen > 0 ? tokens[startParen - 1] : Token.init; 105 106 if (c.type == tok!"{") 107 { 108 if (subDepth == 0) 109 { 110 // we went too far, probably not inside a function (or we are in a delegate, where we don't want calltips) 111 return CalltipsSupport.init; 112 } 113 else 114 subDepth--; 115 } 116 else if (c.type == tok!"}") 117 { 118 subDepth++; 119 } 120 else if (subDepth == 0 && c.type == tok!";") 121 { 122 // this doesn't look like function arguments anymore 123 return CalltipsSupport.init; 124 } 125 else if (depth == 0 && !definition && c.type == tok!"!" && p.type == tok!"identifier") 126 { 127 inTemplate = true; 128 break; 129 } 130 else if (c.type == tok!")") 131 { 132 depth++; 133 } 134 else if (c.type == tok!"(") 135 { 136 if (depth == 0 && subDepth == 0) 137 { 138 if (startParen > 1 && p.type == tok!"!" && tokens[startParen - 2].type 139 == tok!"identifier") 140 { 141 startParen--; 142 inTemplate = true; 143 } 144 break; 145 } 146 else 147 depth--; 148 } 149 else if (depth == 0 && subDepth == 0 && c.type == tok!",") 150 { 151 activeParameter++; 152 } 153 startParen--; 154 } 155 156 if (startParen <= 0) 157 return CalltipsSupport.init; 158 159 /// 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. 160 auto templateOpen = inTemplate ? startParen : 0; 161 /// Token index where the normal argument parentheses start or 0 if it doesn't exist for this call/definition 162 auto functionOpen = inTemplate ? 0 : startParen; 163 164 bool hasTemplateParens = false; 165 166 if (inTemplate) 167 { 168 // go forwards to function arguments 169 if (templateOpen + 2 < tokens.length) 170 { 171 if (tokens[templateOpen + 1].type == tok!"(") 172 { 173 hasTemplateParens = true; 174 templateOpen++; 175 functionOpen = findClosingParenForward(tokens, templateOpen, 176 "in template function open finder"); 177 functionOpen++; 178 179 if (functionOpen >= tokens.length) 180 functionOpen = 0; 181 } 182 else 183 { 184 // single template arg (can only be one token) 185 // https://dlang.org/spec/grammar.html#TemplateSingleArgument 186 if (tokens[templateOpen + 2] == tok!"(") 187 functionOpen = templateOpen + 2; 188 } 189 } 190 else 191 return CalltipsSupport.init; // syntax error 192 } 193 else 194 { 195 // go backwards to template arguments 196 if (functionOpen > 0 && tokens[functionOpen - 1].type == tok!")") 197 { 198 // multi template args 199 depth = 0; 200 subDepth = 0; 201 templateOpen = functionOpen - 1; 202 const minTokenIndex = definition ? 1 : 2; 203 while (templateOpen >= minTokenIndex) 204 { 205 const c = tokens[templateOpen]; 206 207 if (c == tok!")") 208 depth++; 209 else 210 { 211 if (depth == 1 && templateOpen > minTokenIndex && c.type == tok!"(") 212 { 213 if (definition 214 ? tokens[templateOpen - 1].type == tok!"identifier" : (tokens[templateOpen - 1].type == tok!"!" 215 && tokens[templateOpen - 2].type == tok!"identifier")) 216 break; 217 } 218 219 if (depth == 0) 220 { 221 templateOpen = 0; 222 break; 223 } 224 225 if (c == tok!"(") 226 depth--; 227 } 228 229 templateOpen--; 230 } 231 232 if (templateOpen < minTokenIndex) 233 templateOpen = 0; 234 else 235 hasTemplateParens = true; 236 } 237 else 238 { 239 // single template arg (can only be one token) or no template at all here 240 if (functionOpen >= 3 && tokens[functionOpen - 2] == tok!"!" 241 && tokens[functionOpen - 3] == tok!"identifier") 242 { 243 templateOpen = functionOpen - 2; 244 } 245 } 246 } 247 248 depth = 0; 249 subDepth = 0; 250 bool inFuncName = true; 251 auto callStart = (templateOpen ? templateOpen : functionOpen) - 1; 252 auto funcNameStart = callStart; 253 while (callStart >= 0) 254 { 255 const c = tokens[callStart]; 256 const p = callStart > 0 ? tokens[callStart - 1] : Token.init; 257 258 if (c.type == tok!"]") 259 depth++; 260 else if (c.type == tok!"[") 261 { 262 if (depth == 0) 263 { 264 // this is some sort of `foo[(4` situation 265 return CalltipsSupport.init; 266 } 267 depth--; 268 } 269 else if (c.type == tok!")") 270 subDepth++; 271 else if (c.type == tok!"(") 272 { 273 if (subDepth == 0) 274 { 275 // this is some sort of `foo((4` situation 276 return CalltipsSupport.init; 277 } 278 subDepth--; 279 } 280 else if (depth == 0) 281 { 282 283 if (c.type.isCalltipable) 284 { 285 if (c.type == tok!"identifier" && p.type == tok!"." && (callStart < 2 286 || !tokens[callStart - 2].type.among!(tok!";", tok!",", 287 tok!"{", tok!"}", tok!"("))) 288 { 289 // member function, traverse further... 290 if (inFuncName) 291 { 292 funcNameStart = callStart; 293 inFuncName = false; 294 } 295 callStart--; 296 } 297 else 298 { 299 break; 300 } 301 } 302 else 303 { 304 // this is some sort of `4(5` or `if(4` situtation 305 return CalltipsSupport.init; 306 } 307 } 308 // we ignore stuff inside brackets and parens such as `foo[4](5).bar[6](a` 309 callStart--; 310 } 311 312 if (inFuncName) 313 funcNameStart = callStart; 314 315 ptrdiff_t templateClose; 316 if (templateOpen) 317 { 318 if (hasTemplateParens) 319 { 320 if (functionOpen) 321 templateClose = functionOpen - 1; 322 else 323 templateClose = findClosingParenForward(tokens, templateOpen, 324 "in template close finder"); 325 } 326 else 327 templateClose = templateOpen + 2; 328 } 329 //dfmt on 330 auto functionClose = functionOpen ? findClosingParenForward(tokens, 331 functionOpen, "in function close finder") : 0; 332 333 CalltipsSupport.Argument[] templateArgs; 334 if (templateOpen) 335 templateArgs = splitArgs(tokens[templateOpen + 1 .. templateClose]); 336 337 CalltipsSupport.Argument[] functionArgs; 338 if (functionOpen) 339 functionArgs = splitArgs(tokens[functionOpen + 1 .. functionClose]); 340 341 return CalltipsSupport([ 342 tokens.tokenIndex(templateOpen), 343 templateClose ? tokens.tokenEndIndex(templateClose) : 0 344 ], hasTemplateParens, templateArgs, [ 345 tokens.tokenIndex(functionOpen), 346 functionClose ? tokens.tokenEndIndex(functionClose) : 0 347 ], functionArgs, funcNameStart != callStart, tokens.tokenIndex(funcNameStart), 348 tokens.tokenIndex(callStart), inTemplate, activeParameter); 349 } 350 351 /// Finds the immediate surrounding code block at a position or returns CodeBlockInfo.init for none/module block. 352 /// See_Also: CodeBlockInfo 353 CodeBlockInfo getCodeBlockRange(scope const(char)[] code, int position) 354 { 355 RollbackAllocator rba; 356 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 357 auto parsed = parseModule(tokens, "getCodeBlockRange_input.d", &rba); 358 auto reader = new CodeBlockInfoFinder(position); 359 reader.visit(parsed); 360 return reader.block; 361 } 362 363 /// Inserts a generic method after the corresponding block inside the scope where position is. 364 /// If it can't find a good spot it will insert the code properly indented ata fitting location. 365 // make public once usable 366 private CodeReplacement[] insertCodeInContainer(string insert, scope const(char)[] code, 367 int position, bool insertInLastBlock = true, bool insertAtEnd = true) 368 { 369 auto container = getCodeBlockRange(code, position); 370 371 scope const(char)[] codeBlock = code[container.innerRange[0] .. container.innerRange[1]]; 372 373 RollbackAllocator rba; 374 scope tokensInsert = getTokensForParser(cast(ubyte[]) insert, config, 375 &workspaced.stringCache); 376 scope parsedInsert = parseModule(tokensInsert, "insertCode_insert.d", &rba); 377 378 scope insertReader = new CodeDefinitionClassifier(insert); 379 insertReader.visit(parsedInsert); 380 scope insertRegions = insertReader.regions.sort!"a.type < b.type".uniq.array; 381 382 scope tokens = getTokensForParser(cast(ubyte[]) codeBlock, config, &workspaced.stringCache); 383 scope parsed = parseModule(tokens, "insertCode_code.d", &rba); 384 385 scope reader = new CodeDefinitionClassifier(codeBlock); 386 reader.visit(parsed); 387 scope regions = reader.regions; 388 389 CodeReplacement[] ret; 390 391 foreach (CodeDefinitionClassifier.Region toInsert; insertRegions) 392 { 393 auto insertCode = insert[toInsert.region[0] .. toInsert.region[1]]; 394 scope existing = regions.enumerate.filter!(a => a.value.sameBlockAs(toInsert)); 395 if (existing.empty) 396 { 397 const checkProtection = CodeRegionProtection.init.reduce!"a | b"( 398 mixableProtection.filter!(a => (a & toInsert.protection) != 0)); 399 400 bool inIncompatible = false; 401 bool lastFit = false; 402 int fittingProtection = -1; 403 int firstStickyProtection = -1; 404 int regionAfterFitting = -1; 405 foreach (i, stickyProtection; regions) 406 { 407 if (stickyProtection.affectsFollowing 408 && stickyProtection.protection != CodeRegionProtection.init) 409 { 410 if (firstStickyProtection == -1) 411 firstStickyProtection = cast(int) i; 412 413 if ((stickyProtection.protection & checkProtection) != 0) 414 { 415 fittingProtection = cast(int) i; 416 lastFit = true; 417 if (!insertInLastBlock) 418 break; 419 } 420 else 421 { 422 if (lastFit) 423 { 424 regionAfterFitting = cast(int) i; 425 lastFit = false; 426 } 427 inIncompatible = true; 428 } 429 } 430 } 431 assert(firstStickyProtection != -1 || !inIncompatible); 432 assert(regionAfterFitting != -1 || fittingProtection == -1 || !inIncompatible); 433 434 if (inIncompatible) 435 { 436 int insertRegion = fittingProtection == -1 ? firstStickyProtection : regionAfterFitting; 437 insertCode = text(indent(insertCode, regions[insertRegion].minIndentation), "\n\n"); 438 auto len = cast(uint) insertCode.length; 439 440 toInsert.region[0] = regions[insertRegion].region[0]; 441 toInsert.region[1] = regions[insertRegion].region[0] + len; 442 foreach (ref r; regions[insertRegion .. $]) 443 { 444 r.region[0] += len; 445 r.region[1] += len; 446 } 447 } 448 else 449 { 450 auto lastRegion = regions.back; 451 insertCode = indent(insertCode, lastRegion.minIndentation).idup; 452 auto len = cast(uint) insertCode.length; 453 toInsert.region[0] = lastRegion.region[1]; 454 toInsert.region[1] = lastRegion.region[1] + len; 455 } 456 regions ~= toInsert; 457 ret ~= CodeReplacement([toInsert.region[0], toInsert.region[0]], insertCode); 458 } 459 else 460 { 461 auto target = insertInLastBlock ? existing.tail(1).front : existing.front; 462 463 insertCode = text("\n\n", indent(insertCode, regions[target.index].minIndentation)); 464 const codeLength = cast(int) insertCode.length; 465 466 if (insertAtEnd) 467 { 468 ret ~= CodeReplacement([ 469 target.value.region[1], target.value.region[1] 470 ], insertCode); 471 toInsert.region[0] = target.value.region[1]; 472 toInsert.region[1] = target.value.region[1] + codeLength; 473 regions[target.index].region[1] = toInsert.region[1]; 474 foreach (ref other; regions[target.index + 1 .. $]) 475 { 476 other.region[0] += codeLength; 477 other.region[1] += codeLength; 478 } 479 } 480 else 481 { 482 ret ~= CodeReplacement([ 483 target.value.region[0], target.value.region[0] 484 ], insertCode); 485 regions[target.index].region[1] += codeLength; 486 foreach (ref other; regions[target.index + 1 .. $]) 487 { 488 other.region[0] += codeLength; 489 other.region[1] += codeLength; 490 } 491 } 492 } 493 } 494 495 return ret; 496 } 497 498 /// Implements the interfaces or abstract classes of a specified class/interface. 499 /// Helper function which returns all functions as one block for most primitive use. 500 Future!string implement(scope const(char)[] code, int position, 501 bool formatCode = true, string[] formatArgs = []) 502 { 503 auto ret = new typeof(return); 504 gthreads.create({ 505 mixin(traceTask); 506 try 507 { 508 auto impl = implementAllSync(code, position, formatCode, formatArgs); 509 510 auto buf = appender!string; 511 string lastBaseClass; 512 foreach (ref func; impl) 513 { 514 if (func.baseClass != lastBaseClass) 515 { 516 buf.put("// implement " ~ func.baseClass ~ "\n\n"); 517 lastBaseClass = func.baseClass; 518 } 519 520 buf.put(func.code); 521 buf.put("\n\n"); 522 } 523 ret.finish(buf.data.length > 2 ? buf.data[0 .. $ - 2] : buf.data); 524 } 525 catch (Throwable t) 526 { 527 ret.error(t); 528 } 529 }); 530 return ret; 531 } 532 533 /// Implements the interfaces or abstract classes of a specified class/interface. 534 /// The async implementation is preferred when used in background tasks to prevent disruption 535 /// of other services as a lot of code is parsed and processed multiple times for this function. 536 /// Params: 537 /// code = input file to parse and edit. 538 /// position = position of the superclass or interface to implement after the colon in a class definition. 539 /// formatCode = automatically calls dfmt on all function bodys when true. 540 /// formatArgs = sets the formatter arguments to pass to dfmt if formatCode is true. 541 /// 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 542 /// Returns: a list of newly implemented methods 543 Future!(ImplementedMethod[]) implementAll(scope const(char)[] code, int position, 544 bool formatCode = true, string[] formatArgs = [], bool snippetExtensions = false) 545 { 546 mixin( 547 gthreadsAsyncProxy!`implementAllSync(code, position, formatCode, formatArgs, snippetExtensions)`); 548 } 549 550 /// ditto 551 ImplementedMethod[] implementAllSync(scope const(char)[] code, int position, 552 bool formatCode = true, string[] formatArgs = [], bool snippetExtensions = false) 553 { 554 auto tree = describeInterfaceRecursiveSync(code, position); 555 auto availableVariables = tree.availableVariables; 556 557 string[] implementedMethods = tree.details 558 .methods 559 .filter!"!a.needsImplementation" 560 .map!"a.identifier" 561 .array; 562 563 int snippetIndex = 0; 564 // maintains snippet ids and their value in an AA so they can be replaced after formatting 565 string[string] snippetReplacements; 566 567 auto methods = appender!(ImplementedMethod[]); 568 void processTree(ref InterfaceTree tree) 569 { 570 auto details = tree.details; 571 if (details.methods.length) 572 { 573 foreach (fn; details.methods) 574 { 575 if (implementedMethods.canFind(fn.identifier)) 576 continue; 577 if (!fn.needsImplementation) 578 { 579 implementedMethods ~= fn.identifier; 580 continue; 581 } 582 583 //dfmt off 584 ImplementedMethod method = { 585 baseClass: details.name, 586 name: fn.name 587 }; 588 //dfmt on 589 auto buf = appender!string; 590 591 snippetIndex++; 592 bool writtenSnippet; 593 string snippetId; 594 auto snippetBuf = appender!string; 595 596 void startSnippet(bool withDefault = true) 597 { 598 if (writtenSnippet || !snippetExtensions) 599 return; 600 snippetId = format!`/+++__WORKSPACED_SNIPPET__%s__+++/`(snippetIndex); 601 buf.put(snippetId); 602 swap(buf, snippetBuf); 603 buf.put("${"); 604 buf.put(snippetIndex.to!string); 605 if (withDefault) 606 buf.put(":"); 607 writtenSnippet = true; 608 } 609 610 void endSnippet() 611 { 612 if (!writtenSnippet || !snippetExtensions) 613 return; 614 buf.put("}"); 615 616 swap(buf, snippetBuf); 617 snippetReplacements[snippetId] = snippetBuf.data; 618 } 619 620 if (details.needsOverride) 621 buf.put("override "); 622 buf.put(fn.signature[0 .. $ - 1]); 623 buf.put(" {"); 624 if (fn.optionalImplementation) 625 { 626 buf.put("\n\t"); 627 startSnippet(); 628 buf.put("// TODO: optional implementation\n"); 629 } 630 631 string propertySearch; 632 if (fn.signature.canFind("@property") && fn.arguments.length <= 1) 633 propertySearch = fn.name; 634 else if ((fn.name.startsWith("get") && fn.arguments.length == 0) 635 || (fn.name.startsWith("set") && fn.arguments.length == 1)) 636 propertySearch = fn.name[3 .. $]; 637 638 string foundProperty; 639 if (propertySearch) 640 { 641 // frontOrDefault 642 const matching = availableVariables.find!(a => fieldNameMatches(a.name, 643 propertySearch)); 644 if (!matching.empty) 645 foundProperty = matching.front.name; 646 } 647 648 if (foundProperty.length) 649 { 650 method.autoProperty = true; 651 buf.put("\n\t"); 652 startSnippet(); 653 if (fn.returnType != "void") 654 { 655 method.getter = true; 656 buf.put("return "); 657 } 658 659 if (fn.name.startsWith("set") || fn.arguments.length == 1) 660 { 661 method.setter = true; 662 buf.put(foundProperty ~ " = " ~ fn.arguments[0].name); 663 } 664 else 665 { 666 // neither getter nor setter, but we will just put the property here anyway 667 buf.put(foundProperty); 668 } 669 buf.put(";"); 670 endSnippet(); 671 buf.put("\n"); 672 } 673 else if (fn.hasBody) 674 { 675 method.callsSuper = true; 676 buf.put("\n\t"); 677 startSnippet(); 678 if (fn.returnType != "void") 679 buf.put("return "); 680 buf.put("super." ~ fn.name); 681 if (fn.arguments.length) 682 buf.put("(" ~ format("%(%s, %)", fn.arguments) 683 .translate(['\\': `\\`, '{': `\{`, '$': `\$`, '}': `\}`]) ~ ")"); 684 else if (fn.returnType == "void") 685 buf.put("()"); // make functions that don't return add (), otherwise they might be attributes and don't need that 686 buf.put(";"); 687 endSnippet(); 688 buf.put("\n"); 689 } 690 else if (fn.returnType != "void") 691 { 692 method.debugImpl = true; 693 buf.put("\n\t"); 694 if (snippetExtensions) 695 { 696 startSnippet(false); 697 buf.put('|'); 698 // choice snippet 699 700 if (fn.returnType.endsWith("[]")) 701 buf.put("return null; // TODO: implement"); 702 else 703 buf.put("return " ~ fn.returnType.translate([ 704 '\\': `\\`, 705 '{': `\{`, 706 '$': `\$`, 707 '}': `\}`, 708 '|': `\|`, 709 ',': `\,` 710 ]) ~ ".init; // TODO: implement"); 711 712 buf.put(','); 713 714 buf.put(`assert(false\, "Method ` ~ fn.name ~ ` not implemented");`); 715 716 buf.put('|'); 717 endSnippet(); 718 } 719 else 720 { 721 if (fn.isNothrowOrNogc) 722 { 723 if (fn.returnType.endsWith("[]")) 724 buf.put("return null; // TODO: implement"); 725 else 726 buf.put("return " ~ fn.returnType.translate([ 727 '\\': `\\`, 728 '{': `\{`, 729 '$': `\$`, 730 '}': `\}` 731 ]) ~ ".init; // TODO: implement"); 732 } 733 else 734 buf.put(`assert(false, "Method ` ~ fn.name ~ ` not implemented");`); 735 } 736 buf.put("\n"); 737 } 738 else if (snippetExtensions) 739 { 740 buf.put("\n\t"); 741 startSnippet(false); 742 endSnippet(); 743 buf.put("\n"); 744 } 745 746 buf.put("}"); 747 748 method.code = buf.data; 749 methods.put(method); 750 } 751 } 752 753 foreach (parent; tree.inherits) 754 processTree(parent); 755 } 756 757 processTree(tree); 758 759 if (formatCode && instance.has!DfmtComponent) 760 { 761 foreach (ref method; methods.data) 762 method.code = instance.get!DfmtComponent.formatSync(method.code, formatArgs).strip; 763 } 764 765 foreach (ref method; methods.data) 766 { 767 // TODO: replacing using aho-corasick would be far more efficient but there is nothing like that in phobos 768 foreach (key, value; snippetReplacements) 769 { 770 method.code = method.code.replace(key, value); 771 } 772 } 773 774 return methods.data; 775 } 776 777 /// Looks up a declaration of a type and then extracts information about it as class or interface. 778 InterfaceDetails lookupInterface(scope const(char)[] code, int position) 779 { 780 auto data = get!DCDComponent.findDeclaration(code, position).getBlocking; 781 string file = data.file; 782 int newPosition = data.position; 783 784 if (!file.length || !newPosition) 785 return InterfaceDetails.init; 786 787 auto newCode = code; 788 if (file != "stdin") 789 newCode = readText(file); 790 791 return getInterfaceDetails(file, newCode, newPosition); 792 } 793 794 /// Extracts information about a given class or interface at the given position. 795 InterfaceDetails getInterfaceDetails(string file, scope const(char)[] code, int position) 796 { 797 RollbackAllocator rba; 798 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 799 auto parsed = parseModule(tokens, file, &rba); 800 auto reader = new InterfaceMethodFinder(code, position); 801 reader.visit(parsed); 802 return reader.details; 803 } 804 805 Future!InterfaceTree describeInterfaceRecursive(scope const(char)[] code, int position) 806 { 807 mixin(gthreadsAsyncProxy!`describeInterfaceRecursiveSync(code, position)`); 808 } 809 810 InterfaceTree describeInterfaceRecursiveSync(scope const(char)[] code, int position) 811 { 812 auto baseInterface = getInterfaceDetails("stdin", code, position); 813 814 InterfaceTree tree = InterfaceTree(baseInterface); 815 816 InterfaceTree* treeByName(InterfaceTree* tree, string name) 817 { 818 if (tree.details.name == name) 819 return tree; 820 foreach (ref parent; tree.inherits) 821 { 822 InterfaceTree* t = treeByName(&parent, name); 823 if (t !is null) 824 return t; 825 } 826 return null; 827 } 828 829 void traverseTree(ref InterfaceTree sub) 830 { 831 foreach (i, parent; sub.details.parentPositions) 832 { 833 string parentName = sub.details.normalizedParents[i]; 834 if (treeByName(&tree, parentName) is null) 835 { 836 auto details = lookupInterface(sub.details.code, parent); 837 details.name = parentName; 838 sub.inherits ~= InterfaceTree(details); 839 } 840 } 841 foreach (ref inherit; sub.inherits) 842 traverseTree(inherit); 843 } 844 845 traverseTree(tree); 846 847 return tree; 848 } 849 850 Related[] highlightRelated(scope const(char)[] code, int position) 851 { 852 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 853 if (!tokens.length) 854 return null; 855 auto token = tokens.tokenIndexAtByteIndex(position); 856 if (token >= tokens.length || !tokens[token].isLikeIdentifier) 857 return null; 858 859 Related[] ret; 860 861 switch (tokens[token].type) 862 { 863 case tok!"static": 864 if (token + 1 < tokens.length) 865 { 866 if (tokens[token + 1].type == tok!"if") 867 { 868 token++; 869 goto case tok!"if"; 870 } 871 else if (tokens[token + 1].type == tok!"foreach" || tokens[token + 1].type == tok!"foreach_reverse") 872 { 873 token++; 874 goto case tok!"for"; 875 } 876 } 877 goto default; 878 case tok!"if": 879 case tok!"else": 880 // if lister 881 auto finder = new IfFinder(); 882 finder.target = tokens[token].index; 883 RollbackAllocator rba; 884 auto parsed = parseModule(tokens, "stdin", &rba); 885 finder.visit(parsed); 886 foreach (ifToken; finder.foundIf) 887 ret ~= Related(Related.Type.controlFlow, [ifToken.index, ifToken.index + ifToken.tokenText.length]); 888 break; 889 case tok!"for": 890 case tok!"foreach": 891 case tok!"foreach_reverse": 892 case tok!"while": 893 case tok!"do": 894 case tok!"break": 895 case tok!"continue": 896 // loop and switch matcher 897 // special case for switch 898 auto finder = new BreakFinder(); 899 finder.target = tokens[token].index; 900 finder.isBreak = tokens[token].type == tok!"break"; 901 finder.isLoop = !(tokens[token].type == tok!"break" || tokens[token].type == tok!"continue"); 902 if (token + 1 < tokens.length && tokens[token + 1].type == tok!"identifier") 903 finder.label = tokens[token + 1].text; 904 RollbackAllocator rba; 905 auto parsed = parseModule(tokens, "stdin", &rba); 906 finder.visit(parsed); 907 908 if (finder.isLoop && finder.foundBlock.length) 909 { 910 auto retFinder = new ReverseReturnFinder(); 911 retFinder.target = finder.target; 912 retFinder.visit(parsed); 913 finder.foundBlock ~= retFinder.returns; 914 finder.foundBlock.sort!"a.index < b.index"; 915 } 916 917 foreach (blockToken; finder.foundBlock) 918 ret ~= Related(Related.Type.controlFlow, [blockToken.index, blockToken.index + blockToken.tokenText.length]); 919 break; 920 case tok!"switch": 921 case tok!"case": 922 case tok!"default": 923 // switch/case lister 924 auto finder = new SwitchFinder(); 925 finder.target = tokens[token].index; 926 RollbackAllocator rba; 927 auto parsed = parseModule(tokens, "stdin", &rba); 928 finder.visit(parsed); 929 foreach (switchToken; finder.foundSwitch) 930 ret ~= Related(Related.Type.controlFlow, [switchToken.index, switchToken.index + switchToken.tokenText.length]); 931 break; 932 case tok!"return": 933 // return effect lister 934 auto finder = new ReturnFinder(); 935 finder.target = tokens[token].index; 936 RollbackAllocator rba; 937 auto parsed = parseModule(tokens, "stdin", &rba); 938 finder.visit(parsed); 939 foreach (switchToken; finder.related) 940 ret ~= Related(Related.Type.controlFlow, [switchToken.index, switchToken.index + switchToken.tokenText.length]); 941 break; 942 default: 943 // exact token / string matcher 944 auto currentText = tokens[token].tokenText; 945 foreach (i, tok; tokens) 946 { 947 if (tok.type == tokens[token].type && tok.text == tokens[token].text) 948 ret ~= Related(Related.Type.exactToken, [tok.index, tok.index + currentText.length]); 949 else if (tok.type.isSomeString && tok.evaluateExpressionString == currentText) 950 ret ~= Related(Related.Type.exactString, [tok.index, tok.index + tok.text.length]); 951 } 952 break; 953 } 954 955 return ret; 956 } 957 958 /// Formats DCD definitions (symbol declarations) in a readable format. 959 /// For functions this formats each argument in a separate line. 960 /// For other symbols the definition is returned as-is. 961 string formatDefinitionBlock(string definition) 962 { 963 // DCD definition help contains calltips for functions, which always end 964 // with ) 965 if (!definition.endsWith(")")) 966 return definition; 967 968 auto tokens = getTokensForParser(cast(const(ubyte)[]) definition ~ ';', 969 config, &workspaced.stringCache); 970 if (!tokens.length) 971 return definition; 972 973 RollbackAllocator rba; 974 auto parser = new Parser(); 975 parser.fileName = "stdin"; 976 parser.tokens = tokens; 977 parser.messageFunction = null; 978 parser.messageDelegate = null; 979 parser.allocator = &rba; 980 const Declaration decl = parser.parseDeclaration( 981 false, // strict 982 true // must be declaration (for constructor) 983 ); 984 if (!decl) 985 return definition; 986 987 const FunctionDeclaration funcdecl = decl.functionDeclaration; 988 const Constructor ctor = decl.constructor; 989 if (!funcdecl && !ctor) 990 return definition; 991 992 auto ret = appender!string(); 993 ret.reserve(definition.length); 994 995 if (funcdecl) 996 ret.put(definition[0 .. funcdecl.name.index + funcdecl.name.text.length]); 997 else if (ctor) 998 ret.put("this"); 999 1000 const templateParameters = funcdecl ? funcdecl.templateParameters : ctor.templateParameters; 1001 if (templateParameters && templateParameters.templateParameterList) 1002 { 1003 const params = templateParameters.templateParameterList.items; 1004 ret.put("(\n"); 1005 foreach (i, param; params) 1006 { 1007 assert(param.tokens.length, "no tokens for template parameter?!"); 1008 const start = param.tokens[0].index; 1009 const end = param.tokens[$ - 1].index + tokenText(param.tokens[$ - 1]).length; 1010 const hasNext = i + 1 < params.length; 1011 ret.put("\t"); 1012 ret.put(definition[start .. end]); 1013 if (hasNext) 1014 ret.put(","); 1015 ret.put("\n"); 1016 } 1017 ret.put(")"); 1018 } 1019 1020 const parameters = funcdecl ? funcdecl.parameters : ctor.parameters; 1021 if (parameters && (parameters.parameters.length || parameters.hasVarargs)) 1022 { 1023 const params = parameters.parameters; 1024 ret.put("(\n"); 1025 foreach (i, param; params) 1026 { 1027 assert(param.tokens.length, "no tokens for parameter?!"); 1028 const start = param.tokens[0].index; 1029 const end = param.tokens[$ - 1].index + tokenText(param.tokens[$ - 1]).length; 1030 const hasNext = parameters.hasVarargs || i + 1 < params.length; 1031 ret.put("\t"); 1032 ret.put(definition[start .. end]); 1033 if (hasNext) 1034 ret.put(","); 1035 ret.put("\n"); 1036 } 1037 if (parameters.hasVarargs) 1038 ret.put("\t...\n"); 1039 ret.put(")"); 1040 } 1041 else 1042 { 1043 ret.put("()"); 1044 } 1045 1046 return ret.data; 1047 } 1048 1049 private: 1050 LexerConfig config; 1051 } 1052 1053 /// 1054 enum CodeRegionType : int 1055 { 1056 /// null region (unset) 1057 init, 1058 /// Imports inside the block 1059 imports = 1 << 0, 1060 /// Aliases `alias foo this;`, `alias Type = Other;` 1061 aliases = 1 << 1, 1062 /// Nested classes/structs/unions/etc. 1063 types = 1 << 2, 1064 /// Raw variables `Type name;` 1065 fields = 1 << 3, 1066 /// Normal constructors `this(Args args)` 1067 ctor = 1 << 4, 1068 /// Copy constructors `this(this)` 1069 copyctor = 1 << 5, 1070 /// Destructors `~this()` 1071 dtor = 1 << 6, 1072 /// Properties (functions annotated with `@property`) 1073 properties = 1 << 7, 1074 /// Regular functions 1075 methods = 1 << 8, 1076 } 1077 1078 /// 1079 enum CodeRegionProtection : int 1080 { 1081 /// null protection (unset) 1082 init, 1083 /// default (unmarked) protection 1084 default_ = 1 << 0, 1085 /// public protection 1086 public_ = 1 << 1, 1087 /// package (automatic) protection 1088 package_ = 1 << 2, 1089 /// package (manual package name) protection 1090 packageIdentifier = 1 << 3, 1091 /// protected protection 1092 protected_ = 1 << 4, 1093 /// private protection 1094 private_ = 1 << 5, 1095 } 1096 1097 /// 1098 enum CodeRegionStatic : int 1099 { 1100 /// null static (unset) 1101 init, 1102 /// non-static code 1103 instanced = 1 << 0, 1104 /// static code 1105 static_ = 1 << 1, 1106 } 1107 1108 /// Represents a class/interface/struct/union/template with body. 1109 struct CodeBlockInfo 1110 { 1111 /// 1112 enum Type : int 1113 { 1114 // keep the underlines in these values for range checking properly 1115 1116 /// 1117 class_, 1118 /// 1119 interface_, 1120 /// 1121 struct_, 1122 /// 1123 union_, 1124 /// 1125 template_, 1126 } 1127 1128 static immutable string[] typePrefixes = [ 1129 "class ", "interface ", "struct ", "union ", "template " 1130 ]; 1131 1132 /// 1133 Type type; 1134 /// 1135 string name; 1136 /// Outer range inside the code spanning curly braces and name but not type keyword. 1137 uint[2] outerRange; 1138 /// Inner range of body of the block touching, but not spanning curly braces. 1139 uint[2] innerRange; 1140 1141 string prefix() @property 1142 { 1143 return typePrefixes[cast(int) type]; 1144 } 1145 } 1146 1147 /// 1148 struct CalltipsSupport 1149 { 1150 /// 1151 struct Argument 1152 { 1153 /// 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. 1154 int[2] contentRange; 1155 /// Range of just the type, or for templates also `alias` 1156 int[2] typeRange; 1157 /// Range of just the name 1158 int[2] nameRange; 1159 /// Range of just the default value 1160 int[2] valueRange; 1161 /// True if the type declaration is variadic (using ...), or without typeRange a completely variadic argument 1162 bool variadic; 1163 1164 /// Creates Argument(range, range, range, 0) 1165 static Argument templateType(int[2] range) 1166 { 1167 return Argument(range, range, range); 1168 } 1169 1170 /// Creates Argument(range, 0, range, range) 1171 static Argument templateValue(int[2] range) 1172 { 1173 return Argument(range, typeof(range).init, range, range); 1174 } 1175 1176 /// Creates Argument(range, 0, 0, 0, true) 1177 static Argument anyVariadic(int[2] range) 1178 { 1179 return Argument(range, typeof(range).init, typeof(range).init, typeof(range).init, true); 1180 } 1181 } 1182 1183 bool hasTemplate() @property 1184 { 1185 return hasTemplateParens || templateArgumentsRange != typeof(templateArgumentsRange).init; 1186 } 1187 1188 /// Range starting before exclamation point until after closing bracket or before function opening bracket. 1189 int[2] templateArgumentsRange; 1190 /// 1191 bool hasTemplateParens; 1192 /// 1193 Argument[] templateArgs; 1194 /// Range starting before opening parentheses until after closing parentheses. 1195 int[2] functionParensRange; 1196 /// 1197 Argument[] functionArgs; 1198 /// True if the function is UFCS or a member function of some object or namespace. 1199 /// False if this is a global function call. 1200 bool hasParent; 1201 /// Start of the function itself. 1202 int functionStart; 1203 /// Start of the whole call going up all call parents. (`foo.bar.function` having `foo.bar` as parents) 1204 int parentStart; 1205 /// True if cursor is in template parameters 1206 bool inTemplateParameters; 1207 /// Number of the active parameter (where the cursor is) or -1 if in none 1208 int activeParameter = -1; 1209 } 1210 1211 /// Represents one method automatically implemented off a base interface. 1212 struct ImplementedMethod 1213 { 1214 /// Contains the interface or class name from where this method is implemented. 1215 string baseClass; 1216 /// The name of the function being implemented. 1217 string name; 1218 /// True if an automatic implementation calling the base class has been made. 1219 bool callsSuper; 1220 /// True if a default implementation that should definitely be changed (assert or for nogc/nothrow simple init return) has been implemented. 1221 bool debugImpl; 1222 /// True if the method has been detected as property and implemented as such. 1223 bool autoProperty; 1224 /// 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. 1225 bool getter, setter; 1226 /// Actual code to insert for this class without class indentation but optionally already formatted. 1227 string code; 1228 } 1229 1230 /// Contains details about an interface or class and all extended or implemented interfaces/classes recursively. 1231 struct InterfaceTree 1232 { 1233 /// Details of the template in question. 1234 InterfaceDetails details; 1235 /// All inherited classes in lexical order. 1236 InterfaceTree[] inherits; 1237 1238 @SerializeIgnore const(FieldDetails)[] availableVariables(bool onlyPublic = false) const 1239 { 1240 if (!inherits.length && !onlyPublic) 1241 return details.fields; 1242 1243 // start with private, add all the public ones later in traverseTree 1244 auto ret = appender!(typeof(return)); 1245 if (onlyPublic) 1246 ret.put(details.fields.filter!(a => !a.isPrivate)); 1247 else 1248 ret.put(details.fields); 1249 1250 foreach (sub; inherits) 1251 ret.put(sub.availableVariables(true)); 1252 1253 return ret.data; 1254 } 1255 } 1256 1257 /// Represents one selection for things related to the queried cursor position. 1258 struct Related 1259 { 1260 /// 1261 enum Type 1262 { 1263 /// token is the same as the selected token (except for non-text tokens) 1264 exactToken, 1265 /// string content is exactly equal to identifier text 1266 exactString, 1267 /// token is related to control flow: 1268 /// - all if/else keywords when checking any of them 1269 /// - loop/switch keyword when checking a break/continue 1270 controlFlow 1271 } 1272 1273 /// The type of the related selection. 1274 Type type; 1275 /// Byte range [from-inclusive, to-exclusive] of the related selection. 1276 size_t[2] range; 1277 } 1278 1279 private: 1280 1281 bool isCalltipable(IdType type) 1282 { 1283 return type == tok!"identifier" || type == tok!"assert" || type == tok!"import" 1284 || type == tok!"mixin" || type == tok!"super" || type == tok!"this" || type == tok!"__traits"; 1285 } 1286 1287 int[2] tokenRange(const Token token) 1288 { 1289 return [cast(int) token.index, cast(int)(token.index + token.tokenText.length)]; 1290 } 1291 1292 int tokenEnd(const Token token) 1293 { 1294 return cast(int)(token.index + token.tokenText.length); 1295 } 1296 1297 int tokenIndex(const(Token)[] tokens, ptrdiff_t i) 1298 { 1299 if (i > 0 && i == tokens.length) 1300 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].tokenText.length); 1301 return i >= 0 ? cast(int) tokens[i].index : 0; 1302 } 1303 1304 int tokenEndIndex(const(Token)[] tokens, ptrdiff_t i) 1305 { 1306 if (i > 0 && i == tokens.length) 1307 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length); 1308 return i >= 0 ? cast(int)(tokens[i].index + tokens[i].tokenText.length) : 0; 1309 } 1310 1311 /// Returns the index of the closing parentheses in tokens starting at the opening parentheses which is must be at tokens[open]. 1312 ptrdiff_t findClosingParenForward(const(Token)[] tokens, ptrdiff_t open, string what = null) 1313 in(tokens[open].type == tok!"(", 1314 "Calling findClosingParenForward must be done on a ( token and not on a " ~ str( 1315 tokens[open].type) ~ " token! " ~ what) 1316 { 1317 if (open >= tokens.length || open < 0) 1318 return open; 1319 1320 open++; 1321 1322 int depth = 1; 1323 int subDepth = 0; 1324 while (open < tokens.length) 1325 { 1326 const c = tokens[open]; 1327 1328 if (c == tok!"(") 1329 depth++; 1330 else if (c == tok!"{") 1331 subDepth++; 1332 else if (c == tok!"}") 1333 { 1334 if (subDepth == 0) 1335 break; 1336 subDepth--; 1337 } 1338 else 1339 { 1340 if (c == tok!";" && subDepth == 0) 1341 break; 1342 1343 if (c == tok!")") 1344 depth--; 1345 1346 if (depth == 0) 1347 break; 1348 } 1349 1350 open++; 1351 } 1352 return open; 1353 } 1354 1355 CalltipsSupport.Argument[] splitArgs(const(Token)[] tokens) 1356 { 1357 auto ret = appender!(CalltipsSupport.Argument[]); 1358 size_t start = 0; 1359 size_t valueStart = 0; 1360 1361 int depth, subDepth; 1362 const targetDepth = tokens.length > 0 && tokens[0].type == tok!"(" ? 1 : 0; 1363 bool gotValue; 1364 1365 void putArg(size_t end) 1366 { 1367 if (start >= end || start >= tokens.length) 1368 return; 1369 1370 CalltipsSupport.Argument arg; 1371 1372 auto typename = tokens[start .. end]; 1373 arg.contentRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1374 if (typename.length == 1) 1375 { 1376 auto t = typename[0]; 1377 if (t.type == tok!"identifier" || t.type.isBasicType) 1378 arg = CalltipsSupport.Argument.templateType(t.tokenRange); 1379 else if (t.type == tok!"...") 1380 arg = CalltipsSupport.Argument.anyVariadic(t.tokenRange); 1381 else 1382 arg = CalltipsSupport.Argument.templateValue(t.tokenRange); 1383 } 1384 else 1385 { 1386 if (gotValue && valueStart > start && valueStart <= end) 1387 { 1388 typename = tokens[start .. valueStart]; 1389 auto val = tokens[valueStart .. end]; 1390 if (val.length) 1391 arg.valueRange = [cast(int) val[0].index, val[$ - 1].tokenEnd]; 1392 } 1393 1394 else if (typename.length == 1) 1395 { 1396 auto t = typename[0]; 1397 if (t.type == tok!"identifier" || t.type.isBasicType) 1398 arg.typeRange = arg.nameRange = t.tokenRange; 1399 else 1400 arg.typeRange = t.tokenRange; 1401 } 1402 else if (typename.length) 1403 { 1404 if (typename[$ - 1].type == tok!"identifier") 1405 { 1406 arg.nameRange = typename[$ - 1].tokenRange; 1407 typename = typename[0 .. $ - 1]; 1408 } 1409 else if (typename[$ - 1].type == tok!"...") 1410 { 1411 arg.variadic = true; 1412 if (typename.length > 1 && typename[$ - 2].type == tok!"identifier") 1413 { 1414 arg.nameRange = typename[$ - 2].tokenRange; 1415 typename = typename[0 .. $ - 2]; 1416 } 1417 else 1418 typename = typename[0 .. 0]; 1419 } 1420 1421 if (typename.length) 1422 arg.typeRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 1423 } 1424 } 1425 1426 ret.put(arg); 1427 1428 gotValue = false; 1429 start = end + 1; 1430 } 1431 1432 foreach (i, token; tokens) 1433 { 1434 if (token.type == tok!"{") 1435 subDepth++; 1436 else if (token.type == tok!"}") 1437 { 1438 if (subDepth == 0) 1439 break; 1440 subDepth--; 1441 } 1442 else if (token.type == tok!"(" || token.type == tok!"[") 1443 depth++; 1444 else if (token.type == tok!")" || token.type == tok!"]") 1445 { 1446 if (depth <= targetDepth) 1447 break; 1448 depth--; 1449 } 1450 1451 if (depth == targetDepth) 1452 { 1453 if (token.type == tok!",") 1454 putArg(i); 1455 else if (token.type == tok!":" || token.type == tok!"=") 1456 { 1457 if (!gotValue) 1458 { 1459 valueStart = i + 1; 1460 gotValue = true; 1461 } 1462 } 1463 } 1464 } 1465 putArg(tokens.length); 1466 1467 return ret.data; 1468 } 1469 1470 auto indent(scope const(char)[] code, string indentation) 1471 { 1472 return code.lineSplitter!(KeepTerminator.yes) 1473 .map!(a => a.length ? indentation ~ a : a) 1474 .join; 1475 } 1476 1477 bool fieldNameMatches(string field, in char[] expected) 1478 { 1479 import std.uni : sicmp; 1480 1481 if (field.startsWith("_")) 1482 field = field[1 .. $]; 1483 else if (field.startsWith("m_")) 1484 field = field[2 .. $]; 1485 else if (field.length >= 2 && field[0] == 'm' && field[1].isUpper) 1486 field = field[1 .. $]; 1487 1488 return field.sicmp(expected) == 0; 1489 } 1490 1491 final class CodeBlockInfoFinder : ASTVisitor 1492 { 1493 this(int targetPosition) 1494 { 1495 this.targetPosition = targetPosition; 1496 } 1497 1498 override void visit(const ClassDeclaration dec) 1499 { 1500 visitContainer(dec.name, CodeBlockInfo.Type.class_, dec.structBody); 1501 } 1502 1503 override void visit(const InterfaceDeclaration dec) 1504 { 1505 visitContainer(dec.name, CodeBlockInfo.Type.interface_, dec.structBody); 1506 } 1507 1508 override void visit(const StructDeclaration dec) 1509 { 1510 visitContainer(dec.name, CodeBlockInfo.Type.struct_, dec.structBody); 1511 } 1512 1513 override void visit(const UnionDeclaration dec) 1514 { 1515 visitContainer(dec.name, CodeBlockInfo.Type.union_, dec.structBody); 1516 } 1517 1518 override void visit(const TemplateDeclaration dec) 1519 { 1520 if (cast(int) targetPosition >= cast(int) dec.name.index && targetPosition < dec.endLocation) 1521 { 1522 block = CodeBlockInfo.init; 1523 block.type = CodeBlockInfo.Type.template_; 1524 block.name = dec.name.text; 1525 block.outerRange = [ 1526 cast(uint) dec.name.index, cast(uint) dec.endLocation + 1 1527 ]; 1528 block.innerRange = [ 1529 cast(uint) dec.startLocation + 1, cast(uint) dec.endLocation 1530 ]; 1531 dec.accept(this); 1532 } 1533 } 1534 1535 private void visitContainer(const Token name, CodeBlockInfo.Type type, const StructBody structBody) 1536 { 1537 if (!structBody) 1538 return; 1539 if (cast(int) targetPosition >= cast(int) name.index && targetPosition < structBody.endLocation) 1540 { 1541 block = CodeBlockInfo.init; 1542 block.type = type; 1543 block.name = name.text; 1544 block.outerRange = [ 1545 cast(uint) name.index, cast(uint) structBody.endLocation + 1 1546 ]; 1547 block.innerRange = [ 1548 cast(uint) structBody.startLocation + 1, cast(uint) structBody.endLocation 1549 ]; 1550 structBody.accept(this); 1551 } 1552 } 1553 1554 alias visit = ASTVisitor.visit; 1555 1556 CodeBlockInfo block; 1557 int targetPosition; 1558 } 1559 1560 version (unittest) static immutable string SimpleClassTestCode = q{ 1561 module foo; 1562 1563 class FooBar 1564 { 1565 public: 1566 int i; // default instanced fields 1567 string s; 1568 long l; 1569 1570 public this() // public instanced ctor 1571 { 1572 i = 4; 1573 } 1574 1575 protected: 1576 int x; // protected instanced field 1577 1578 private: 1579 static const int foo() @nogc nothrow pure @system // private static methods 1580 { 1581 if (s == "a") 1582 { 1583 i = 5; 1584 } 1585 } 1586 1587 static void bar1() {} 1588 1589 void bar2() {} // private instanced methods 1590 void bar3() {} 1591 1592 struct Something { string bar; } 1593 1594 FooBar.Something somefunc() { return Something.init; } 1595 Something somefunc2() { return Something.init; } 1596 }}; 1597 1598 unittest 1599 { 1600 scope backend = new WorkspaceD(); 1601 auto workspace = makeTemporaryTestingWorkspace; 1602 auto instance = backend.addInstance(workspace.directory); 1603 backend.register!DCDExtComponent; 1604 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1605 1606 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 123) == CodeBlockInfo(CodeBlockInfo.Type.class_, 1607 "FooBar", [20, SimpleClassTestCode.length], [ 1608 28, SimpleClassTestCode.length - 1 1609 ])); 1610 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 19) == CodeBlockInfo.init); 1611 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 20) != CodeBlockInfo.init); 1612 1613 auto replacements = dcdext.insertCodeInContainer("void foo()\n{\n\twriteln();\n}", 1614 SimpleClassTestCode, 123); 1615 1616 // TODO: make insertCodeInContainer work properly? 1617 } 1618 1619 unittest 1620 { 1621 import std.conv; 1622 1623 scope backend = new WorkspaceD(); 1624 auto workspace = makeTemporaryTestingWorkspace; 1625 auto instance = backend.addInstance(workspace.directory); 1626 backend.register!DCDExtComponent; 1627 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1628 1629 auto extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg\n\nfoo(); int y;", 23); 1630 assert(!extract.hasTemplate); 1631 assert(extract.parentStart == 7); 1632 assert(extract.functionStart == 11); 1633 assert(extract.functionParensRange[0] == 14); 1634 assert(extract.functionParensRange[1] <= 31); 1635 assert(extract.functionArgs.length == 2); 1636 assert(extract.functionArgs[0].contentRange == [15, 16]); 1637 assert(extract.functionArgs[1].contentRange[0] == 18); 1638 assert(extract.functionArgs[1].contentRange[1] <= 31); 1639 1640 extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg)\n\nfoo(); int y;", 23); 1641 assert(!extract.hasTemplate); 1642 assert(extract.parentStart == 7); 1643 assert(extract.functionStart == 11); 1644 assert(extract.functionParensRange == [14, 24]); 1645 assert(extract.functionArgs.length == 2); 1646 assert(extract.functionArgs[0].contentRange == [15, 16]); 1647 assert(extract.functionArgs[1].contentRange == [18, 23]); 1648 1649 extract = dcdext.extractCallParameters("void foo()", 9, true); 1650 assert(extract != CalltipsSupport.init); 1651 extract = dcdext.extractCallParameters("void foo()", 10, true); 1652 assert(extract == CalltipsSupport.init); 1653 1654 // caused segfault once, doesn't return anything important 1655 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!")")`, 1656 140, true); 1657 assert(extract == CalltipsSupport.init); 1658 1659 extract = dcdext.extractCallParameters( 1660 `auto bar(int foo, Button, my.Callback cb, ..., int[] arr ...)`, 60, true); 1661 assert(extract != CalltipsSupport.init); 1662 assert(!extract.hasTemplate); 1663 assert(!extract.inTemplateParameters); 1664 assert(extract.activeParameter == 4); 1665 assert(extract.functionStart == 5); 1666 assert(extract.parentStart == 5); 1667 assert(extract.functionParensRange == [8, 61]); 1668 assert(extract.functionArgs.length == 5); 1669 assert(extract.functionArgs[0].contentRange == [9, 16]); 1670 assert(extract.functionArgs[0].typeRange == [9, 12]); 1671 assert(extract.functionArgs[0].nameRange == [13, 16]); 1672 assert(extract.functionArgs[1].contentRange == [18, 24]); 1673 assert(extract.functionArgs[1].typeRange == [18, 24]); 1674 assert(extract.functionArgs[1].nameRange == [18, 24]); 1675 assert(extract.functionArgs[2].contentRange == [26, 40]); 1676 assert(extract.functionArgs[2].typeRange == [26, 37]); 1677 assert(extract.functionArgs[2].nameRange == [38, 40]); 1678 assert(extract.functionArgs[3].contentRange == [42, 45]); 1679 assert(extract.functionArgs[3].variadic); 1680 assert(extract.functionArgs[4].contentRange == [47, 60]); 1681 assert(extract.functionArgs[4].typeRange == [47, 52]); 1682 assert(extract.functionArgs[4].nameRange == [53, 56]); 1683 assert(extract.functionArgs[4].variadic); 1684 1685 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!")")}, 1686 150, true); 1687 assert(extract != CalltipsSupport.init); 1688 assert(extract.hasTemplate); 1689 assert(extract.templateArgumentsRange == [26, 38]); 1690 assert(extract.templateArgs.length == 2); 1691 assert(extract.templateArgs[0].contentRange == [27, 28]); 1692 assert(extract.templateArgs[0].nameRange == [27, 28]); 1693 assert(extract.templateArgs[1].contentRange == [30, 37]); 1694 assert(extract.templateArgs[1].nameRange == [30, 34]); 1695 assert(extract.functionStart == 23); 1696 assert(extract.parentStart == 23); 1697 assert(extract.functionParensRange == [38, 151]); 1698 assert(extract.functionArgs.length == 7); 1699 assert(extract.functionArgs[0].contentRange == [39, 42]); 1700 assert(extract.functionArgs[0].typeRange == [39, 40]); 1701 assert(extract.functionArgs[0].nameRange == [41, 42]); 1702 assert(extract.functionArgs[1].contentRange == [44, 47]); 1703 assert(extract.functionArgs[1].typeRange == [44, 45]); 1704 assert(extract.functionArgs[1].nameRange == [46, 47]); 1705 assert(extract.functionArgs[2].contentRange == [49, 67]); 1706 assert(extract.functionArgs[2].typeRange == [49, 63]); 1707 assert(extract.functionArgs[2].nameRange == [64, 67]); 1708 assert(extract.functionArgs[3].contentRange == [69, 85]); 1709 assert(extract.functionArgs[3].typeRange == [69, 78]); 1710 assert(extract.functionArgs[3].nameRange == [79, 85]); 1711 assert(extract.functionArgs[4].contentRange == [87, 122]); 1712 assert(extract.functionArgs[4].typeRange == [87, 115]); 1713 assert(extract.functionArgs[4].nameRange == [116, 122]); 1714 assert(extract.functionArgs[5].contentRange == [124, 139]); 1715 assert(extract.functionArgs[5].typeRange == [124, 133]); 1716 assert(extract.functionArgs[5].nameRange == [134, 139]); 1717 assert(extract.functionArgs[6].contentRange == [141, 150]); 1718 assert(extract.functionArgs[6].typeRange == [141, 150]); 1719 1720 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4`, 44); 1721 assert(extract != CalltipsSupport.init); 1722 assert(!extract.hasTemplate); 1723 assert(extract.activeParameter == 0); 1724 assert(extract.functionStart == 34); 1725 assert(extract.parentStart == 34); 1726 assert(extract.functionArgs.length == 1); 1727 assert(extract.functionArgs[0].contentRange == [43, 44]); 1728 1729 extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4, f(4)`, 50); 1730 assert(extract != CalltipsSupport.init); 1731 assert(!extract.hasTemplate); 1732 assert(extract.activeParameter == 1); 1733 assert(extract.functionStart == 34); 1734 assert(extract.parentStart == 34); 1735 assert(extract.functionArgs.length == 2); 1736 assert(extract.functionArgs[0].contentRange == [43, 44]); 1737 assert(extract.functionArgs[1].contentRange == [46, 50]); 1738 1739 extract = dcdext.extractCallParameters(q{some_garbage(code); before(this); funcCall(4, ["a"], JSONValue(["b": JSONValue("c")]), recursive(func, call!s()), "texts )\"(too"}, 1740 129); 1741 assert(extract != CalltipsSupport.init); 1742 assert(!extract.hasTemplate); 1743 assert(extract.functionStart == 34); 1744 assert(extract.parentStart == 34); 1745 assert(extract.functionArgs.length == 5); 1746 assert(extract.functionArgs[0].contentRange == [43, 44]); 1747 assert(extract.functionArgs[1].contentRange == [46, 51]); 1748 assert(extract.functionArgs[2].contentRange == [53, 85]); 1749 assert(extract.functionArgs[3].contentRange == [87, 112]); 1750 assert(extract.functionArgs[4].contentRange == [114, 129]); 1751 1752 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`, 1753 127); 1754 assert(extract == CalltipsSupport.init); 1755 } 1756 1757 unittest 1758 { 1759 scope backend = new WorkspaceD(); 1760 auto workspace = makeTemporaryTestingWorkspace; 1761 auto instance = backend.addInstance(workspace.directory); 1762 backend.register!DCDExtComponent; 1763 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1764 1765 auto info = dcdext.describeInterfaceRecursiveSync(SimpleClassTestCode, 23); 1766 assert(info.details.name == "FooBar"); 1767 assert(info.details.blockRange == [27, 554]); 1768 assert(info.details.referencedTypes.length == 2); 1769 assert(info.details.referencedTypes[0].name == "Something"); 1770 assert(info.details.referencedTypes[0].location == 455); 1771 assert(info.details.referencedTypes[1].name == "string"); 1772 assert(info.details.referencedTypes[1].location == 74); 1773 1774 assert(info.details.fields.length == 4); 1775 assert(info.details.fields[0].name == "i"); 1776 assert(info.details.fields[1].name == "s"); 1777 assert(info.details.fields[2].name == "l"); 1778 assert(info.details.fields[3].name == "x"); 1779 1780 assert(info.details.types.length == 1); 1781 assert(info.details.types[0].type == TypeDetails.Type.struct_); 1782 assert(info.details.types[0].name == ["FooBar", "Something"]); 1783 assert(info.details.types[0].nameLocation == 420); 1784 1785 assert(info.details.methods.length == 6); 1786 assert(info.details.methods[0].name == "foo"); 1787 assert( 1788 info.details.methods[0].signature 1789 == "private static const int foo() @nogc nothrow pure @system;"); 1790 assert(info.details.methods[0].returnType == "int"); 1791 assert(info.details.methods[0].isNothrowOrNogc); 1792 assert(info.details.methods[0].hasBody); 1793 assert(!info.details.methods[0].needsImplementation); 1794 assert(!info.details.methods[0].optionalImplementation); 1795 assert(info.details.methods[0].definitionRange == [222, 286]); 1796 assert(info.details.methods[0].blockRange == [286, 324]); 1797 1798 assert(info.details.methods[1].name == "bar1"); 1799 assert(info.details.methods[1].signature == "private static void bar1();"); 1800 assert(info.details.methods[1].returnType == "void"); 1801 assert(!info.details.methods[1].isNothrowOrNogc); 1802 assert(info.details.methods[1].hasBody); 1803 assert(!info.details.methods[1].needsImplementation); 1804 assert(!info.details.methods[1].optionalImplementation); 1805 assert(info.details.methods[1].definitionRange == [334, 346]); 1806 assert(info.details.methods[1].blockRange == [346, 348]); 1807 1808 assert(info.details.methods[2].name == "bar2"); 1809 assert(info.details.methods[2].signature == "private void bar2();"); 1810 assert(info.details.methods[2].returnType == "void"); 1811 assert(!info.details.methods[2].isNothrowOrNogc); 1812 assert(info.details.methods[2].hasBody); 1813 assert(!info.details.methods[2].needsImplementation); 1814 assert(!info.details.methods[2].optionalImplementation); 1815 assert(info.details.methods[2].definitionRange == [351, 363]); 1816 assert(info.details.methods[2].blockRange == [363, 365]); 1817 1818 assert(info.details.methods[3].name == "bar3"); 1819 assert(info.details.methods[3].signature == "private void bar3();"); 1820 assert(info.details.methods[3].returnType == "void"); 1821 assert(!info.details.methods[3].isNothrowOrNogc); 1822 assert(info.details.methods[3].hasBody); 1823 assert(!info.details.methods[3].needsImplementation); 1824 assert(!info.details.methods[3].optionalImplementation); 1825 assert(info.details.methods[3].definitionRange == [396, 408]); 1826 assert(info.details.methods[3].blockRange == [408, 410]); 1827 1828 assert(info.details.methods[4].name == "somefunc"); 1829 assert(info.details.methods[4].signature == "private FooBar.Something somefunc();"); 1830 assert(info.details.methods[4].returnType == "FooBar.Something"); 1831 assert(!info.details.methods[4].isNothrowOrNogc); 1832 assert(info.details.methods[4].hasBody); 1833 assert(!info.details.methods[4].needsImplementation); 1834 assert(!info.details.methods[4].optionalImplementation); 1835 assert(info.details.methods[4].definitionRange == [448, 476]); 1836 assert(info.details.methods[4].blockRange == [476, 502]); 1837 1838 // test normalization of types 1839 assert(info.details.methods[5].name == "somefunc2"); 1840 assert(info.details.methods[5].signature == "private FooBar.Something somefunc2();", 1841 info.details.methods[5].signature); 1842 assert(info.details.methods[5].returnType == "FooBar.Something"); 1843 assert(!info.details.methods[5].isNothrowOrNogc); 1844 assert(info.details.methods[5].hasBody); 1845 assert(!info.details.methods[5].needsImplementation); 1846 assert(!info.details.methods[5].optionalImplementation); 1847 assert(info.details.methods[5].definitionRange == [504, 526]); 1848 assert(info.details.methods[5].blockRange == [526, 552]); 1849 } 1850 1851 unittest 1852 { 1853 string testCode = q{package interface Foo0 1854 { 1855 string stringMethod(); 1856 Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c); 1857 void normalMethod(); 1858 int attributeSuffixMethod() nothrow @property @nogc; 1859 private 1860 { 1861 void middleprivate1(); 1862 void middleprivate2(); 1863 } 1864 extern(C) @property @nogc ref immutable int attributePrefixMethod() const; 1865 final void alreadyImplementedMethod() {} 1866 deprecated("foo") void deprecatedMethod() {} 1867 static void staticMethod() {} 1868 protected void protectedMethod(); 1869 private: 1870 void barfoo(); 1871 }}; 1872 1873 scope backend = new WorkspaceD(); 1874 auto workspace = makeTemporaryTestingWorkspace; 1875 auto instance = backend.addInstance(workspace.directory); 1876 backend.register!DCDExtComponent; 1877 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1878 1879 auto info = dcdext.describeInterfaceRecursiveSync(testCode, 20); 1880 assert(info.details.name == "Foo0"); 1881 assert(info.details.blockRange == [23, 523]); 1882 assert(info.details.referencedTypes.length == 3); 1883 assert(info.details.referencedTypes[0].name == "Array"); 1884 assert(info.details.referencedTypes[0].location == 70); 1885 assert(info.details.referencedTypes[1].name == "Tuple"); 1886 assert(info.details.referencedTypes[1].location == 50); 1887 assert(info.details.referencedTypes[2].name == "string"); 1888 assert(info.details.referencedTypes[2].location == 26); 1889 1890 assert(info.details.fields.length == 0); 1891 1892 assert(info.details.methods[0 .. 4].all!"!a.hasBody"); 1893 assert(info.details.methods[0 .. 4].all!"a.needsImplementation"); 1894 assert(info.details.methods.all!"!a.optionalImplementation"); 1895 1896 assert(info.details.methods.length == 12); 1897 assert(info.details.methods[0].name == "stringMethod"); 1898 assert(info.details.methods[0].signature == "string stringMethod();"); 1899 assert(info.details.methods[0].returnType == "string"); 1900 assert(!info.details.methods[0].isNothrowOrNogc); 1901 1902 assert(info.details.methods[1].name == "advancedMethod"); 1903 assert(info.details.methods[1].signature 1904 == "Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c);"); 1905 assert(info.details.methods[1].returnType == "Tuple!(int, string, Array!bool)[][]"); 1906 assert(!info.details.methods[1].isNothrowOrNogc); 1907 1908 assert(info.details.methods[2].name == "normalMethod"); 1909 assert(info.details.methods[2].signature == "void normalMethod();"); 1910 assert(info.details.methods[2].returnType == "void"); 1911 1912 assert(info.details.methods[3].name == "attributeSuffixMethod"); 1913 assert(info.details.methods[3].signature == "int attributeSuffixMethod() nothrow @property @nogc;"); 1914 assert(info.details.methods[3].returnType == "int"); 1915 assert(info.details.methods[3].isNothrowOrNogc); 1916 1917 assert(info.details.methods[4].name == "middleprivate1"); 1918 assert(info.details.methods[4].signature == "private void middleprivate1();"); 1919 assert(info.details.methods[4].returnType == "void"); 1920 1921 assert(info.details.methods[5].name == "middleprivate2"); 1922 1923 assert(info.details.methods[6].name == "attributePrefixMethod"); 1924 assert(info.details.methods[6].signature 1925 == "extern (C) @property @nogc ref immutable int attributePrefixMethod() const;"); 1926 assert(info.details.methods[6].returnType == "int"); 1927 assert(info.details.methods[6].isNothrowOrNogc); 1928 1929 assert(info.details.methods[7].name == "alreadyImplementedMethod"); 1930 assert(info.details.methods[7].signature == "void alreadyImplementedMethod();"); 1931 assert(info.details.methods[7].returnType == "void"); 1932 assert(!info.details.methods[7].needsImplementation); 1933 assert(info.details.methods[7].hasBody); 1934 1935 assert(info.details.methods[8].name == "deprecatedMethod"); 1936 assert(info.details.methods[8].signature == `deprecated("foo") void deprecatedMethod();`); 1937 assert(info.details.methods[8].returnType == "void"); 1938 assert(info.details.methods[8].needsImplementation); 1939 assert(info.details.methods[8].hasBody); 1940 1941 assert(info.details.methods[9].name == "staticMethod"); 1942 assert(info.details.methods[9].signature == `static void staticMethod();`); 1943 assert(info.details.methods[9].returnType == "void"); 1944 assert(!info.details.methods[9].needsImplementation); 1945 assert(info.details.methods[9].hasBody); 1946 1947 assert(info.details.methods[10].name == "protectedMethod"); 1948 assert(info.details.methods[10].signature == `protected void protectedMethod();`); 1949 assert(info.details.methods[10].returnType == "void"); 1950 assert(info.details.methods[10].needsImplementation); 1951 assert(!info.details.methods[10].hasBody); 1952 1953 assert(info.details.methods[11].name == "barfoo"); 1954 assert(info.details.methods[11].signature == `private void barfoo();`); 1955 assert(info.details.methods[11].returnType == "void"); 1956 assert(!info.details.methods[11].needsImplementation); 1957 assert(!info.details.methods[11].hasBody); 1958 } 1959 1960 unittest 1961 { 1962 string testCode = q{module hello; 1963 1964 interface MyInterface 1965 { 1966 void foo(); 1967 } 1968 1969 class ImplA : MyInterface 1970 { 1971 1972 } 1973 1974 class ImplB : MyInterface 1975 { 1976 void foo() {} 1977 } 1978 }; 1979 1980 scope backend = new WorkspaceD(); 1981 auto workspace = makeTemporaryTestingWorkspace; 1982 auto instance = backend.addInstance(workspace.directory); 1983 backend.register!DCDExtComponent; 1984 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1985 1986 auto info = dcdext.getInterfaceDetails("stdin", testCode, 72); 1987 1988 assert(info.blockRange == [81, 85]); 1989 } 1990 1991 unittest 1992 { 1993 scope backend = new WorkspaceD(); 1994 auto workspace = makeTemporaryTestingWorkspace; 1995 auto instance = backend.addInstance(workspace.directory); 1996 backend.register!DCDExtComponent; 1997 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1998 1999 assert(dcdext.formatDefinitionBlock("Foo!(int, string) x") == "Foo!(int, string) x"); 2000 assert(dcdext.formatDefinitionBlock("void foo()") == "void foo()"); 2001 assert(dcdext.formatDefinitionBlock("void foo(string x)") == "void foo(\n\tstring x\n)"); 2002 assert(dcdext.formatDefinitionBlock("void foo(string x,)") == "void foo(\n\tstring x\n)"); 2003 assert(dcdext.formatDefinitionBlock("void foo(string x, int y)") == "void foo(\n\tstring x,\n\tint y\n)"); 2004 assert(dcdext.formatDefinitionBlock("void foo(string, int)") == "void foo(\n\tstring,\n\tint\n)"); 2005 assert(dcdext.formatDefinitionBlock("this(string, int)") == "this(\n\tstring,\n\tint\n)"); 2006 assert(dcdext.formatDefinitionBlock("auto foo(string, int)") == "auto foo(\n\tstring,\n\tint\n)"); 2007 assert(dcdext.formatDefinitionBlock("ComplexTemplate!(int, 'a', string, Nested!(Foo)) foo(string, int)") 2008 == "ComplexTemplate!(int, 'a', string, Nested!(Foo)) foo(\n\tstring,\n\tint\n)"); 2009 assert(dcdext.formatDefinitionBlock("auto foo(T, V)(string, int)") == "auto foo(\n\tT,\n\tV\n)(\n\tstring,\n\tint\n)"); 2010 assert(dcdext.formatDefinitionBlock("auto foo(string, int f, ...)") == "auto foo(\n\tstring,\n\tint f,\n\t...\n)"); 2011 } 2012 2013 final class IfFinder : ASTVisitor 2014 { 2015 Token[] currentIf, foundIf; 2016 2017 size_t target; 2018 2019 alias visit = ASTVisitor.visit; 2020 2021 static foreach (If; AliasSeq!(IfStatement, ConditionalStatement)) 2022 override void visit(const If ifStatement) 2023 { 2024 if (foundIf.length) 2025 return; 2026 2027 auto lastIf = currentIf; 2028 scope (exit) 2029 currentIf = lastIf; 2030 2031 currentIf = [ifStatement.tokens[0]]; 2032 2033 static auto thenStatement(const If v) 2034 { 2035 static if (is(If == IfStatement)) 2036 return v.thenStatement; 2037 else 2038 return v.trueStatement; 2039 } 2040 2041 static auto elseStatement(const If v) 2042 { 2043 static if (is(If == IfStatement)) 2044 return v.elseStatement; 2045 else 2046 return v.falseStatement; 2047 } 2048 2049 if (thenStatement(ifStatement)) 2050 thenStatement(ifStatement).accept(this); 2051 2052 const(BaseNode) elseStmt = elseStatement(ifStatement); 2053 while (elseStmt) 2054 { 2055 auto elseToken = elseStmt.tokens.ptr - 1; 2056 2057 // possible from if declarations 2058 if (elseToken.type == tok!"{" || elseToken.type == tok!":") 2059 elseToken--; 2060 2061 if (elseToken.type == tok!"else") 2062 { 2063 if (!currentIf.length || currentIf[$ - 1] != *elseToken) 2064 currentIf ~= *elseToken; 2065 } 2066 2067 if (auto elseIf = cast(IfStatement) elseStmt) 2068 { 2069 currentIf ~= elseIf.tokens[0]; 2070 elseIf.accept(this); 2071 cast()elseStmt = elseIf.elseStatement; 2072 } 2073 else if (auto elseStaticIf = cast(ConditionalStatement) elseStmt) 2074 { 2075 currentIf ~= elseStaticIf.tokens[0]; 2076 currentIf ~= elseStaticIf.tokens[1]; 2077 elseStaticIf.accept(this); 2078 cast()elseStmt = elseStaticIf.falseStatement; 2079 } 2080 else if (auto declOrStatement = cast(DeclarationOrStatement) elseStmt) 2081 { 2082 if (declOrStatement.statement && declOrStatement.statement.statementNoCaseNoDefault) 2083 { 2084 if (declOrStatement.statement.statementNoCaseNoDefault.conditionalStatement) 2085 { 2086 cast()elseStmt = declOrStatement.statement.statementNoCaseNoDefault.conditionalStatement; 2087 } 2088 else if (declOrStatement.statement.statementNoCaseNoDefault.ifStatement) 2089 { 2090 cast()elseStmt = declOrStatement.statement.statementNoCaseNoDefault.ifStatement; 2091 } 2092 else 2093 { 2094 elseStmt.accept(this); 2095 cast()elseStmt = null; 2096 } 2097 } 2098 else if (declOrStatement.declaration && declOrStatement.declaration.conditionalDeclaration) 2099 { 2100 auto cond = declOrStatement.declaration.conditionalDeclaration; 2101 if (cond.trueDeclarations.length) 2102 { 2103 auto ifSearch = cond.trueDeclarations[0].tokens.ptr; 2104 while (!ifSearch.type.among!(tok!"if", tok!";", tok!"}", tok!"module")) 2105 ifSearch--; 2106 2107 if (ifSearch.type == tok!"if") 2108 { 2109 if ((ifSearch - 1).type == tok!"static") 2110 currentIf ~= *(ifSearch - 1); 2111 currentIf ~= *ifSearch; 2112 } 2113 } 2114 2115 if (cond.hasElse && cond.falseDeclarations.length == 1) 2116 { 2117 elseStmt.accept(this); 2118 cast()elseStmt = cast()cond.falseDeclarations[0]; 2119 } 2120 else 2121 { 2122 elseStmt.accept(this); 2123 cast()elseStmt = null; 2124 } 2125 } 2126 else 2127 { 2128 elseStmt.accept(this); 2129 cast()elseStmt = null; 2130 } 2131 } 2132 else 2133 { 2134 elseStmt.accept(this); 2135 cast()elseStmt = null; 2136 } 2137 } 2138 2139 saveIfMatching(); 2140 } 2141 2142 void saveIfMatching() 2143 { 2144 if (foundIf.length) 2145 return; 2146 2147 foreach (v; currentIf) 2148 if (v.index == target) 2149 { 2150 foundIf = currentIf; 2151 return; 2152 } 2153 } 2154 } 2155 2156 unittest 2157 { 2158 scope backend = new WorkspaceD(); 2159 auto workspace = makeTemporaryTestingWorkspace; 2160 auto instance = backend.addInstance(workspace.directory); 2161 backend.register!DCDExtComponent; 2162 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2163 2164 assert(dcdext.highlightRelated(`void foo() 2165 { 2166 if (true) {} 2167 else static if (true) {} 2168 else if (true) {} 2169 else {} 2170 2171 if (true) {} 2172 else static if (true) {} 2173 else {} 2174 }`, 35) == [ 2175 Related(Related.Type.controlFlow, [14, 16]), 2176 Related(Related.Type.controlFlow, [28, 32]), 2177 Related(Related.Type.controlFlow, [33, 39]), 2178 Related(Related.Type.controlFlow, [40, 42]), 2179 Related(Related.Type.controlFlow, [54, 58]), 2180 Related(Related.Type.controlFlow, [59, 61]), 2181 Related(Related.Type.controlFlow, [73, 77]), 2182 ]); 2183 2184 assert(dcdext.highlightRelated(`void foo() 2185 { 2186 if (true) {} 2187 else static if (true) {} 2188 else if (true) {} 2189 else {} 2190 2191 if (true) {} 2192 else static if (true) { int a; } 2193 else { int b;} 2194 }`, 83) == [ 2195 Related(Related.Type.controlFlow, [83, 85]), 2196 Related(Related.Type.controlFlow, [97, 101]), 2197 Related(Related.Type.controlFlow, [102, 108]), 2198 Related(Related.Type.controlFlow, [109, 111]), 2199 Related(Related.Type.controlFlow, [131, 135]), 2200 ]); 2201 } 2202 2203 final class SwitchFinder : ASTVisitor 2204 { 2205 Token[] currentSwitch, foundSwitch; 2206 const(Statement) currentStatement; 2207 2208 size_t target; 2209 2210 alias visit = ASTVisitor.visit; 2211 2212 override void visit(const SwitchStatement stmt) 2213 { 2214 if (foundSwitch.length) 2215 return; 2216 2217 auto lastSwitch = currentSwitch; 2218 scope (exit) 2219 currentSwitch = lastSwitch; 2220 2221 currentSwitch = [stmt.tokens[0]]; 2222 stmt.accept(this); 2223 2224 saveIfMatching(); 2225 } 2226 2227 override void visit(const CaseRangeStatement stmt) 2228 { 2229 if (currentStatement) 2230 { 2231 auto curr = currentStatement.tokens[0]; 2232 if (curr.type == tok!"case") 2233 currentSwitch ~= curr; 2234 } 2235 auto last = *(stmt.high.tokens.ptr - 1); 2236 if (last.type == tok!"case") 2237 currentSwitch ~= last; 2238 stmt.accept(this); 2239 } 2240 2241 override void visit(const CaseStatement stmt) 2242 { 2243 if (currentStatement) 2244 { 2245 auto curr = currentStatement.tokens[0]; 2246 if (curr.type == tok!"case") 2247 currentSwitch ~= curr; 2248 } 2249 stmt.accept(this); 2250 } 2251 2252 override void visit(const DefaultStatement stmt) 2253 { 2254 currentSwitch ~= stmt.tokens[0]; 2255 stmt.accept(this); 2256 } 2257 2258 override void visit(const Statement stmt) 2259 { 2260 auto last = currentStatement; 2261 scope (exit) 2262 cast()currentStatement = cast()last; 2263 cast()currentStatement = cast()stmt; 2264 stmt.accept(this); 2265 } 2266 2267 void saveIfMatching() 2268 { 2269 if (foundSwitch.length) 2270 return; 2271 2272 foreach (v; currentSwitch) 2273 if (v.index == target) 2274 { 2275 foundSwitch = currentSwitch; 2276 return; 2277 } 2278 } 2279 } 2280 2281 unittest 2282 { 2283 scope backend = new WorkspaceD(); 2284 auto workspace = makeTemporaryTestingWorkspace; 2285 auto instance = backend.addInstance(workspace.directory); 2286 backend.register!DCDExtComponent; 2287 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2288 2289 assert(dcdext.highlightRelated(`void foo() 2290 { 2291 switch (foo) 2292 { 2293 case 1: .. case 3: 2294 break; 2295 case 5: 2296 switch (bar) 2297 { 2298 case 6: 2299 break; 2300 default: 2301 break; 2302 } 2303 break; 2304 default: 2305 break; 2306 } 2307 }`, 35) == [ 2308 Related(Related.Type.controlFlow, [14, 20]), 2309 Related(Related.Type.controlFlow, [32, 36]), 2310 Related(Related.Type.controlFlow, [43, 47]), 2311 Related(Related.Type.controlFlow, [63, 67]), 2312 Related(Related.Type.controlFlow, [154, 161]), 2313 ]); 2314 } 2315 2316 final class BreakFinder : ASTVisitor 2317 { 2318 Token[] currentBlock, foundBlock; 2319 const(Statement) currentStatement; 2320 bool inSwitch; 2321 2322 size_t target; 2323 bool isBreak; // else continue if not loop 2324 bool isLoop; // checking loop token (instead of break/continue) 2325 string label; 2326 2327 alias visit = ASTVisitor.visit; 2328 2329 override void visit(const LabeledStatement stmt) 2330 { 2331 if (foundBlock.length) 2332 return; 2333 2334 if (label.length && label == stmt.identifier.text) 2335 { 2336 foundBlock = [stmt.identifier]; 2337 return; 2338 } 2339 2340 stmt.accept(this); 2341 } 2342 2343 override void visit(const SwitchStatement stmt) 2344 { 2345 if (foundBlock.length) 2346 return; 2347 2348 bool wasSwitch = inSwitch; 2349 scope (exit) 2350 inSwitch = wasSwitch; 2351 inSwitch = true; 2352 2353 if (isBreak) 2354 { 2355 auto lastSwitch = currentBlock; 2356 scope (exit) 2357 currentBlock = lastSwitch; 2358 2359 currentBlock = [stmt.tokens[0]]; 2360 stmt.accept(this); 2361 2362 saveIfMatching(); 2363 } 2364 else 2365 { 2366 stmt.accept(this); 2367 } 2368 } 2369 2370 static foreach (LoopT; AliasSeq!(ForeachStatement, StaticForeachDeclaration, 2371 StaticForeachStatement, ForStatement, WhileStatement)) 2372 override void visit(const LoopT stmt) 2373 { 2374 if (foundBlock.length) 2375 return; 2376 2377 auto lastSwitch = currentBlock; 2378 scope (exit) 2379 currentBlock = lastSwitch; 2380 2381 currentBlock = [stmt.tokens[0]]; 2382 stmt.accept(this); 2383 2384 saveIfMatching(); 2385 } 2386 2387 override void visit(const DoStatement stmt) 2388 { 2389 if (foundBlock.length) 2390 return; 2391 2392 auto lastSwitch = currentBlock; 2393 scope (exit) 2394 currentBlock = lastSwitch; 2395 2396 currentBlock = [stmt.tokens[0]]; 2397 auto whileTok = *(stmt.expression.tokens.ptr - 2); 2398 stmt.accept(this); 2399 if (whileTok.type == tok!"while") 2400 currentBlock ~= whileTok; 2401 2402 saveIfMatching(); 2403 } 2404 2405 static foreach (IgnoreT; AliasSeq!(FunctionBody, FunctionDeclaration, StructBody)) 2406 override void visit(const IgnoreT stmt) 2407 { 2408 if (foundBlock.length) 2409 return; 2410 2411 auto lastSwitch = currentBlock; 2412 scope (exit) 2413 currentBlock = lastSwitch; 2414 2415 currentBlock = null; 2416 stmt.accept(this); 2417 } 2418 2419 override void visit(const CaseRangeStatement stmt) 2420 { 2421 if (isBreak) 2422 { 2423 if (currentStatement) 2424 { 2425 auto curr = currentStatement.tokens[0]; 2426 if (curr.type == tok!"case") 2427 currentBlock ~= curr; 2428 } 2429 auto last = *(stmt.high.tokens.ptr - 1); 2430 if (last.type == tok!"case") 2431 currentBlock ~= last; 2432 } 2433 stmt.accept(this); 2434 } 2435 2436 override void visit(const CaseStatement stmt) 2437 { 2438 if (currentStatement && isBreak) 2439 { 2440 auto curr = currentStatement.tokens[0]; 2441 if (curr.type == tok!"case") 2442 currentBlock ~= curr; 2443 } 2444 stmt.accept(this); 2445 } 2446 2447 override void visit(const DefaultStatement stmt) 2448 { 2449 if (isBreak) 2450 currentBlock ~= stmt.tokens[0]; 2451 stmt.accept(this); 2452 } 2453 2454 override void visit(const Statement stmt) 2455 { 2456 auto last = currentStatement; 2457 scope (exit) 2458 cast()currentStatement = cast()last; 2459 cast()currentStatement = cast()stmt; 2460 stmt.accept(this); 2461 } 2462 2463 override void visit(const BreakStatement stmt) 2464 { 2465 if (stmt.tokens[0].index == target || isLoop) 2466 if (isBreak) 2467 currentBlock ~= stmt.tokens[0]; 2468 stmt.accept(this); 2469 } 2470 2471 override void visit(const ContinueStatement stmt) 2472 { 2473 // break token: 2474 // continue in switch: ignore 2475 // continue outside switch: include 2476 // other token: 2477 // continue in switch: include 2478 // continue outside switch: include 2479 if (stmt.tokens[0].index == target || isLoop) 2480 if (!(isBreak && inSwitch)) 2481 currentBlock ~= stmt.tokens[0]; 2482 stmt.accept(this); 2483 } 2484 2485 void saveIfMatching() 2486 { 2487 if (foundBlock.length || label.length) 2488 return; 2489 2490 foreach (v; currentBlock) 2491 if (v.index == target) 2492 { 2493 foundBlock = currentBlock; 2494 return; 2495 } 2496 } 2497 } 2498 2499 class ReverseReturnFinder : ASTVisitor 2500 { 2501 Token[] returns; 2502 size_t target; 2503 bool record; 2504 2505 alias visit = ASTVisitor.visit; 2506 2507 static foreach (DeclT; AliasSeq!(Declaration, Statement)) 2508 override void visit(const DeclT stmt) 2509 { 2510 if (returns.length && !record) 2511 return; 2512 2513 bool matches = stmt.tokens.length && stmt.tokens[0].index == target; 2514 if (matches) 2515 record = true; 2516 stmt.accept(this); 2517 if (matches) 2518 record = false; 2519 } 2520 2521 override void visit(const ReturnStatement ret) 2522 { 2523 if (record) 2524 returns ~= ret.tokens[0]; 2525 ret.accept(this); 2526 } 2527 } 2528 2529 unittest 2530 { 2531 scope backend = new WorkspaceD(); 2532 auto workspace = makeTemporaryTestingWorkspace; 2533 auto instance = backend.addInstance(workspace.directory); 2534 backend.register!DCDExtComponent; 2535 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2536 2537 assert(dcdext.highlightRelated(`void foo() 2538 { 2539 while (true) 2540 { 2541 foreach (a; b) 2542 { 2543 switch (a) 2544 { 2545 case 1: 2546 break; 2547 case 2: 2548 continue; 2549 default: 2550 return; 2551 } 2552 } 2553 } 2554 }`, 88) == [ 2555 Related(Related.Type.controlFlow, [54, 60]), 2556 Related(Related.Type.controlFlow, [73, 77]), 2557 Related(Related.Type.controlFlow, [85, 90]), 2558 Related(Related.Type.controlFlow, [95, 99]), 2559 Related(Related.Type.controlFlow, [120, 127]), 2560 ]); 2561 2562 assert(dcdext.highlightRelated(`void foo() 2563 { 2564 while (true) 2565 { 2566 foreach (a; b) 2567 { 2568 switch (a) 2569 { 2570 case 1: 2571 break; 2572 case 2: 2573 continue; 2574 default: 2575 return; 2576 } 2577 } 2578 } 2579 }`, 111) == [ 2580 Related(Related.Type.controlFlow, [32, 39]), 2581 Related(Related.Type.controlFlow, [107, 115]), 2582 ]); 2583 2584 assert(dcdext.highlightRelated(`void foo() 2585 { 2586 while (true) 2587 { 2588 foreach (a; b) 2589 { 2590 switch (a) 2591 { 2592 case 1: 2593 break; 2594 case 2: 2595 continue; 2596 default: 2597 return; 2598 } 2599 } 2600 } 2601 }`, 15) == [ 2602 Related(Related.Type.controlFlow, [14, 19]), 2603 Related(Related.Type.controlFlow, [133, 139]), 2604 ]); 2605 } 2606 2607 class ReturnFinder : ASTVisitor 2608 { 2609 Token[] returns; 2610 Token[] currentScope; 2611 bool inTargetBlock; 2612 Token[] related; 2613 size_t target; 2614 2615 alias visit = ASTVisitor.visit; 2616 2617 static foreach (DeclT; AliasSeq!(FunctionBody)) 2618 override void visit(const DeclT stmt) 2619 { 2620 if (inTargetBlock || related.length) 2621 return; 2622 2623 auto lastScope = currentScope; 2624 scope (exit) 2625 currentScope = lastScope; 2626 currentScope = null; 2627 2628 auto lastReturns = returns; 2629 scope (exit) 2630 returns = lastReturns; 2631 returns = null; 2632 2633 stmt.accept(this); 2634 if (inTargetBlock) 2635 { 2636 related ~= returns; 2637 2638 related.sort!"a.index < b.index"; 2639 } 2640 } 2641 2642 static foreach (ScopeT; AliasSeq!(SwitchStatement, ForeachStatement, 2643 StaticForeachDeclaration, StaticForeachStatement, ForStatement, WhileStatement)) 2644 override void visit(const ScopeT stmt) 2645 { 2646 auto lastScope = currentScope; 2647 scope (exit) 2648 currentScope = lastScope; 2649 currentScope ~= stmt.tokens[0]; 2650 2651 stmt.accept(this); 2652 } 2653 2654 override void visit(const DoStatement stmt) 2655 { 2656 auto lastScope = currentScope; 2657 scope (exit) 2658 currentScope = lastScope; 2659 currentScope ~= stmt.tokens[0]; 2660 2661 auto whileTok = *(stmt.expression.tokens.ptr - 2); 2662 if (whileTok.type == tok!"while") 2663 currentScope ~= whileTok; 2664 2665 stmt.accept(this); 2666 } 2667 2668 override void visit(const ReturnStatement ret) 2669 { 2670 returns ~= ret.tokens[0]; 2671 if (target == ret.tokens[0].index) 2672 { 2673 inTargetBlock = true; 2674 related ~= currentScope; 2675 } 2676 ret.accept(this); 2677 } 2678 } 2679 2680 unittest 2681 { 2682 scope backend = new WorkspaceD(); 2683 auto workspace = makeTemporaryTestingWorkspace; 2684 auto instance = backend.addInstance(workspace.directory); 2685 backend.register!DCDExtComponent; 2686 DCDExtComponent dcdext = instance.get!DCDExtComponent; 2687 2688 assert(dcdext.highlightRelated(`void foo() 2689 { 2690 foreach (a; b) 2691 return; 2692 2693 void bar() 2694 { 2695 return; 2696 } 2697 2698 bar(); 2699 2700 return; 2701 }`, 33) == [ 2702 Related(Related.Type.controlFlow, [14, 21]), 2703 Related(Related.Type.controlFlow, [31, 37]), 2704 Related(Related.Type.controlFlow, [79, 85]), 2705 ]); 2706 }