1 /**
2  * D Documentation Generator
3  * Copyright: © 2014 Economic Modeling Specialists, Intl., © 2015 Ferdinand Majerech
4  * Authors: Brian Schott, Ferdinand Majerech
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0)
6  */
7 module visitor;
8 
9 import std.algorithm;
10 import std.array: appender, empty, array, popBack, back, popFront, front;
11 import dparse.ast;
12 import dparse.lexer;
13 import std.file;
14 import std.path;
15 import std.stdio;
16 import std.string: format, join;
17 import std.typecons;
18 
19 import config;
20 import ddoc.comments;
21 import item;
22 import symboldatabase;
23 import unittest_preprocessor;
24 import writer;
25 
26 /**
27  * Generates documentation for a (single) module.
28  */
29 class DocVisitor(Writer) : ASTVisitor
30 {
31 	/**
32 	 * Params:
33 	 *
34 	 * config          = Configuration data, including macros and the output directory.
35 	 * database        = Stores information about modules and symbols for e.g. cross-referencing.
36 	 * unitTestMapping = The mapping of declaration addresses to their documentation unittests
37 	 * fileBytes       = The source code of the module as a byte array.
38 	 * writer          = Handles writing into generated files.
39 	 */
40 	this(ref const Config config, SymbolDatabase database,
41 		 TestRange[][size_t] unitTestMapping, const(ubyte[]) fileBytes, Writer writer)
42 	{
43 		this.config          = &config;
44 		this.database        = database;
45 		this.unitTestMapping = unitTestMapping;
46 		this.fileBytes       = fileBytes;
47 		this.writer          = writer;
48 
49 		this.writer.processCode = &crossReference;
50 	}
51 
52 	override void visit(const Unittest){}
53 
54 	override void visit(const Module mod)
55 	{
56 		import std.conv : to;
57 		import std.range: join;
58 		assert(mod.moduleDeclaration !is null, "DataGatherVisitor should have caught this");
59 		pushAttributes();
60 		stack = cast(string[]) mod.moduleDeclaration.moduleName.identifiers.map!(a => a.text).array;
61 		writer.prepareModule(stack);
62 
63 		moduleName = stack.join(".").to!string;
64 
65 		scope(exit) { writer.finishModule(); }
66 
67 		// The module is the first and only top-level "symbol".
68 		bool dummyFirst;
69 		string link;
70 		auto fileWriter = writer.pushSymbol(stack, database, dummyFirst, link);
71 		scope(exit) { writer.popSymbol(); }
72 
73 		writer.writeHeader(fileWriter, moduleName, stack.length - 1);
74 		writer.writeBreadcrumbs(fileWriter, stack, database);
75 		writer.writeTOC(fileWriter, moduleName);
76 		writer.writeSymbolStart(fileWriter, link);
77 
78 		prevComments.length = 1;
79 
80 		const comment = mod.moduleDeclaration.comment;
81 		memberStack.length = 1;
82 
83 		mod.accept(this);
84 
85 		writer.writeSymbolDescription(fileWriter,
86 		{
87 			memberStack.back.writeImports(fileWriter, writer);
88 
89 			if (comment !is null)
90 			{
91 				writer.readAndWriteComment(fileWriter, comment, prevComments,
92 					null, getUnittestDocTuple(mod.moduleDeclaration));
93 			}
94 		});
95 
96 		memberStack.back.write(fileWriter, writer);
97 		writer.writeSymbolEnd(fileWriter);
98 	}
99 
100 	override void visit(const EnumDeclaration ed)
101 	{
102 		enum formattingCode = q{
103 		fileWriter.put("enum " ~ ad.name.text);
104 		if (ad.type !is null)
105 		{
106 			fileWriter.put(" : ");
107 			formatter.format(ad.type);
108 		}
109 		};
110 		visitAggregateDeclaration!(formattingCode, "enums")(ed);
111 	}
112 
113 	override void visit(const EnumMember member)
114 	{
115 		memberStack.back.values ~= Item("#", member.name.text, member.comment);
116 	}
117 
118 	override void visit(const ClassDeclaration cd)
119 	{
120 		enum formattingCode = q{
121 		fileWriter.put("class " ~ ad.name.text);
122 		if (ad.templateParameters !is null)
123 			formatter.format(ad.templateParameters);
124 		if (ad.baseClassList !is null)
125 			formatter.format(ad.baseClassList);
126 		if (ad.constraint !is null)
127 			formatter.format(ad.constraint);
128 		};
129 		visitAggregateDeclaration!(formattingCode, "classes")(cd);
130 	}
131 
132 	override void visit(const TemplateDeclaration td)
133 	{
134 		enum formattingCode = q{
135 		fileWriter.put("template " ~ ad.name.text);
136 		if (ad.templateParameters !is null)
137 			formatter.format(ad.templateParameters);
138 		if (ad.constraint)
139 			formatter.format(ad.constraint);
140 		};
141 		visitAggregateDeclaration!(formattingCode, "templates")(td);
142 	}
143 
144 	override void visit(const StructDeclaration sd)
145 	{
146 		enum formattingCode = q{
147 		fileWriter.put("struct " ~ ad.name.text);
148 		if (ad.templateParameters)
149 			formatter.format(ad.templateParameters);
150 		if (ad.constraint)
151 			formatter.format(ad.constraint);
152 		};
153 		visitAggregateDeclaration!(formattingCode, "structs")(sd);
154 	}
155 
156 	override void visit(const InterfaceDeclaration id)
157 	{
158 		enum formattingCode = q{
159 		fileWriter.put("interface " ~ ad.name.text);
160 		if (ad.templateParameters !is null)
161 			formatter.format(ad.templateParameters);
162 		if (ad.baseClassList !is null)
163 			formatter.format(ad.baseClassList);
164 		if (ad.constraint !is null)
165 			formatter.format(ad.constraint);
166 		};
167 		visitAggregateDeclaration!(formattingCode, "interfaces")(id);
168 	}
169 
170 	override void visit(const AliasDeclaration ad)
171 	{
172 		if (ad.comment is null)
173 			return;
174 		bool first;
175 		if (ad.declaratorIdentifierList !is null)
176 			foreach (name; ad.declaratorIdentifierList.identifiers)
177 		{
178 			string itemURL;
179 			auto fileWriter = pushSymbol(name.text, first, itemURL);
180 			scope(exit) popSymbol(fileWriter);
181 
182 			string type, summary;
183 			writer.writeSymbolDescription(fileWriter,
184 			{
185 				type = writeAliasType(fileWriter, name.text, ad.type);
186 				summary = writer.readAndWriteComment(fileWriter, ad.comment, prevComments);
187 			});
188 
189 			memberStack[$ - 2].aliases ~= Item(itemURL, name.text, summary, type);
190 		}
191 		else foreach (initializer; ad.initializers)
192 		{
193 			string itemURL;
194 			auto fileWriter = pushSymbol(initializer.name.text, first, itemURL);
195 			scope(exit) popSymbol(fileWriter);
196 
197 			string type, summary;
198 			writer.writeSymbolDescription(fileWriter,
199 			{
200 				type = writeAliasType(fileWriter, initializer.name.text, initializer.type);
201 				summary = writer.readAndWriteComment(fileWriter, ad.comment, prevComments);
202 			});
203 
204 			memberStack[$ - 2].aliases ~= Item(itemURL, initializer.name.text, summary, type);
205 		}
206 	}
207 
208 	override void visit(const VariableDeclaration vd)
209 	{
210 		// Write the variable attributes, type, name.
211 		void writeVariableHeader(R)(ref R dst, string typeStr, string nameStr, string defStr = "")
212 		{
213 			writer.writeCodeBlock(dst,
214 			{
215 				assert(attributeStack.length > 0,
216 					"Attributes stack must not be empty when writing variable attributes");
217 				auto formatter = writer.newFormatter(dst);
218 				scope(exit) { destroy(formatter.sink); }
219 				// Attributes like public, etc.
220 				writeAttributes(dst, formatter, attributeStack.back);
221 				dst.put(typeStr);
222 				dst.put(` `);
223 				dst.put(nameStr);
224 				dst.put(defStr);
225 			});
226 		}
227 		bool first;
228 		foreach (const Declarator dec; vd.declarators)
229 		{
230 			if (vd.comment is null && dec.comment is null)
231 				continue;
232 			string itemURL;
233 			auto fileWriter = pushSymbol(dec.name.text, first, itemURL);
234 			scope(exit) popSymbol(fileWriter);
235 
236 			string typeStr = writer.formatNode(vd.type);
237 			string summary;
238 			writer.writeSymbolDescription(fileWriter,
239 			{
240 				string defStr;
241 				if (dec.initializer)
242 				{
243 					import dparse.formatter : fmt = format;
244 					import std.array : Appender;
245 					Appender!string app;
246 					app.put(" = ");
247 					fmt(&app, dec.initializer);
248 					defStr = app.data;
249 				}
250 				writeVariableHeader(fileWriter, typeStr, dec.name.text, defStr);
251 				summary = writer.readAndWriteComment(fileWriter,
252 					dec.comment is null ? vd.comment : dec.comment,
253 					prevComments);
254 			});
255 
256 			memberStack[$ - 2].variables ~= Item(itemURL, dec.name.text, summary, typeStr, dec);
257 		}
258 		if (vd.comment !is null && vd.autoDeclaration !is null)
259 		{
260 			foreach (part; vd.autoDeclaration.parts) with (part)
261 			{
262 				string itemURL;
263 				auto fileWriter = pushSymbol(identifier.text, first, itemURL);
264 				scope(exit) popSymbol(fileWriter);
265 
266 				// TODO this was hastily updated to get harbored-mod to compile
267 				// after a libdparse update. Revisit and validate/fix any errors.
268 				string[] storageClasses;
269 				vd.storageClasses.each!(stor => storageClasses ~= str(stor.token.type));
270 
271 				string typeStr = storageClasses.canFind("enum") ? null : "auto";
272 				string summary;
273 				writer.writeSymbolDescription(fileWriter,
274 				{
275 					writeVariableHeader(fileWriter, typeStr, identifier.text);
276 					summary = writer.readAndWriteComment(fileWriter, vd.comment, prevComments);
277 				});
278 				auto i = Item(itemURL, identifier.text, summary, typeStr);
279 				if (storageClasses.canFind("enum"))
280 					memberStack[$ - 2].enums ~= i;
281 				else
282 					memberStack[$ - 2].variables ~= i;
283 
284 				// string storageClass;
285 				// foreach (attr; vd.attributes)
286 				// {
287 				// 	if (attr.storageClass !is null)
288 				// 		storageClass = str(attr.storageClass.token.type);
289 				// }
290 				// auto i = Item(name, ident.text,
291 				// 	summary, storageClass == "enum" ? null : "auto");
292 				// if (storageClass == "enum")
293 				// 	memberStack[$ - 2].enums ~= i;
294 				// else
295 				// 	memberStack[$ - 2].variables ~= i;
296 			}
297 		}
298 	}
299 
300 	override void visit(const StructBody sb)
301 	{
302 		pushAttributes();
303 		sb.accept(this);
304 		popAttributes();
305 	}
306 
307 	override void visit(const BlockStatement bs)
308 	{
309 		pushAttributes();
310 		bs.accept(this);
311 		popAttributes();
312 	}
313 
314 	override void visit(const Declaration dec)
315 	{
316 		attributeStack.back ~= dec.attributes;
317 		dec.accept(this);
318 		if (dec.attributeDeclaration is null)
319 			attributeStack.back = attributeStack.back[0 .. $ - dec.attributes.length];
320 	}
321 
322 	override void visit(const AttributeDeclaration dec)
323 	{
324 		attributeStack.back ~= dec.attribute;
325 	}
326 
327 	override void visit(const Constructor cons)
328 	{
329 		if (cons.comment is null)
330 			return;
331 		writeFnDocumentation("this", cons, attributeStack.back);
332 	}
333 
334 	override void visit(const FunctionDeclaration fd)
335 	{
336 		if (fd.comment is null)
337 			return;
338 		writeFnDocumentation(fd.name.text, fd, attributeStack.back);
339 	}
340 
341 	override void visit(const ImportDeclaration imp)
342 	{
343 		// public attribute must be specified explicitly for public imports.
344 		foreach (attr; attributeStack.back)
345 			if (attr.attribute.type == tok!"public")
346 		{
347 			foreach (i; imp.singleImports)
348 			{
349 				const nameParts = i.identifierChain.identifiers.map!(t => t.text).array;
350 				const name = nameParts.join(".");
351 				const knownModule = database.moduleNames.canFind(name);
352 				const link = knownModule ? writer.moduleLink(nameParts) : null;
353 				memberStack.back.publicImports ~=
354 					Item(link, name, null, null, imp);
355 			}
356 			return;
357 		}
358 		//TODO handle imp.importBindings as well? Need to figure out how it works.
359 	}
360 
361 	// Optimization: don't allow visit() for these AST nodes to result in visit()
362 	// calls for their subnodes. This avoids most of the dynamic cast overhead.
363 	override void visit(const AssignExpression assignExpression) {}
364 	override void visit(const CmpExpression cmpExpression) {}
365 	override void visit(const TernaryExpression ternaryExpression) {}
366 	override void visit(const IdentityExpression identityExpression) {}
367 	override void visit(const InExpression inExpression) {}
368 
369 	alias visit = ASTVisitor.visit;
370 
371 private:
372 	/// Get the current protection attribute.
373 	IdType currentProtection()
374 	out(result)
375 	{
376 		assert([tok!"private", tok!"package", tok!"protected", tok!"public"].canFind(result),
377 			   "Unknown protection attribute");
378 	}
379 	body
380 	{
381 		foreach (a; attributeStack.back.filter!(a => a.attribute.type.isProtection))
382 		{
383 			return a.attribute.type;
384 		}
385 		return tok!"public";
386 	}
387 
388 	/** Writes attributes to the range dst using formatter to format code.
389 	 *
390 	 * Params:
391 	 *
392 	 * dst       = Range to write to.
393 	 * formatter = Formatter to format the attributes with.
394 	 * attrs     = Attributes to write.
395 	 */
396 	void writeAttributes(R, F)(ref R dst, F formatter, const(Attribute)[] attrs)
397 	{
398 		import dparse.lexer : isProtection, tok;
399 		switch (currentProtection())
400 		{
401 			case tok!"private":   dst.put("private ");   break;
402 			case tok!"package":   dst.put("package ");   break;
403 			case tok!"protected": dst.put("protected "); break;
404 			default:              dst.put("public ");    break;
405 		}
406 		foreach (a; attrs.filter!(a => !a.attribute.type.isProtection))
407 		{
408 			formatter.format(a);
409 			dst.put(" ");
410 		}
411 	}
412 
413 
414 	void visitAggregateDeclaration(string formattingCode, string name, A)(const A ad)
415 	{
416 		bool first;
417 		if (ad.comment is null)
418 			return;
419 
420 		string itemURL;
421 		auto fileWriter = pushSymbol(ad.name.text, first, itemURL);
422 		scope(exit) popSymbol(fileWriter);
423 
424 		string summary;
425 		writer.writeSymbolDescription(fileWriter,
426 		{
427 			writer.writeCodeBlock(fileWriter,
428 			{
429 				auto formatter = writer.newFormatter(fileWriter);
430 				scope(exit) destroy(formatter.sink);
431 				assert(attributeStack.length > 0,
432 					"Attributes stack must not be empty when writing aggregate attributes");
433 				writeAttributes(fileWriter, formatter, attributeStack.back);
434 				mixin(formattingCode);
435 			});
436 
437 			summary = writer.readAndWriteComment(fileWriter, ad.comment, prevComments,
438 				null, getUnittestDocTuple(ad));
439 		});
440 
441 		mixin(`memberStack[$ - 2].` ~ name ~ ` ~= Item(itemURL, ad.name.text, summary);`);
442 
443 		prevComments.length = prevComments.length + 1;
444 		ad.accept(this);
445 		prevComments.popBack();
446 
447 		memberStack.back.write(fileWriter, writer);
448 	}
449 
450 	/**
451 	 * Params:
452 	 *     t = The declaration.
453 	 * Returns: An array of tuples where the first item is the contents of the
454 	 *     unittest block and the second item is the doc comment for the
455 	 *     unittest block. This array may be empty.
456 	 */
457 	Tuple!(string, string)[] getUnittestDocTuple(T)(const T t)
458 	{
459 		immutable size_t index = cast(size_t) (cast(void*) t);
460 //		writeln("Searching for unittest associated with ", index);
461 		auto tupArray = index in unitTestMapping;
462 		if (tupArray is null)
463 			return [];
464 //		writeln("Found a doc unit test for ", cast(size_t) &t);
465 		Tuple!(string, string)[] rVal;
466 		foreach (tup; *tupArray)
467 			rVal ~= tuple(cast(string) fileBytes[tup[0] + 2 .. tup[1]], tup[2]);
468 		return rVal;
469 	}
470 
471 	/**
472 	 *
473 	 */
474 	void writeFnDocumentation(Fn)(string name, Fn fn, const(Attribute)[] attrs)
475 	{
476 		bool first;
477 		string itemURL;
478 		auto fileWriter = pushSymbol(name, first, itemURL);
479 		scope(exit) popSymbol(fileWriter);
480 
481 		string summary;
482 		writer.writeSymbolDescription(fileWriter,
483 		{
484 			auto formatter = writer.newFormatter(fileWriter);
485 			scope(exit) destroy(formatter.sink);
486 
487 			// Write the function signature.
488 			writer.writeCodeBlock(fileWriter,
489 			{
490 				assert(attributeStack.length > 0,
491 					   "Attributes stack must not be empty when writing " ~
492 					   "function attributes");
493 				// Attributes like public, etc.
494 				writeAttributes(fileWriter, formatter, attrs);
495 				// Return type and function name, with special case fo constructor
496 				static if (__traits(hasMember, typeof(fn), "returnType"))
497 				{
498 					if (fn.returnType)
499 					{
500 						formatter.format(fn.returnType);
501 						fileWriter.put(" ");
502 					}
503 					formatter.format(fn.name);
504 				}
505 				else
506 				{
507 					fileWriter.put("this");
508 				}
509 				// Template params
510 				if (fn.templateParameters !is null)
511 					formatter.format(fn.templateParameters);
512 				// Function params
513 				if (fn.parameters !is null)
514 					formatter.format(fn.parameters);
515 				// Attributes like const, nothrow, etc.
516 				foreach (a; fn.memberFunctionAttributes)
517 				{
518 					fileWriter.put(" ");
519 					formatter.format(a);
520 				}
521 				// Template constraint
522 				if (fn.constraint)
523 				{
524 					fileWriter.put(" ");
525 					formatter.format(fn.constraint);
526 				}
527 			});
528 
529 			summary = writer.readAndWriteComment(fileWriter, fn.comment,
530 				prevComments, fn.functionBody, getUnittestDocTuple(fn));
531 		});
532 		string fdName;
533 		static if (__traits(hasMember, typeof(fn), "name"))
534 			fdName = fn.name.text;
535 		else
536 			fdName = "this";
537 		auto fnItem = Item(itemURL, fdName, summary, null, fn);
538 		memberStack[$ - 2].functions ~= fnItem;
539 		prevComments.length = prevComments.length + 1;
540 		fn.accept(this);
541 
542 		// The function may have nested functions/classes/etc, so at the very
543 		// least we need to close their files, and once public/private works even
544 		// document them.
545 		memberStack.back.write(fileWriter, writer);
546 		prevComments.popBack();
547 	}
548 
549 	/**
550 	 * Writes an alias' type to the given range and returns it.
551 	 * Params:
552 	 *     dst  = The range to write to
553 	 *     name = the name of the alias
554 	 *     t    = the aliased type
555 	 * Returns: A string reperesentation of the given type.
556 	 */
557 	string writeAliasType(R)(ref R dst, string name, const Type t)
558 	{
559 		if (t is null)
560 			return null;
561 		string formatted = writer.formatNode(t);
562 		writer.writeCodeBlock(dst,
563 		{
564 			dst.put("alias %s = ".format(name));
565 			dst.put(formatted);
566 		});
567 		return formatted;
568 	}
569 
570 
571 	/** Generate links from symbols in input to files documenting those symbols.
572 	 *
573 	 * Note: The current implementation is far from perfect. It doesn't try to parse
574 	 * input; it just searches for alphanumeric words and patterns like
575 	 * "alnumword.otheralnumword" and asks SymbolDatabase to find a reference to them.
576 	 *
577 	 * TODO: Improve this by trying to parse input as D code first, only falling back
578 	 * to current implementation if the parsing fails. Parsing would only be used to
579 	 * correctly detect names, but must not reformat any code from input.
580 	 *
581 	 * Params:
582 	 *
583 	 * input = String to find symbols in.
584 	 *
585 	 * Returns:
586 	 *
587 	 * string with symbols replaced by links (links' format depends on Writer).
588 	 */
589 	string crossReference(string input) @trusted nothrow
590 	{
591 		import std.ascii : isAlphaNum;
592 		bool isNameCharacter(dchar c)
593 		{
594 			char c8 = cast(char)c;
595 			return c8 == c && (c8.isAlphaNum || "_.".canFind(c8));
596 		}
597 
598 		auto app = appender!string();
599 		dchar prevC = '\0';
600 		dchar c;
601 
602 		// Scan a symbol name. When done, both c and input.front will be set to
603 		// the first character after the name.
604 		string scanName()
605 		{
606 			auto scanApp = appender!string();
607 			while(!input.empty)
608 			{
609 				c = input.front;
610 				if (!isNameCharacter(c) && isNameCharacter(prevC))
611 					break;
612 
613 				scanApp.put(c);
614 				prevC = c;
615 				input.popFront();
616 			}
617 			return scanApp.data;
618 		}
619 
620 		// There should be no UTF decoding errors as we validate text when loading
621 		// with std.file.readText().
622 		try while(!input.empty)
623 		{
624 			c = input.front;
625 			if (isNameCharacter(c) && !isNameCharacter(prevC))
626 			{
627 				string name = scanName();
628 
629 				auto link = database.crossReference(writer, stack, name);
630 				size_t partIdx = 0;
631 
632 				if (link.length)
633 					writer.writeLink(app, link, { app.put(name); });
634 				// Attempt to cross-reference individual parts of the name
635 				// (e.g. "variable.method" will not match anything if
636 				// "variable" is a local variable "method" by itself may
637 				// still match something)
638 				else foreach (part; name.splitter("."))
639 				{
640 					if (partIdx++ > 0)
641 						app.put(".");
642 
643 					link = database.crossReference(writer, stack, part);
644 					if (link.length)
645 						writer.writeLink(app, link, { app.put(part); });
646 					else
647 						app.put(part);
648 				}
649 			}
650 
651 			if (input.empty)
652 				break;
653 
654 			// Even if scanName was called above, c is the first character
655 			// *after* scanName.
656 			app.put(c);
657 			prevC = c;
658 			// Must check again because scanName might have exhausted the input.
659 			input.popFront();
660 		}
661 		catch(Exception e)
662 		{
663 			import std.exception: assumeWontThrow;
664 			writeln("Unexpected exception when cross-referencing: ", e.msg)
665 				.assumeWontThrow;
666 		}
667 
668 		return app.data;
669 	}
670 
671 	/**
672 	 * Params:
673 	 *
674 	 * name    = The symbol's name
675 	 * first   = Set to true if this is the first time that pushSymbol has been
676 	 *           called for this name.
677 	 * itemURL = URL to use in the Item for this symbol will be written here.
678 	 *
679 	 * Returns: A range to write the symbol's documentation to.
680 	 */
681 	auto pushSymbol(string name, ref bool first, ref string itemURL)
682 	{
683 		import std.array : array, join;
684 		import std.string : format;
685 		stack ~= name;
686 		memberStack.length = memberStack.length + 1;
687 
688 		// Sets first
689 		auto result = writer.pushSymbol(stack, database, first, itemURL);
690 
691 		if (first)
692 		{
693 			writer.writeHeader(result, name, writer.moduleNameLength);
694 			writer.writeBreadcrumbs(result, stack, database);
695 			writer.writeTOC(result, moduleName);
696 		}
697 		else
698 		{
699 			writer.writeSeparator(result);
700 		}
701 		writer.writeSymbolStart(result, itemURL);
702 		return result;
703 	}
704 
705 	void popSymbol(R)(ref R dst)
706 	{
707 		writer.writeSymbolEnd(dst);
708 		stack.popBack();
709 		memberStack.popBack();
710 		writer.popSymbol();
711 	}
712 
713 	void pushAttributes() { attributeStack.length = attributeStack.length + 1; }
714 
715 	void popAttributes() { attributeStack.popBack(); }
716 
717 
718 	/// The module name in "package.package.module" format.
719 	string moduleName;
720 
721 	const(Attribute)[][] attributeStack;
722 	Comment[] prevComments;
723 	/** Namespace stack of the current symbol,
724 	 *
725 	 * E.g. ["package", "subpackage", "module", "Class", "member"]
726 	 */
727 	string[] stack;
728 	/** Every item of this stack corresponds to a parent module/class/etc of the
729 	 * current symbol, but not package.
730 	 *
731 	 * Each Members struct is used to accumulate all members of that module/class/etc
732 	 * so the list of all members can be generated.
733 	 */
734 	Members[] memberStack;
735 	TestRange[][size_t] unitTestMapping;
736 	const(ubyte[]) fileBytes;
737 	const(Config)* config;
738 	/// Information about modules and symbols for e.g. cross-referencing.
739 	SymbolDatabase database;
740 	Writer writer;
741 }