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