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 		if (targetPosition >= name.index && targetPosition < structBody.endLocation)
199 		{
200 			details.blockRange = [structBody.startLocation, structBody.endLocation + 1];
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 		if (dec && dec.declaratorIdentifierList)
385 			foreach (ident; dec.declaratorIdentifierList.identifiers)
386 				details.types ~= TypeDetails(nested ~ ident.text, ident.index, TypeDetails.Type.alias_);
387 	}
388 
389 	void handleType(T)(TypeDetails.Type type, string name, size_t location, T node)
390 	{
391 		pushNestedType(type, name, location);
392 		super.visit(node);
393 		popNestedType();
394 	}
395 
396 	override void visit(const FunctionBody)
397 	{
398 	}
399 
400 	alias visit = ASTVisitor.visit;
401 
402 	protected void pushNestedType(TypeDetails.Type type, string name, size_t index)
403 	{
404 		nested ~= name;
405 		details.types ~= TypeDetails(nested, index, type);
406 	}
407 
408 	protected void popNestedType()
409 	{
410 		nested.length--;
411 	}
412 
413 	string[] nested;
414 	InterfaceDetails* details;
415 }
416 
417 void formatTypeTransforming(Sink, T)(Sink sink, T node, string delegate(const(char)[]) translateType,
418 		bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWith = 4)
419 {
420 	TypeTransformingFormatter!Sink formatter = new TypeTransformingFormatter!(Sink)(sink,
421 			useTabs, style, indentWith);
422 	formatter.translateType = translateType;
423 	formatter.format(node);
424 }
425 
426 ///
427 class TypeTransformingFormatter(Sink) : Formatter!Sink
428 {
429 	string delegate(const(char)[]) translateType;
430 	Appender!(char[]) tempBuffer;
431 	bool useTempBuffer;
432 
433 	this(Sink sink, bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWidth = 4)
434 	{
435 		super(sink, useTabs, style, indentWidth);
436 		tempBuffer = appender!(char[]);
437 	}
438 
439 	override void put(string s)
440 	{
441 		if (useTempBuffer)
442 			tempBuffer.put(s);
443 		else
444 			super.put(s);
445 	}
446 
447 	protected void flushTempBuffer()
448 	{
449 		if (!useTempBuffer || tempBuffer.data.empty)
450 			return;
451 
452 		useTempBuffer = false;
453 		put(translateType(tempBuffer.data));
454 		tempBuffer.clear();
455 	}
456 
457 	override void format(const TypeIdentifierPart type)
458 	{
459 		useTempBuffer = true;
460 
461 		if (type.dot)
462 		{
463 			put(".");
464 		}
465 		if (type.identifierOrTemplateInstance)
466 		{
467 			format(type.identifierOrTemplateInstance);
468 		}
469 		if (type.indexer)
470 		{
471 			flushTempBuffer();
472 			put("[");
473 			format(type.indexer);
474 			put("]");
475 		}
476 		if (type.typeIdentifierPart)
477 		{
478 			put(".");
479 			format(type.typeIdentifierPart);
480 		}
481 		else
482 		{
483 			flushTempBuffer();
484 		}
485 	}
486 
487 	override void format(const IdentifierOrTemplateInstance identifierOrTemplateInstance)
488 	{
489 		with (identifierOrTemplateInstance)
490 		{
491 			format(identifier);
492 			if (templateInstance)
493 			{
494 				flushTempBuffer();
495 				format(templateInstance);
496 			}
497 		}
498 	}
499 
500 	alias format = Formatter!Sink.format;
501 }