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