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 static StringCache stringCache = void;
134 static this()
135 {
136     stringCache = StringCache(StringCache.defaultBucketCount);
137 }
138 
139 const(Token)[] lex(string source)
140 {
141     return lex(source, null);
142 }
143 
144 const(Token)[] lex(string source, string filename)
145 {
146     import dparse.lexer : getTokensForParser;
147     import std..string : representation;
148     LexerConfig config;
149     config.fileName = filename;
150     return getTokensForParser(source.dup.representation, config, &stringCache);
151 }
152 
153 unittest
154 {
155     auto tokens = lex(q{int a = 9;});
156     foreach(i, t;
157         cast(IdType[]) [tok!"int", tok!"identifier", tok!"=", tok!"intLiteral", tok!";"])
158     {
159         assert(tokens[i] == t);
160     }
161     assert(tokens[1].text == "a", tokens[1].text);
162     assert(tokens[3].text == "9", tokens[3].text);
163 }
164 
165 string randomDFilename()
166 {
167     import std.uuid : randomUUID;
168     return "dsymbol_" ~ randomUUID().toString() ~ ".d";
169 }
170 
171 ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache)
172 {
173     return generateAutocompleteTrees(source, randomDFilename, cache);
174 }
175 
176 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache)
177 {
178     auto tokens = lex(source);
179     RollbackAllocator rba;
180     Module m = parseModule(tokens, filename, &rba);
181 
182     auto first = scoped!FirstPass(m, internString(filename),
183             theAllocator, theAllocator, true, &cache);
184     first.run();
185 
186     secondPass(first.rootSymbol, first.moduleScope, cache);
187     auto r = first.rootSymbol.acSymbol;
188     typeid(SemanticSymbol).destroy(first.rootSymbol);
189     return ScopeSymbolPair(r, first.moduleScope);
190 }
191 
192 ScopeSymbolPair generateAutocompleteTrees(string source, size_t cursorPosition, ref ModuleCache cache)
193 {
194     return generateAutocompleteTrees(source, null, cache);
195 }
196 
197 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
198 {
199     auto tokens = lex(source);
200     RollbackAllocator rba;
201     return dsymbol.conversion.generateAutocompleteTrees(
202         tokens, theAllocator, &rba, cursorPosition, cache);
203 }