1 /// Finds methods in a specified interface or class location.
2 module workspaced.visitors.methodfinder;
3 
4 import workspaced.visitors.attributes;
5 
6 import workspaced.dparseext;
7 
8 import dparse.ast;
9 import dparse.formatter;
10 import dparse.lexer;
11 
12 import std.algorithm;
13 import std.array;
14 import std.range;
15 import std.string;
16 
17 /// Information about an argument in a method defintion.
18 struct ArgumentInfo
19 {
20 	/// The whole definition of the argument including everything related to it as formatted code string.
21 	string signature;
22 	/// The type of the argument.
23 	string type;
24 	/// The name of the argument.
25 	string name;
26 
27 	/// Returns just the name.
28 	string toString() const
29 	{
30 		return name;
31 	}
32 }
33 
34 /// Information about a method definition.
35 struct MethodDetails
36 {
37 	/// The name of the method.
38 	string name;
39 	/// The type definition of the method without body, abstract or final.
40 	string signature;
41 	/// The return type of the method.
42 	string returnType;
43 	/// All (regular) arguments passable into this method.
44 	ArgumentInfo[] arguments;
45 	///
46 	bool isNothrowOrNogc;
47 	/// True if this function has an implementation.
48 	bool hasBody;
49 	/// True when the container is an interface or (optionally implicit) abstract class or when in class not having a body.
50 	bool needsImplementation;
51 	/// True when in a class and method doesn't have a body.
52 	bool optionalImplementation;
53 	/// Range starting at return type, going until last token before opening curly brace.
54 	size_t[2] definitionRange;
55 	/// Range containing the starting and ending braces of the body.
56 	size_t[2] blockRange;
57 
58 	/// Signature without any attributes, constraints or parameter details other than types.
59 	/// Used to differentiate a method from others without computing the mangle.
60 	/// Returns: `"<type> <name>(<argument types>)"`
61 	string identifier()
62 	{
63 		return format("%s %s(%(%s,%))", returnType, name, arguments.map!"a.type");
64 	}
65 }
66 
67 ///
68 struct FieldDetails
69 {
70 	///
71 	string name, type;
72 	///
73 	bool isPrivate;
74 }
75 
76 ///
77 struct TypeDetails
78 {
79 	enum Type
80 	{
81 		none,
82 		class_,
83 		interface_,
84 		enum_,
85 		struct_,
86 		union_,
87 		alias_,
88 		template_,
89 	}
90 
91 	/// Name in last element, all parents in previous elements.
92 	string[] name;
93 	///
94 	size_t nameLocation;
95 	///
96 	Type type;
97 }
98 
99 ///
100 struct ReferencedType
101 {
102 	/// Referenced type name, might be longer than actual written name because of normalization of parents.
103 	string name;
104 	/// Location of name which will start right before the last identifier of the type in a dot chain.
105 	size_t location;
106 }
107 
108 /// Information about an interface or class
109 struct InterfaceDetails
110 {
111 	/// Entire code of the file
112 	const(char)[] code;
113 	/// True if this is a class and therefore need to override methods using $(D override).
114 	bool needsOverride;
115 	/// Name of the interface or class.
116 	string name;
117 	/// Plain old variable fields in this container.
118 	FieldDetails[] fields;
119 	/// All methods defined in this container.
120 	MethodDetails[] methods;
121 	/// A list of nested types and locations defined in this interface/class.
122 	TypeDetails[] types;
123 	// reserved for future use with templates
124 	string[] parents;
125 	/// Name of all base classes or interfaces. Should use normalizedParents,
126 	string[] normalizedParents;
127 	/// Absolute code position after the colon where the corresponding parent name starts.
128 	int[] parentPositions;
129 	/// Range containing the starting and ending braces of the body.
130 	size_t[2] blockRange;
131 	/// A (name-based) sorted set of referenced types with first occurences of every type or alias not including built-in types, but including object.d types and aliases.
132 	ReferencedType[] referencedTypes;
133 }
134 
135 class InterfaceMethodFinder : AttributesVisitor
136 {
137 	this(const(char)[] code, int targetPosition)
138 	{
139 		this.code = code;
140 		details.code = code;
141 		this.targetPosition = targetPosition;
142 	}
143 
144 	override void visit(const StructDeclaration dec)
145 	{
146 		if (inTarget)
147 			return;
148 		else
149 			super.visit(dec);
150 	}
151 
152 	override void visit(const UnionDeclaration dec)
153 	{
154 		if (inTarget)
155 			return;
156 		else
157 			super.visit(dec);
158 	}
159 
160 	override void visit(const EnumDeclaration dec)
161 	{
162 		if (inTarget)
163 			return;
164 		else
165 			super.visit(dec);
166 	}
167 
168 	override void visit(const ClassDeclaration dec)
169 	{
170 		if (inTarget)
171 			return;
172 
173 		auto c = context.save();
174 		context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text);
175 		visitInterface(dec.name, dec.baseClassList, dec.structBody, true);
176 		context.restore(c);
177 	}
178 
179 	override void visit(const InterfaceDeclaration dec)
180 	{
181 		if (inTarget)
182 			return;
183 
184 		auto c = context.save();
185 		context.pushContainer(ASTContext.ContainerAttribute.Type.interface_, dec.name.text);
186 		visitInterface(dec.name, dec.baseClassList, dec.structBody, false);
187 		context.restore(c);
188 	}
189 
190 	private void visitInterface(const Token name, const BaseClassList baseClassList,
191 			const StructBody structBody, bool needsOverride)
192 	{
193 		if (!structBody)
194 			return;
195 		if (inTarget)
196 			return; // ignore nested
197 
198 		details.blockRange = [structBody.startLocation, structBody.endLocation + 1];
199 		if (targetPosition >= name.index && targetPosition < structBody.endLocation)
200 		{
201 			details.name = name.text;
202 			if (baseClassList)
203 				foreach (base; baseClassList.items)
204 				{
205 					if (!base.type2 || !base.type2.typeIdentifierPart
206 							|| !base.type2.typeIdentifierPart.identifierOrTemplateInstance)
207 						continue;
208 					// TODO: template support!
209 					details.parents ~= astToString(base.type2);
210 					details.normalizedParents ~= astToString(base.type2);
211 					details.parentPositions ~= cast(
212 							int) base.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier.index + 1;
213 				}
214 			details.needsOverride = needsOverride;
215 			inTarget = true;
216 			structBody.accept(new NestedTypeFinder(&details, details.name));
217 			super.visit(structBody);
218 			inTarget = false;
219 		}
220 	}
221 
222 	override void visit(const FunctionDeclaration dec)
223 	{
224 		if (!inTarget)
225 			return;
226 
227 		size_t[2] definitionRange = [dec.name.index, 0];
228 		size_t[2] blockRange;
229 
230 		if (dec.returnType !is null && dec.returnType.tokens.length > 0)
231 			definitionRange[0] = dec.returnType.tokens[0].index;
232 
233 		if (dec.functionBody !is null && dec.functionBody.tokens.length > 0)
234 		{
235 			definitionRange[1] = dec.functionBody.tokens[0].index;
236 			blockRange = [
237 				dec.functionBody.tokens[0].index, dec.functionBody.tokens[$ - 1].index + 1
238 			];
239 		}
240 		else if (dec.parameters !is null && dec.parameters.tokens.length > 0)
241 			definitionRange[1] = dec.parameters.tokens[$ - 1].index
242 				+ dec.parameters.tokens[$ - 1].text.length;
243 
244 		auto origBody = (cast() dec).functionBody;
245 		const hasBody = !!origBody && origBody.missingFunctionBody is null;
246 		auto origComment = (cast() dec).comment;
247 		const implLevel = context.requiredImplementationLevel;
248 		const optionalImplementation = implLevel == 1 && !hasBody;
249 		const needsImplementation = implLevel == 9 || optionalImplementation;
250 		(cast() dec).functionBody = null;
251 		(cast() dec).comment = null;
252 		scope (exit)
253 		{
254 			(cast() dec).functionBody = origBody;
255 			(cast() dec).comment = origComment;
256 		}
257 		auto t = appender!string;
258 		formatTypeTransforming(t, dec, &resolveType);
259 		string method = context.localFormattedAttributes.chain([t.data.strip])
260 			.filter!(a => a.length > 0 && !a.among!("abstract", "final")).join(" ");
261 		ArgumentInfo[] arguments;
262 		if (dec.parameters)
263 			foreach (arg; dec.parameters.parameters)
264 				arguments ~= ArgumentInfo(astToString(arg), astToString(arg.type), arg.name.text);
265 		string returnType = dec.returnType ? resolveType(astToString(dec.returnType)) : "void";
266 
267 		// now visit to populate isNothrow, isNogc (before it would add to the localFormattedAttributes string)
268 		// also fills in used types
269 		super.visit(dec);
270 
271 		details.methods ~= MethodDetails(dec.name.text, method, returnType, arguments, context.isNothrowInContainer
272 				|| context.isNogcInContainer, hasBody, needsImplementation,
273 				optionalImplementation, definitionRange, blockRange);
274 	}
275 
276 	override void visit(const FunctionBody)
277 	{
278 	}
279 
280 	override void visit(const VariableDeclaration variable)
281 	{
282 		if (!inTarget)
283 			return;
284 		if (!variable.type)
285 			return;
286 		string type = astToString(variable.type);
287 		auto isPrivate = context.protectionType == tok!"private";
288 
289 		foreach (decl; variable.declarators)
290 			details.fields ~= FieldDetails(decl.name.text, type, isPrivate);
291 
292 		if (variable.type)
293 			variable.type.accept(this); // to fill in types
294 	}
295 
296 	override void visit(const TypeIdentifierPart type)
297 	{
298 		if (!inTarget)
299 			return;
300 
301 		if (type.identifierOrTemplateInstance && !type.typeIdentifierPart)
302 		{
303 			auto tok = type.identifierOrTemplateInstance.templateInstance
304 				? type.identifierOrTemplateInstance.templateInstance.identifier
305 				: type.identifierOrTemplateInstance.identifier;
306 
307 			usedType(ReferencedType(tok.text, tok.index));
308 		}
309 
310 		super.visit(type);
311 	}
312 
313 	alias visit = AttributesVisitor.visit;
314 
315 	protected void usedType(ReferencedType type)
316 	{
317 		// this is a simple sorted set insert
318 		auto sorted = assumeSorted!"a.name < b.name"(details.referencedTypes).trisect(type);
319 		if (sorted[1].length)
320 			return; // exists already
321 		details.referencedTypes.insertInPlace(sorted[0].length, type);
322 	}
323 
324 	string resolveType(const(char)[] inType)
325 	{
326 		auto parts = inType.splitter('.');
327 		string[] best;
328 		foreach (type; details.types)
329 			if ((!best.length || type.name.length < best.length) && type.name.endsWith(parts))
330 				best = type.name;
331 
332 		if (best.length)
333 			return best.join(".");
334 		else
335 			return inType.idup;
336 	}
337 
338 	const(char)[] code;
339 	bool inTarget;
340 	int targetPosition;
341 	InterfaceDetails details;
342 }
343 
344 class NestedTypeFinder : ASTVisitor
345 {
346 	this(InterfaceDetails* details, string start)
347 	{
348 		this.details = details;
349 		this.nested = [start];
350 	}
351 
352 	override void visit(const StructDeclaration dec)
353 	{
354 		handleType(TypeDetails.Type.struct_, dec.name.text, dec.name.index, dec);
355 	}
356 
357 	override void visit(const UnionDeclaration dec)
358 	{
359 		handleType(TypeDetails.Type.union_, dec.name.text, dec.name.index, dec);
360 	}
361 
362 	override void visit(const EnumDeclaration dec)
363 	{
364 		handleType(TypeDetails.Type.enum_, dec.name.text, dec.name.index, dec);
365 	}
366 
367 	override void visit(const ClassDeclaration dec)
368 	{
369 		handleType(TypeDetails.Type.class_, dec.name.text, dec.name.index, dec);
370 	}
371 
372 	override void visit(const InterfaceDeclaration dec)
373 	{
374 		handleType(TypeDetails.Type.interface_, dec.name.text, dec.name.index, dec);
375 	}
376 
377 	override void visit(const TemplateDeclaration dec)
378 	{
379 		handleType(TypeDetails.Type.template_, dec.name.text, dec.name.index, dec);
380 	}
381 
382 	override void visit(const AliasDeclaration dec)
383 	{
384 		foreach (ident; dec.declaratorIdentifierList.identifiers)
385 			details.types ~= TypeDetails(nested ~ ident.text, ident.index, TypeDetails.Type.alias_);
386 	}
387 
388 	void handleType(T)(TypeDetails.Type type, string name, size_t location, T node)
389 	{
390 		pushNestedType(type, name, location);
391 		super.visit(node);
392 		popNestedType();
393 	}
394 
395 	override void visit(const FunctionBody)
396 	{
397 	}
398 
399 	alias visit = ASTVisitor.visit;
400 
401 	protected void pushNestedType(TypeDetails.Type type, string name, size_t index)
402 	{
403 		nested ~= name;
404 		details.types ~= TypeDetails(nested, index, type);
405 	}
406 
407 	protected void popNestedType()
408 	{
409 		nested.length--;
410 	}
411 
412 	string[] nested;
413 	InterfaceDetails* details;
414 }
415 
416 void formatTypeTransforming(Sink, T)(Sink sink, T node, string delegate(const(char)[]) translateType,
417 		bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWith = 4)
418 {
419 	TypeTransformingFormatter!Sink formatter = new TypeTransformingFormatter!(Sink)(sink,
420 			useTabs, style, indentWith);
421 	formatter.translateType = translateType;
422 	formatter.format(node);
423 }
424 
425 ///
426 class TypeTransformingFormatter(Sink) : Formatter!Sink
427 {
428 	string delegate(const(char)[]) translateType;
429 	Appender!(char[]) tempBuffer;
430 	bool useTempBuffer;
431 
432 	this(Sink sink, bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWidth = 4)
433 	{
434 		super(sink, useTabs, style, indentWidth);
435 		tempBuffer = appender!(char[]);
436 	}
437 
438 	override void put(string s)
439 	{
440 		if (useTempBuffer)
441 			tempBuffer.put(s);
442 		else
443 			super.put(s);
444 	}
445 
446 	protected void flushTempBuffer()
447 	{
448 		if (!useTempBuffer || tempBuffer.data.empty)
449 			return;
450 
451 		useTempBuffer = false;
452 		put(translateType(tempBuffer.data));
453 		tempBuffer.clear();
454 	}
455 
456 	override void format(const TypeIdentifierPart type)
457 	{
458 		useTempBuffer = true;
459 
460 		if (type.dot)
461 		{
462 			put(".");
463 		}
464 		if (type.identifierOrTemplateInstance)
465 		{
466 			format(type.identifierOrTemplateInstance);
467 		}
468 		if (type.indexer)
469 		{
470 			flushTempBuffer();
471 			put("[");
472 			format(type.indexer);
473 			put("]");
474 		}
475 		if (type.typeIdentifierPart)
476 		{
477 			put(".");
478 			format(type.typeIdentifierPart);
479 		}
480 		else
481 		{
482 			flushTempBuffer();
483 		}
484 	}
485 
486 	override void format(const IdentifierOrTemplateInstance identifierOrTemplateInstance)
487 	{
488 		with (identifierOrTemplateInstance)
489 		{
490 			format(identifier);
491 			if (templateInstance)
492 			{
493 				flushTempBuffer();
494 				format(templateInstance);
495 			}
496 		}
497 	}
498 
499 	alias format = Formatter!Sink.format;
500 }