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 }