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