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 	/// Returns true if there are no non-whitespace characters inside the block.
135 	bool isEmpty() const
136 	{
137 		return !code.substr(blockRange).strip.length;
138 	}
139 }
140 
141 class InterfaceMethodFinder : AttributesVisitor
142 {
143 	this(const(char)[] code, int targetPosition)
144 	{
145 		this.code = code;
146 		details.code = code;
147 		this.targetPosition = targetPosition;
148 	}
149 
150 	override void visit(const StructDeclaration dec)
151 	{
152 		if (inTarget)
153 			return;
154 		else
155 			super.visit(dec);
156 	}
157 
158 	override void visit(const UnionDeclaration dec)
159 	{
160 		if (inTarget)
161 			return;
162 		else
163 			super.visit(dec);
164 	}
165 
166 	override void visit(const EnumDeclaration dec)
167 	{
168 		if (inTarget)
169 			return;
170 		else
171 			super.visit(dec);
172 	}
173 
174 	override void visit(const ClassDeclaration dec)
175 	{
176 		if (inTarget)
177 			return;
178 
179 		auto c = context.save();
180 		context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text);
181 		visitInterface(dec.name, dec.baseClassList, dec.structBody, true);
182 		context.restore(c);
183 	}
184 
185 	override void visit(const InterfaceDeclaration dec)
186 	{
187 		if (inTarget)
188 			return;
189 
190 		auto c = context.save();
191 		context.pushContainer(ASTContext.ContainerAttribute.Type.interface_, dec.name.text);
192 		visitInterface(dec.name, dec.baseClassList, dec.structBody, false);
193 		context.restore(c);
194 	}
195 
196 	private void visitInterface(const Token name, const BaseClassList baseClassList,
197 			const StructBody structBody, bool needsOverride)
198 	{
199 		if (!structBody)
200 			return;
201 		if (inTarget)
202 			return; // ignore nested
203 
204 		if (targetPosition >= name.index && targetPosition < structBody.endLocation)
205 		{
206 			details.blockRange = [structBody.startLocation, structBody.endLocation + 1];
207 			details.name = name.text;
208 			if (baseClassList)
209 				foreach (base; baseClassList.items)
210 				{
211 					if (!base.type2 || !base.type2.typeIdentifierPart
212 							|| !base.type2.typeIdentifierPart.identifierOrTemplateInstance)
213 						continue;
214 					// TODO: template support!
215 					details.parents ~= astToString(base.type2);
216 					details.normalizedParents ~= astToString(base.type2);
217 					details.parentPositions ~= cast(
218 							int) base.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier.index + 1;
219 				}
220 			details.needsOverride = needsOverride;
221 			inTarget = true;
222 			structBody.accept(new NestedTypeFinder(&details, details.name));
223 			super.visit(structBody);
224 			inTarget = false;
225 		}
226 	}
227 
228 	override void visit(const FunctionDeclaration dec)
229 	{
230 		if (!inTarget)
231 			return;
232 
233 		size_t[2] definitionRange = [dec.name.index, 0];
234 		size_t[2] blockRange;
235 
236 		if (dec.returnType !is null && dec.returnType.tokens.length > 0)
237 			definitionRange[0] = dec.returnType.tokens[0].index;
238 
239 		if (dec.functionBody !is null && dec.functionBody.tokens.length > 0)
240 		{
241 			definitionRange[1] = dec.functionBody.tokens[0].index;
242 			blockRange = [
243 				dec.functionBody.tokens[0].index, dec.functionBody.tokens[$ - 1].index + 1
244 			];
245 		}
246 		else if (dec.parameters !is null && dec.parameters.tokens.length > 0)
247 			definitionRange[1] = dec.parameters.tokens[$ - 1].index
248 				+ dec.parameters.tokens[$ - 1].text.length;
249 
250 		auto origBody = (cast() dec).functionBody;
251 		const hasBody = !!origBody && origBody.missingFunctionBody is null;
252 		auto origComment = (cast() dec).comment;
253 		const implLevel = context.requiredImplementationLevel;
254 		const optionalImplementation = implLevel == 1 && !hasBody;
255 		const needsImplementation = implLevel == 9 || optionalImplementation;
256 		(cast() dec).functionBody = null;
257 		(cast() dec).comment = null;
258 		scope (exit)
259 		{
260 			(cast() dec).functionBody = origBody;
261 			(cast() dec).comment = origComment;
262 		}
263 		auto t = appender!string;
264 		formatTypeTransforming(t, dec, &resolveType);
265 		string method = context.localFormattedAttributes.chain([t.data.strip])
266 			.filter!(a => a.length > 0 && !a.among!("abstract", "final")).join(" ");
267 		ArgumentInfo[] arguments;
268 		if (dec.parameters)
269 			foreach (arg; dec.parameters.parameters)
270 				arguments ~= ArgumentInfo(astToString(arg), astToString(arg.type), arg.name.text);
271 		string returnType = dec.returnType ? resolveType(astToString(dec.returnType)) : "void";
272 
273 		// now visit to populate isNothrow, isNogc (before it would add to the localFormattedAttributes string)
274 		// also fills in used types
275 		super.visit(dec);
276 
277 		details.methods ~= MethodDetails(dec.name.text, method, returnType, arguments, context.isNothrowInContainer
278 				|| context.isNogcInContainer, hasBody, needsImplementation,
279 				optionalImplementation, definitionRange, blockRange);
280 	}
281 
282 	override void visit(const FunctionBody)
283 	{
284 	}
285 
286 	override void visit(const VariableDeclaration variable)
287 	{
288 		if (!inTarget)
289 			return;
290 		if (!variable.type)
291 			return;
292 		string type = astToString(variable.type);
293 		auto isPrivate = context.protectionType == tok!"private";
294 
295 		foreach (decl; variable.declarators)
296 			details.fields ~= FieldDetails(decl.name.text, type, isPrivate);
297 
298 		if (variable.type)
299 			variable.type.accept(this); // to fill in types
300 	}
301 
302 	override void visit(const TypeIdentifierPart type)
303 	{
304 		if (!inTarget)
305 			return;
306 
307 		if (type.identifierOrTemplateInstance && !type.typeIdentifierPart)
308 		{
309 			auto tok = type.identifierOrTemplateInstance.templateInstance
310 				? type.identifierOrTemplateInstance.templateInstance.identifier
311 				: type.identifierOrTemplateInstance.identifier;
312 
313 			usedType(ReferencedType(tok.text, tok.index));
314 		}
315 
316 		super.visit(type);
317 	}
318 
319 	alias visit = AttributesVisitor.visit;
320 
321 	protected void usedType(ReferencedType type)
322 	{
323 		// this is a simple sorted set insert
324 		auto sorted = assumeSorted!"a.name < b.name"(details.referencedTypes).trisect(type);
325 		if (sorted[1].length)
326 			return; // exists already
327 		details.referencedTypes.insertInPlace(sorted[0].length, type);
328 	}
329 
330 	string resolveType(const(char)[] inType)
331 	{
332 		auto parts = inType.splitter('.');
333 		string[] best;
334 		foreach (type; details.types)
335 			if ((!best.length || type.name.length < best.length) && type.name.endsWith(parts))
336 				best = type.name;
337 
338 		if (best.length)
339 			return best.join(".");
340 		else
341 			return inType.idup;
342 	}
343 
344 	const(char)[] code;
345 	bool inTarget;
346 	int targetPosition;
347 	InterfaceDetails details;
348 }
349 
350 class NestedTypeFinder : ASTVisitor
351 {
352 	this(InterfaceDetails* details, string start)
353 	{
354 		this.details = details;
355 		this.nested = [start];
356 	}
357 
358 	override void visit(const StructDeclaration dec)
359 	{
360 		handleType(TypeDetails.Type.struct_, dec.name.text, dec.name.index, dec);
361 	}
362 
363 	override void visit(const UnionDeclaration dec)
364 	{
365 		handleType(TypeDetails.Type.union_, dec.name.text, dec.name.index, dec);
366 	}
367 
368 	override void visit(const EnumDeclaration dec)
369 	{
370 		handleType(TypeDetails.Type.enum_, dec.name.text, dec.name.index, dec);
371 	}
372 
373 	override void visit(const ClassDeclaration dec)
374 	{
375 		handleType(TypeDetails.Type.class_, dec.name.text, dec.name.index, dec);
376 	}
377 
378 	override void visit(const InterfaceDeclaration dec)
379 	{
380 		handleType(TypeDetails.Type.interface_, dec.name.text, dec.name.index, dec);
381 	}
382 
383 	override void visit(const TemplateDeclaration dec)
384 	{
385 		handleType(TypeDetails.Type.template_, dec.name.text, dec.name.index, dec);
386 	}
387 
388 	override void visit(const AliasDeclaration dec)
389 	{
390 		if (dec && dec.declaratorIdentifierList)
391 			foreach (ident; dec.declaratorIdentifierList.identifiers)
392 				details.types ~= TypeDetails(nested ~ ident.text, ident.index, TypeDetails.Type.alias_);
393 	}
394 
395 	void handleType(T)(TypeDetails.Type type, string name, size_t location, T node)
396 	{
397 		pushNestedType(type, name, location);
398 		super.visit(node);
399 		popNestedType();
400 	}
401 
402 	override void visit(const FunctionBody)
403 	{
404 	}
405 
406 	alias visit = ASTVisitor.visit;
407 
408 	protected void pushNestedType(TypeDetails.Type type, string name, size_t index)
409 	{
410 		nested ~= name;
411 		details.types ~= TypeDetails(nested, index, type);
412 	}
413 
414 	protected void popNestedType()
415 	{
416 		nested.length--;
417 	}
418 
419 	string[] nested;
420 	InterfaceDetails* details;
421 }
422 
423 void formatTypeTransforming(Sink, T)(Sink sink, T node, string delegate(const(char)[]) translateType,
424 		bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWith = 4)
425 {
426 	TypeTransformingFormatter!Sink formatter = new TypeTransformingFormatter!(Sink)(sink,
427 			useTabs, style, indentWith);
428 	formatter.translateType = translateType;
429 	formatter.format(node);
430 }
431 
432 ///
433 class TypeTransformingFormatter(Sink) : Formatter!Sink
434 {
435 	string delegate(const(char)[]) translateType;
436 	Appender!(char[]) tempBuffer;
437 	bool useTempBuffer;
438 
439 	this(Sink sink, bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWidth = 4)
440 	{
441 		super(sink, useTabs, style, indentWidth);
442 		tempBuffer = appender!(char[]);
443 	}
444 
445 	override void put(string s)
446 	{
447 		if (useTempBuffer)
448 			tempBuffer.put(s);
449 		else
450 			super.put(s);
451 	}
452 
453 	protected void flushTempBuffer()
454 	{
455 		if (!useTempBuffer || tempBuffer.data.empty)
456 			return;
457 
458 		useTempBuffer = false;
459 		put(translateType(tempBuffer.data));
460 		tempBuffer.clear();
461 	}
462 
463 	override void format(const TypeIdentifierPart type)
464 	{
465 		useTempBuffer = true;
466 
467 		if (type.dot)
468 		{
469 			put(".");
470 		}
471 		if (type.identifierOrTemplateInstance)
472 		{
473 			format(type.identifierOrTemplateInstance);
474 		}
475 		if (type.indexer)
476 		{
477 			flushTempBuffer();
478 			put("[");
479 			format(type.indexer);
480 			put("]");
481 		}
482 		if (type.typeIdentifierPart)
483 		{
484 			put(".");
485 			format(type.typeIdentifierPart);
486 		}
487 		else
488 		{
489 			flushTempBuffer();
490 		}
491 	}
492 
493 	override void format(const IdentifierOrTemplateInstance identifierOrTemplateInstance)
494 	{
495 		with (identifierOrTemplateInstance)
496 		{
497 			format(identifier);
498 			if (templateInstance)
499 			{
500 				flushTempBuffer();
501 				format(templateInstance);
502 			}
503 		}
504 	}
505 
506 	alias format = Formatter!Sink.format;
507 }