1 module dsymbol.tests;
2 
3 import std.experimental.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 non-importable symbols tests...");
78     auto source = q{
79         class A { this(int a){} }
80         class B : A {}
81         class C { A f; alias f this; }
82     };
83     auto pair = generateAutocompleteTrees(source, cache);
84     auto A = pair.symbol.getFirstPartNamed(internString("A"));
85     auto B = pair.symbol.getFirstPartNamed(internString("B"));
86     auto C = pair.symbol.getFirstPartNamed(internString("C"));
87     assert(A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) !is null);
88     assert(B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
89     assert(C.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
90 }
91 
92 static StringCache stringCache = void;
93 static this()
94 {
95     stringCache = StringCache(StringCache.defaultBucketCount);
96 }
97 
98 const(Token)[] lex(string source)
99 {
100     return lex(source, null);
101 }
102 
103 const(Token)[] lex(string source, string filename)
104 {
105     import dparse.lexer : getTokensForParser;
106     import std..string : representation;
107     LexerConfig config;
108     config.fileName = filename;
109     return getTokensForParser(source.dup.representation, config, &stringCache);
110 }
111 
112 unittest
113 {
114     auto tokens = lex(q{int a = 9;});
115     foreach(i, t;
116         cast(IdType[]) [tok!"int", tok!"identifier", tok!"=", tok!"intLiteral", tok!";"])
117     {
118         assert(tokens[i] == t);
119     }
120     assert(tokens[1].text == "a", tokens[1].text);
121     assert(tokens[3].text == "9", tokens[3].text);
122 }
123 
124 string randomDFilename()
125 {
126     import std.uuid : randomUUID;
127     return "dsymbol_" ~ randomUUID().toString() ~ ".d";
128 }
129 
130 ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache)
131 {
132     return generateAutocompleteTrees(source, randomDFilename, cache);
133 }
134 
135 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache)
136 {
137     auto tokens = lex(source);
138     RollbackAllocator rba;
139     Module m = parseModule(tokens, filename, &rba);
140 
141 	auto first = scoped!FirstPass(m, internString(filename),
142             theAllocator, theAllocator, true, &cache);
143 	first.run();
144 
145 	secondPass(first.rootSymbol, first.moduleScope, cache);
146 	auto r = first.rootSymbol.acSymbol;
147 	typeid(SemanticSymbol).destroy(first.rootSymbol);
148 	return ScopeSymbolPair(r, first.moduleScope);
149 }
150 
151 ScopeSymbolPair generateAutocompleteTrees(string source, size_t cursorPosition, ref ModuleCache cache)
152 {
153     return generateAutocompleteTrees(source, null, cache);
154 }
155 
156 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
157 {
158     auto tokens = lex(source);
159     RollbackAllocator rba;
160     return dsymbol.conversion.generateAutocompleteTrees(
161             tokens, theAllocator, &rba, cursorPosition, cache);
162 }