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 }