1 module dsymbol.tests;
2 
3 import stdx.allocator;
4 import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator;
5 import dsymbol.cache_entry, dsymbol.modulecache, dsymbol.symbol;
6 import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second;
7 import dsymbol.semantic, dsymbol.string_interning, dsymbol.builtin.names;
8 import std.file, std.path, std.format;
9 import std.stdio : writeln, stdout;
10 import std.typecons : scoped;
11 
12 /**
13  * Parses `source`, caches its symbols and compares the the cache content
14  * with the `results`.
15  *
16  * Params:
17  *      source = The source code to test.
18  *      results = An array of string array. Each slot represents the variable name
19  *      followed by the type strings.
20  */
21 version (unittest):
22 void expectSymbolsAndTypes(const string source, const string[][] results,
23 	string file = __FILE_FULL_PATH__, size_t line = __LINE__)
24 {
25 	import core.exception : AssertError;
26 	import std.exception : enforce;
27 
28 	ModuleCache mcache = ModuleCache(theAllocator);
29 	auto pair = generateAutocompleteTrees(source, mcache);
30 	scope(exit) pair.destroy();
31 
32 	size_t i;
33 	foreach(ss; (*pair.symbol)[])
34 	{
35 		if (ss.type)
36 		{
37 			enforce!AssertError(i <= results.length, "not enough results", file, line);
38 			enforce!AssertError(results[i].length > 1,
39 				"at least one type must be present in a result row", file, line);
40 			enforce!AssertError(ss.name == results[i][0],
41 				"expected variableName: `%s` but got `%s`".format(results[i][0], ss.name),
42 				file, line);
43 
44 			auto t = cast() ss.type;
45 			foreach (immutable j; 1..results[i].length)
46 			{
47 				enforce!AssertError(t != null, "null symbol", file, line);
48 				enforce!AssertError(t.name == results[i][j],
49 					"expected typeName: `%s` but got `%s`".format(results[i][j], t.name),
50 					file, line);
51 				if (t.type is t && t.name.length && t.name[0] != '*')
52 					break;
53 				t = t.type;
54 			}
55 			i++;
56 		}
57 	}
58 }
59 
60 @system unittest
61 {
62 	writeln("Running type deduction tests...");
63 	q{bool b; int i;}.expectSymbolsAndTypes([["b", "bool"],["i", "int"]]);
64 	q{auto b = false;}.expectSymbolsAndTypes([["b", "bool"]]);
65 	q{auto b = true;}.expectSymbolsAndTypes([["b", "bool"]]);
66 	q{auto b = [0];}.expectSymbolsAndTypes([["b", "*arr*", "int"]]);
67 	q{auto b = [[0]];}.expectSymbolsAndTypes([["b", "*arr*", "*arr*", "int"]]);
68 	q{auto b = [[[0]]];}.expectSymbolsAndTypes([["b", "*arr*", "*arr*", "*arr*", "int"]]);
69 	//q{int* b;}.expectSymbolsAndTypes([["b", "*", "int"]]);
70 	//q{int*[] b;}.expectSymbolsAndTypes([["b", "*arr*", "*", "int"]]);
71 }
72 
73 unittest
74 {
75 	ModuleCache cache = ModuleCache(theAllocator);
76 
77 	writeln("Running struct constructor tests...");
78 	auto source = q{ struct A {int a; struct B {bool b;} int c;} };
79 	auto pair = generateAutocompleteTrees(source, cache);
80 	auto A = pair.symbol.getFirstPartNamed(internString("A"));
81 	auto B = A.getFirstPartNamed(internString("B"));
82 	auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
83 	auto BCtor = B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
84 	assert(ACtor.callTip == "this(int a, int c)");
85 	assert(BCtor.callTip == "this(bool b)");
86 }
87 
88 unittest
89 {
90 	ModuleCache cache = ModuleCache(theAllocator);
91 
92 	writeln("Running union constructor tests...");
93 	auto source = q{ union A {int a; bool b;} };
94 	auto pair = generateAutocompleteTrees(source, cache);
95 	auto A = pair.symbol.getFirstPartNamed(internString("A"));
96 	auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
97 	assert(ACtor.callTip == "this(int a, bool b)");
98 }
99 
100 unittest
101 {
102 	ModuleCache cache = ModuleCache(theAllocator);
103 	writeln("Running non-importable symbols tests...");
104 	auto source = q{
105 		class A { this(int a){} }
106 		class B : A {}
107 		class C { A f; alias f this; }
108 	};
109 	auto pair = generateAutocompleteTrees(source, cache);
110 	auto A = pair.symbol.getFirstPartNamed(internString("A"));
111 	auto B = pair.symbol.getFirstPartNamed(internString("B"));
112 	auto C = pair.symbol.getFirstPartNamed(internString("C"));
113 	assert(A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) !is null);
114 	assert(B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
115 	assert(C.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
116 }
117 
118 unittest
119 {
120 	ModuleCache cache = ModuleCache(theAllocator);
121 
122 	writeln("Running alias this tests...");
123 	auto source = q{ struct A {int f;} struct B { A a; alias a this; void fun() { auto var = f; };} };
124 	auto pair = generateAutocompleteTrees(source, cache);
125 	auto A = pair.symbol.getFirstPartNamed(internString("A"));
126 	auto B = pair.symbol.getFirstPartNamed(internString("B"));
127 	auto Af = A.getFirstPartNamed(internString("f"));
128 	auto fun = B.getFirstPartNamed(internString("fun"));
129 	auto var = fun.getFirstPartNamed(internString("var"));
130 	assert(Af is pair.scope_.getFirstSymbolByNameAndCursor(internString("f"), var.location));
131 }
132 
133 unittest
134 {
135 	ModuleCache cache = ModuleCache(theAllocator);
136 
137 	writeln("Running `super` tests...");
138 	auto source = q{ class A {} class B : A {} };
139 	auto pair = generateAutocompleteTrees(source, cache);
140 	assert(pair.symbol);
141 	auto A = pair.symbol.getFirstPartNamed(internString("A"));
142 	auto B = pair.symbol.getFirstPartNamed(internString("B"));
143 	auto scopeA = (pair.scope_.getScopeByCursor(A.location + A.name.length));
144 	auto scopeB = (pair.scope_.getScopeByCursor(B.location + B.name.length));
145 	assert(scopeA !is scopeB);
146 
147 	assert(!scopeA.getSymbolsByName(SUPER_SYMBOL_NAME).length);
148 	assert(scopeB.getSymbolsByName(SUPER_SYMBOL_NAME)[0].type is A);
149 }
150 
151 static StringCache stringCache = void;
152 static this()
153 {
154 	stringCache = StringCache(StringCache.defaultBucketCount);
155 }
156 
157 const(Token)[] lex(string source)
158 {
159 	return lex(source, null);
160 }
161 
162 const(Token)[] lex(string source, string filename)
163 {
164 	import dparse.lexer : getTokensForParser;
165 	import std.string : representation;
166 	LexerConfig config;
167 	config.fileName = filename;
168 	return getTokensForParser(source.dup.representation, config, &stringCache);
169 }
170 
171 unittest
172 {
173 	auto tokens = lex(q{int a = 9;});
174 	foreach(i, t;
175 		cast(IdType[]) [tok!"int", tok!"identifier", tok!"=", tok!"intLiteral", tok!";"])
176 	{
177 		assert(tokens[i] == t);
178 	}
179 	assert(tokens[1].text == "a", tokens[1].text);
180 	assert(tokens[3].text == "9", tokens[3].text);
181 }
182 
183 string randomDFilename()
184 {
185 	import std.uuid : randomUUID;
186 	return "dsymbol_" ~ randomUUID().toString() ~ ".d";
187 }
188 
189 ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache)
190 {
191 	return generateAutocompleteTrees(source, randomDFilename, cache);
192 }
193 
194 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache)
195 {
196 	auto tokens = lex(source);
197 	RollbackAllocator rba;
198 	Module m = parseModule(tokens, filename, &rba);
199 
200 	auto first = scoped!FirstPass(m, internString(filename),
201 			theAllocator, theAllocator, true, &cache);
202 	first.run();
203 
204 	secondPass(first.rootSymbol, first.moduleScope, cache);
205 	auto r = first.rootSymbol.acSymbol;
206 	typeid(SemanticSymbol).destroy(first.rootSymbol);
207 	return ScopeSymbolPair(r, first.moduleScope);
208 }
209 
210 ScopeSymbolPair generateAutocompleteTrees(string source, size_t cursorPosition, ref ModuleCache cache)
211 {
212 	return generateAutocompleteTrees(source, null, cache);
213 }
214 
215 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
216 {
217 	auto tokens = lex(source);
218 	RollbackAllocator rba;
219 	return dsymbol.conversion.generateAutocompleteTrees(
220 		tokens, theAllocator, &rba, cursorPosition, cache);
221 }