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 dscanner.analysis.imports_sortedness;
6 
7 import dscanner.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 	mixin ScopedVisit!Module;
28 	mixin ScopedVisit!Statement;
29 	mixin ScopedVisit!BlockStatement;
30 	mixin ScopedVisit!StructBody;
31 	mixin ScopedVisit!IfStatement;
32 	mixin ScopedVisit!TemplateDeclaration;
33 	mixin ScopedVisit!ConditionalDeclaration;
34 
35 	override void visit(const VariableDeclaration id)
36 	{
37 		imports[level] = [];
38 	}
39 
40 	override void visit(const ImportDeclaration id)
41 	{
42 		import std.algorithm.iteration : map;
43 		import std.array : join;
44 		import std..string : strip;
45 
46 		if (id.importBindings is null || id.importBindings.importBinds.length == 0)
47 		{
48 			foreach (singleImport; id.singleImports)
49 			{
50 				string importModuleName = singleImport.identifierChain.identifiers.map!`a.text`.join(".");
51 				addImport(importModuleName, singleImport);
52 			}
53 		}
54 		else
55 		{
56 			string importModuleName = id.importBindings.singleImport.identifierChain.identifiers.map!`a.text`.join(".");
57 
58 			foreach (importBind; id.importBindings.importBinds)
59 			{
60 				addImport(importModuleName ~ "-" ~ importBind.left.text, id.importBindings.singleImport);
61 			}
62 		}
63 	}
64 
65 	alias visit = BaseAnalyzer.visit;
66 
67 private:
68 
69 	int level;
70 	string[][int] imports;
71 
72 	template ScopedVisit(NodeType)
73 	{
74 		override void visit(const NodeType n)
75 		{
76 			imports[++level] = [];
77 			n.accept(this);
78 			level--;
79 		}
80 	}
81 
82 	void addImport(string importModuleName, const SingleImport singleImport)
83 	{
84 		import std.uni : sicmp;
85 
86 		if (imports[level].length > 0 && imports[level][$ -1].sicmp(importModuleName) > 0)
87 		{
88 			addErrorMessage(singleImport.identifierChain.identifiers[0].line,
89 					singleImport.identifierChain.identifiers[0].column, KEY, MESSAGE);
90 		}
91 		else
92 		{
93 			imports[level] ~= importModuleName;
94 		}
95 	}
96 }
97 
98 unittest
99 {
100 	import std.stdio : stderr;
101 	import std.format : format;
102 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
103 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
104 
105 	StaticAnalysisConfig sac = disabledConfig();
106 	sac.imports_sortedness = Check.enabled;
107 
108 	assertAnalyzerWarnings(q{
109 		import bar.foo;
110 		import foo.bar;
111 	}c, sac);
112 
113 	assertAnalyzerWarnings(q{
114 		import foo.bar;
115 		import bar.foo; // [warn]: %s
116 	}c.format(
117 		ImportSortednessCheck.MESSAGE,
118 	), sac);
119 
120 	assertAnalyzerWarnings(q{
121 		import c;
122 		import c.b;
123 		import c.a; // [warn]: %s
124 		import d.a;
125 		import d; // [warn]: %s
126 	}c.format(
127 		ImportSortednessCheck.MESSAGE,
128 		ImportSortednessCheck.MESSAGE,
129 	), sac);
130 
131 	assertAnalyzerWarnings(q{
132 		import a.b, a.c, a.d;
133 		import a.b, a.d, a.c; // [warn]: %s
134 		import a.c, a.b, a.c; // [warn]: %s
135 		import foo.bar, bar.foo; // [warn]: %s
136 	}c.format(
137 		ImportSortednessCheck.MESSAGE,
138 		ImportSortednessCheck.MESSAGE,
139 		ImportSortednessCheck.MESSAGE,
140 	), sac);
141 
142 	// multiple items out of order
143 	assertAnalyzerWarnings(q{
144 		import foo.bar;
145 		import bar.foo; // [warn]: %s
146 		import bar.bar.foo; // [warn]: %s
147 	}c.format(
148 		ImportSortednessCheck.MESSAGE,
149 		ImportSortednessCheck.MESSAGE,
150 	), sac);
151 
152 	assertAnalyzerWarnings(q{
153 		import test : bar;
154 		import test : foo;
155 	}c, sac);
156 
157 	// selective imports
158 	assertAnalyzerWarnings(q{
159 		import test : foo;
160 		import test : bar; // [warn]: %s
161 	}c.format(
162 		ImportSortednessCheck.MESSAGE,
163 	), sac);
164 
165 	// selective imports
166 	assertAnalyzerWarnings(q{
167 		import test : foo, bar; // [warn]: %s
168 	}c.format(
169 		ImportSortednessCheck.MESSAGE,
170 	), sac);
171 
172 	assertAnalyzerWarnings(q{
173 		import b;
174 		import c : foo;
175 		import c : bar; // [warn]: %s
176 		import a; // [warn]: %s
177 	}c.format(
178 		ImportSortednessCheck.MESSAGE,
179 		ImportSortednessCheck.MESSAGE,
180 	), sac);
181 
182 	assertAnalyzerWarnings(q{
183 		import c;
184 		import c : bar;
185 		import d : bar;
186 		import d; // [warn]: %s
187 		import a : bar; // [warn]: %s
188 	}c.format(
189 		ImportSortednessCheck.MESSAGE,
190 		ImportSortednessCheck.MESSAGE,
191 	), sac);
192 
193 	assertAnalyzerWarnings(q{
194 		import t0;
195 		import t1 : a, b = foo;
196 		import t2;
197 	}c, sac);
198 
199 	assertAnalyzerWarnings(q{
200 		import t1 : a, b = foo;
201 		import t1 : b, a = foo; // [warn]: %s
202 		import t0 : a, b = foo; // [warn]: %s
203 	}c.format(
204 		ImportSortednessCheck.MESSAGE,
205 		ImportSortednessCheck.MESSAGE,
206 	), sac);
207 
208 	// local imports in functions
209 	assertAnalyzerWarnings(q{
210 		import t2;
211 		import t1; // [warn]: %s
212 		void foo()
213 		{
214 			import f2;
215 			import f1; // [warn]: %s
216 			import f3;
217 		}
218 		void bar()
219 		{
220 			import f1;
221 			import f2;
222 		}
223 	}c.format(
224 		ImportSortednessCheck.MESSAGE,
225 		ImportSortednessCheck.MESSAGE,
226 	), sac);
227 
228 	// local imports in scopes
229 	assertAnalyzerWarnings(q{
230 		import t2;
231 		import t1; // [warn]: %s
232 		void foo()
233 		{
234 			import f2;
235 			import f1; // [warn]: %s
236 			import f3;
237 			{
238 				import f2;
239 				import f1; // [warn]: %s
240 				import f3;
241 			}
242 			{
243 				import f1;
244 				import f2;
245 				import f3;
246 			}
247 		}
248 	}c.format(
249 		ImportSortednessCheck.MESSAGE,
250 		ImportSortednessCheck.MESSAGE,
251 		ImportSortednessCheck.MESSAGE,
252 	), sac);
253 
254 	// local imports in functions
255 	assertAnalyzerWarnings(q{
256 		import t2;
257 		import t1; // [warn]: %s
258 		void foo()
259 		{
260 			import f2;
261 			import f1; // [warn]: %s
262 			import f3;
263 			while (true) {
264 				import f2;
265 				import f1; // [warn]: %s
266 				import f3;
267 			}
268 			for (;;) {
269 				import f1;
270 				import f2;
271 				import f3;
272 			}
273 			foreach (el; arr) {
274 				import f2;
275 				import f1; // [warn]: %s
276 				import f3;
277 			}
278 		}
279 	}c.format(
280 		ImportSortednessCheck.MESSAGE,
281 		ImportSortednessCheck.MESSAGE,
282 		ImportSortednessCheck.MESSAGE,
283 		ImportSortednessCheck.MESSAGE,
284 	), sac);
285 
286 	// nested scopes
287 	assertAnalyzerWarnings(q{
288 		import t2;
289 		import t1; // [warn]: %s
290 		void foo()
291 		{
292 			import f2;
293 			import f1; // [warn]: %s
294 			import f3;
295 			{
296 				import f2;
297 				import f1; // [warn]: %s
298 				import f3;
299 				{
300 					import f2;
301 					import f1; // [warn]: %s
302 					import f3;
303 					{
304 						import f2;
305 						import f1; // [warn]: %s
306 						import f3;
307 					}
308 				}
309 			}
310 		}
311 	}c.format(
312 		ImportSortednessCheck.MESSAGE,
313 		ImportSortednessCheck.MESSAGE,
314 		ImportSortednessCheck.MESSAGE,
315 		ImportSortednessCheck.MESSAGE,
316 		ImportSortednessCheck.MESSAGE,
317 	), sac);
318 
319 	// local imports in functions
320 	assertAnalyzerWarnings(q{
321 		import t2;
322 		import t1; // [warn]: %s
323 		struct foo()
324 		{
325 			import f2;
326 			import f1; // [warn]: %s
327 			import f3;
328 		}
329 		class bar()
330 		{
331 			import f1;
332 			import f2;
333 		}
334 	}c.format(
335 		ImportSortednessCheck.MESSAGE,
336 		ImportSortednessCheck.MESSAGE,
337 	), sac);
338 
339 	// issue 422 - sorted imports with :
340 	assertAnalyzerWarnings(q{
341 		import foo.bar : bar;
342 		import foo.barbar;
343 	}, sac);
344 
345 	// issue 422 - sorted imports with :
346 	assertAnalyzerWarnings(q{
347 		import foo;
348 		import foo.bar;
349 		import fooa;
350 		import std.range : Take;
351 		import std.range.primitives : isInputRange, walkLength;
352 	}, sac);
353 
354 	// condition declaration
355 	assertAnalyzerWarnings(q{
356 		import t2;
357 		version(unittest)
358 		{
359 			import t1;
360 		}
361 	}, sac);
362 
363 	// if statements
364 	assertAnalyzerWarnings(q{
365 	unittest
366 	{
367 		import t2;
368 		if (true)
369 		{
370 			import t1;
371 		}
372 	}
373 	}, sac);
374 
375 	// intermediate imports
376 	assertAnalyzerWarnings(q{
377 	unittest
378 	{
379 		import t2;
380 		int a = 1;
381 		import t1;
382 	}
383 	}, sac);
384 
385 	stderr.writeln("Unittest for ImportSortednessCheck passed.");
386 }