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 }