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