1 // Distributed under the Boost Software License, Version 1.0.
2 //    (See accompanying file LICENSE_1_0.txt or copy at
3 //          http://www.boost.org/LICENSE_1_0.txt)
4 
5 module analysis.imports_sortedness;
6 
7 import analysis.base : BaseAnalyzer;
8 import dparse.lexer;
9 import dparse.ast;
10 
11 import std.stdio;
12 
13 /**
14  * Checks the sortedness of module imports
15  */
16 class ImportSortednessCheck : BaseAnalyzer
17 {
18 	enum string KEY = "dscanner.style.imports_sortedness";
19 	enum string MESSAGE = "The imports are not sorted in alphabetical order";
20 
21 	///
22 	this(string fileName, bool skipTests = false)
23 	{
24 		super(fileName, null, skipTests);
25 	}
26 
27 	override void visit(const Module mod)
28 	{
29 		level = 0;
30 		imports[level] = [];
31 		mod.accept(this);
32 	}
33 
34 	override void visit(const Statement decl)
35 	{
36 		imports[++level] = [];
37 		decl.accept(this);
38 		level--;
39 	}
40 
41 	override void visit(const BlockStatement decl)
42 	{
43 		imports[++level] = [];
44 		decl.accept(this);
45 		level--;
46 	}
47 
48 	override void visit(const StructBody decl)
49 	{
50 		imports[++level] = [];
51 		decl.accept(this);
52 		level--;
53 	}
54 
55 	override void visit(const ImportDeclaration id)
56 	{
57 		import std.algorithm.iteration : map;
58 		import std.array : join;
59 		import std.string : strip;
60 
61 		if (id.importBindings is null || id.importBindings.importBinds.length == 0)
62 		{
63 			foreach (singleImport; id.singleImports)
64 			{
65 				string importModuleName = singleImport.identifierChain.identifiers.map!`a.text`.join(".");
66 				addImport(importModuleName, singleImport);
67 			}
68 		}
69 		else
70 		{
71 			string importModuleName = id.importBindings.singleImport.identifierChain.identifiers.map!`a.text`.join(".");
72 
73 			foreach (importBind; id.importBindings.importBinds)
74 			{
75 				addImport(importModuleName ~ "_" ~ importBind.left.text, id.importBindings.singleImport);
76 			}
77 		}
78 	}
79 
80 	alias visit = BaseAnalyzer.visit;
81 
82 private:
83 
84 	int level = 0;
85 	string[][int] imports;
86 
87 	void addImport(string importModuleName, const SingleImport singleImport)
88 	{
89 		import std.uni : sicmp;
90 
91 		if (imports[level].length > 0 && imports[level][$ -1].sicmp(importModuleName) > 0)
92 		{
93 			addErrorMessage(singleImport.identifierChain.identifiers[0].line,
94 					singleImport.identifierChain.identifiers[0].column, KEY, MESSAGE);
95 		}
96 		else
97 		{
98 			imports[level] ~= importModuleName;
99 		}
100 	}
101 }
102 
103 unittest
104 {
105 	import std.stdio : stderr;
106 	import std.format : format;
107 	import analysis.config : StaticAnalysisConfig, Check;
108 	import analysis.helpers : assertAnalyzerWarnings;
109 
110 	StaticAnalysisConfig sac;
111 	sac.imports_sortedness = Check.enabled;
112 
113 	assertAnalyzerWarnings(q{
114 		import bar.foo;
115 		import foo.bar;
116 	}}, sac);
117 
118 	assertAnalyzerWarnings(q{
119 		import foo.bar;
120 		import bar.foo; // [warn]: %s
121 	}}.format(
122 		ImportSortednessCheck.MESSAGE,
123 	), sac);
124 
125 	assertAnalyzerWarnings(q{
126 		import c;
127 		import c.b;
128 		import c.a; // [warn]: %s
129 		import d.a;
130 		import d; // [warn]: %s
131 	}}.format(
132 		ImportSortednessCheck.MESSAGE,
133 		ImportSortednessCheck.MESSAGE,
134 	), sac);
135 
136 	assertAnalyzerWarnings(q{
137 		import a.b, a.c, a.d;
138 		import a.b, a.d, a.c; // [warn]: %s
139 		import a.c, a.b, a.c; // [warn]: %s
140 		import foo.bar, bar.foo; // [warn]: %s
141 	}}.format(
142 		ImportSortednessCheck.MESSAGE,
143 		ImportSortednessCheck.MESSAGE,
144 		ImportSortednessCheck.MESSAGE,
145 	), sac);
146 
147 	// multiple items out of order
148 	assertAnalyzerWarnings(q{
149 		import foo.bar;
150 		import bar.foo; // [warn]: %s
151 		import bar.bar.foo; // [warn]: %s
152 	}}.format(
153 		ImportSortednessCheck.MESSAGE,
154 		ImportSortednessCheck.MESSAGE,
155 	), sac);
156 
157 	assertAnalyzerWarnings(q{
158 		import test : bar;
159 		import test : foo;
160 	}}, sac);
161 
162 	// selective imports
163 	assertAnalyzerWarnings(q{
164 		import test : foo;
165 		import test : bar; // [warn]: %s
166 	}}.format(
167 		ImportSortednessCheck.MESSAGE,
168 	), sac);
169 
170 	// selective imports
171 	assertAnalyzerWarnings(q{
172 		import test : foo, bar; // [warn]: %s
173 	}}.format(
174 		ImportSortednessCheck.MESSAGE,
175 	), sac);
176 
177 	assertAnalyzerWarnings(q{
178 		import b;
179 		import c : foo;
180 		import c : bar; // [warn]: %s
181 		import a; // [warn]: %s
182 	}}.format(
183 		ImportSortednessCheck.MESSAGE,
184 		ImportSortednessCheck.MESSAGE,
185 	), sac);
186 
187 	assertAnalyzerWarnings(q{
188 		import c;
189 		import c : bar;
190 		import d : bar;
191 		import d; // [warn]: %s
192 		import a : bar; // [warn]: %s
193 	}}.format(
194 		ImportSortednessCheck.MESSAGE,
195 		ImportSortednessCheck.MESSAGE,
196 	), sac);
197 
198 	assertAnalyzerWarnings(q{
199 		import t0;
200 		import t1 : a, b = foo;
201 		import t2;
202 	}}, sac);
203 
204 	assertAnalyzerWarnings(q{
205 		import t1 : a, b = foo;
206 		import t1 : b, a = foo; // [warn]: %s
207 		import t0 : a, b = foo; // [warn]: %s
208 	}}.format(
209 		ImportSortednessCheck.MESSAGE,
210 		ImportSortednessCheck.MESSAGE,
211 	), sac);
212 
213 	// local imports in functions
214 	assertAnalyzerWarnings(q{
215 		import t2;
216 		import t1; // [warn]: %s
217 		void foo()
218 		{
219 			import f2;
220 			import f1; // [warn]: %s
221 			import f3;
222 		}
223 		void bar()
224 		{
225 			import f1;
226 			import f2;
227 		}
228 	}}.format(
229 		ImportSortednessCheck.MESSAGE,
230 		ImportSortednessCheck.MESSAGE,
231 	), sac);
232 
233 	// local imports in scopes
234 	assertAnalyzerWarnings(q{
235 		import t2;
236 		import t1; // [warn]: %s
237 		void foo()
238 		{
239 			import f2;
240 			import f1; // [warn]: %s
241 			import f3;
242 			{
243 				import f2;
244 				import f1; // [warn]: %s
245 				import f3;
246 			}
247 			{
248 				import f1;
249 				import f2;
250 				import f3;
251 			}
252 		}
253 	}}.format(
254 		ImportSortednessCheck.MESSAGE,
255 		ImportSortednessCheck.MESSAGE,
256 		ImportSortednessCheck.MESSAGE,
257 	), sac);
258 
259 	// local imports in functions
260 	assertAnalyzerWarnings(q{
261 		import t2;
262 		import t1; // [warn]: %s
263 		void foo()
264 		{
265 			import f2;
266 			import f1; // [warn]: %s
267 			import f3;
268 			while (true) {
269 				import f2;
270 				import f1; // [warn]: %s
271 				import f3;
272 			}
273 			for (;;) {
274 				import f1;
275 				import f2;
276 				import f3;
277 			}
278 			foreach (el; arr) {
279 				import f2;
280 				import f1; // [warn]: %s
281 				import f3;
282 			}
283 		}
284 	}}.format(
285 		ImportSortednessCheck.MESSAGE,
286 		ImportSortednessCheck.MESSAGE,
287 		ImportSortednessCheck.MESSAGE,
288 		ImportSortednessCheck.MESSAGE,
289 	), sac);
290 
291 	// nested scopes
292 	assertAnalyzerWarnings(q{
293 		import t2;
294 		import t1; // [warn]: %s
295 		void foo()
296 		{
297 			import f2;
298 			import f1; // [warn]: %s
299 			import f3;
300 			{
301 				import f2;
302 				import f1; // [warn]: %s
303 				import f3;
304 				{
305 					import f2;
306 					import f1; // [warn]: %s
307 					import f3;
308 					{
309 						import f2;
310 						import f1; // [warn]: %s
311 						import f3;
312 					}
313 				}
314 			}
315 		}
316 	}}.format(
317 		ImportSortednessCheck.MESSAGE,
318 		ImportSortednessCheck.MESSAGE,
319 		ImportSortednessCheck.MESSAGE,
320 		ImportSortednessCheck.MESSAGE,
321 		ImportSortednessCheck.MESSAGE,
322 	), sac);
323 
324 	// local imports in functions
325 	assertAnalyzerWarnings(q{
326 		import t2;
327 		import t1; // [warn]: %s
328 		struct foo()
329 		{
330 			import f2;
331 			import f1; // [warn]: %s
332 			import f3;
333 		}
334 		class bar()
335 		{
336 			import f1;
337 			import f2;
338 		}
339 	}}.format(
340 		ImportSortednessCheck.MESSAGE,
341 		ImportSortednessCheck.MESSAGE,
342 	), sac);
343 
344 	stderr.writeln("Unittest for ImportSortednessCheck passed.");
345 }