1 module workspaced.com.importer; 2 3 import dparse.parser; 4 import dparse.lexer; 5 import dparse.ast; 6 import dparse.rollback_allocator; 7 8 import std.algorithm; 9 import std.array; 10 import std.stdio; 11 12 import workspaced.api; 13 14 @component("importer") : 15 16 /// Initializes the import parser. Call with `{"cmd": "load", "components": ["importer"]}` 17 @load void start() 18 { 19 config.stringBehavior = StringBehavior.source; 20 cache = new StringCache(StringCache.defaultBucketCount); 21 } 22 23 /// Has no purpose right now. 24 @unload void stop() 25 { 26 } 27 28 /// Returns all imports available at some code position. 29 /// Call_With: `{"subcmd": "get"}` 30 @arguments("subcmd", "get") 31 ImportInfo[] get(string code, int pos) 32 { 33 auto tokens = getTokensForParser(cast(ubyte[]) code, config, cache); 34 auto mod = parseModule(tokens, "code", &rba, &doNothing); 35 auto reader = new ImporterReaderVisitor(pos); 36 reader.visit(mod); 37 return reader.imports; 38 } 39 40 /// Returns a list of code patches for adding an import. 41 /// If `insertOutermost` is false, the import will get added to the innermost block. 42 /// Call_With: `{"subcmd": "add"}` 43 @arguments("subcmd", "add") 44 ImportModification add(string importName, string code, int pos, bool insertOutermost = true) 45 { 46 auto tokens = getTokensForParser(cast(ubyte[]) code, config, cache); 47 auto mod = parseModule(tokens, "code", &rba, &doNothing); 48 auto reader = new ImporterReaderVisitor(pos); 49 reader.visit(mod); 50 foreach (i; reader.imports) 51 { 52 if (i.name.join('.') == importName) 53 { 54 if (i.selectives.length == 0) 55 return ImportModification(i.rename, []); 56 else 57 insertOutermost = false; 58 } 59 } 60 string indentation = ""; 61 if (insertOutermost) 62 { 63 indentation = reader.outerImportLocation == 0 ? "" : (cast(ubyte[]) code) 64 .getIndentation(reader.outerImportLocation); 65 if (reader.isModule) 66 indentation = '\n' ~ indentation; 67 return ImportModification("", [CodeReplacement([reader.outerImportLocation, reader.outerImportLocation], 68 indentation ~ "import " ~ importName ~ ";" ~ (reader.outerImportLocation == 0 ? "\n" : ""))]); 69 } 70 else 71 { 72 indentation = (cast(ubyte[]) code).getIndentation(reader.innermostBlockStart); 73 if (reader.isModule) 74 indentation = '\n' ~ indentation; 75 return ImportModification("", [CodeReplacement([reader.innermostBlockStart, 76 reader.innermostBlockStart], indentation ~ "import " ~ importName ~ ";")]); 77 } 78 } 79 80 /// Information about how to add an import 81 struct ImportModification 82 { 83 /// Set if there was already an import which was renamed. (for example import io = std.stdio; would be "io") 84 string rename; 85 /// Array of replacements to add the import to the code 86 CodeReplacement[] replacements; 87 } 88 89 /// Name and (if specified) rename of a symbol 90 struct SelectiveImport 91 { 92 /// Original name (always available) 93 string name; 94 /// Rename if specified 95 string rename; 96 } 97 98 /// Information about one import statement 99 struct ImportInfo 100 { 101 /// Parts of the imported module. (std.stdio -> ["std", "stdio"]) 102 string[] name; 103 /// Available if the module has been imported renamed 104 string rename; 105 /// Array of selective imports or empty if the entire module has been imported 106 SelectiveImport[] selectives; 107 } 108 109 private __gshared: 110 RollbackAllocator rba; 111 LexerConfig config; 112 StringCache* cache; 113 114 string getIndentation(ubyte[] code, size_t index) 115 { 116 import std.ascii : isWhite; 117 118 bool atLineEnd = false; 119 if (index < code.length && code[index] == '\n') 120 { 121 for (size_t i = index; i < code.length; i++) 122 if (!code[i].isWhite) 123 break; 124 atLineEnd = true; 125 } 126 while (index > 0) 127 { 128 if (code[index - 1] == cast(ubyte) '\n') 129 break; 130 index--; 131 } 132 size_t end = index; 133 while (end < code.length) 134 { 135 if (!code[end].isWhite) 136 break; 137 end++; 138 } 139 auto indent = cast(string) code[index .. end]; 140 if (!indent.length && index == 0 && !atLineEnd) 141 return " "; 142 return "\n" ~ indent.stripLeft('\n'); 143 } 144 145 unittest 146 { 147 auto code = cast(ubyte[]) "void foo() {\n\tfoo();\n}"; 148 auto indent = getIndentation(code, 20); 149 assert(indent == "\n\t", '"' ~ indent ~ '"'); 150 151 code = cast(ubyte[]) "void foo() { foo(); }"; 152 indent = getIndentation(code, 19); 153 assert(indent == " ", '"' ~ indent ~ '"'); 154 155 code = cast(ubyte[]) "import a;\n\nvoid foo() {\n\tfoo();\n}"; 156 indent = getIndentation(code, 9); 157 assert(indent == "\n", '"' ~ indent ~ '"'); 158 } 159 160 class ImporterReaderVisitor : ASTVisitor 161 { 162 this(int pos) 163 { 164 this.pos = pos; 165 inBlock = false; 166 } 167 168 alias visit = ASTVisitor.visit; 169 170 override void visit(const ModuleDeclaration decl) 171 { 172 if (decl.endLocation + 1 < outerImportLocation || inBlock) 173 return; 174 isModule = true; 175 outerImportLocation = decl.endLocation + 1; 176 } 177 178 override void visit(const ImportDeclaration decl) 179 { 180 if (decl.startIndex >= pos) 181 return; 182 isModule = false; 183 if (inBlock) 184 innermostBlockStart = decl.endIndex; 185 else 186 outerImportLocation = decl.endIndex; 187 foreach (i; decl.singleImports) 188 imports ~= ImportInfo(i.identifierChain.identifiers.map!(tok => tok.text.idup) 189 .array, i.rename.text); 190 if (decl.importBindings) 191 { 192 ImportInfo info; 193 if (!decl.importBindings.singleImport) 194 return; 195 info.name = decl.importBindings.singleImport.identifierChain.identifiers.map!( 196 tok => tok.text.idup).array; 197 info.rename = decl.importBindings.singleImport.rename.text; 198 foreach (bind; decl.importBindings.importBinds) 199 { 200 if (bind.right.text) 201 info.selectives ~= SelectiveImport(bind.right.text, bind.left.text); 202 else 203 info.selectives ~= SelectiveImport(bind.left.text); 204 } 205 if (info.selectives.length) 206 imports ~= info; 207 } 208 } 209 210 override void visit(const BlockStatement content) 211 { 212 if (content && pos >= content.startLocation && pos < content.endLocation) 213 { 214 if (content.startLocation + 1 >= innermostBlockStart) 215 innermostBlockStart = content.startLocation + 1; 216 inBlock = true; 217 return content.accept(this); 218 } 219 } 220 221 private int pos; 222 private bool inBlock; 223 ImportInfo[] imports; 224 bool isModule; 225 size_t outerImportLocation; 226 size_t innermostBlockStart; 227 } 228 229 void doNothing(string, size_t, size_t, string, bool) 230 { 231 } 232 233 unittest 234 { 235 import std.conv; 236 237 start(); 238 auto imports = get("import std.stdio; void foo() { import fs = std.file; import std.algorithm : map, each2 = each; writeln(\"hi\"); } void bar() { import std.string; import std.regex : ctRegex; }", 239 81); 240 bool equalsImport(ImportInfo i, string s) 241 { 242 return i.name.join('.') == s; 243 } 244 245 void assertEquals(T)(T a, T b) 246 { 247 assert(a == b, "'" ~ a.to!string ~ "' != '" ~ b.to!string ~ "'"); 248 } 249 250 assertEquals(imports.length, 3); 251 assert(equalsImport(imports[0], "std.stdio")); 252 assert(equalsImport(imports[1], "std.file")); 253 assertEquals(imports[1].rename, "fs"); 254 assert(equalsImport(imports[2], "std.algorithm")); 255 assertEquals(imports[2].selectives.length, 2); 256 assertEquals(imports[2].selectives[0].name, "map"); 257 assertEquals(imports[2].selectives[1].name, "each"); 258 assertEquals(imports[2].selectives[1].rename, "each2"); 259 260 string code = "void foo() { import std.stdio : stderr; writeln(\"hi\"); }"; 261 auto mod = add("std.stdio", code, 45); 262 assertEquals(mod.rename, ""); 263 assertEquals(mod.replacements.length, 1); 264 assertEquals(mod.replacements[0].apply(code), 265 "void foo() { import std.stdio : stderr; import std.stdio; writeln(\"hi\"); }"); 266 267 code = "void foo() {\n\timport std.stdio : stderr;\n\twriteln(\"hi\");\n}"; 268 mod = add("std.stdio", code, 45); 269 assertEquals(mod.rename, ""); 270 assertEquals(mod.replacements.length, 1); 271 assertEquals(mod.replacements[0].apply(code), 272 "void foo() {\n\timport std.stdio : stderr;\n\timport std.stdio;\n\twriteln(\"hi\");\n}"); 273 274 code = "void foo() {\n\timport std.file : readText;\n\twriteln(\"hi\");\n}"; 275 mod = add("std.stdio", code, 45); 276 assertEquals(mod.rename, ""); 277 assertEquals(mod.replacements.length, 1); 278 assertEquals(mod.replacements[0].apply(code), 279 "import std.stdio;\nvoid foo() {\n\timport std.file : readText;\n\twriteln(\"hi\");\n}"); 280 281 code = "void foo() { import io = std.stdio; io.writeln(\"hi\"); }"; 282 mod = add("std.stdio", code, 45); 283 assertEquals(mod.rename, "io"); 284 assertEquals(mod.replacements.length, 0); 285 286 code = "import std.file : readText;\n\nvoid foo() {\n\twriteln(\"hi\");\n}"; 287 mod = add("std.stdio", code, 45); 288 assertEquals(mod.rename, ""); 289 assertEquals(mod.replacements.length, 1); 290 assertEquals(mod.replacements[0].apply(code), 291 "import std.file : readText;\nimport std.stdio;\n\nvoid foo() {\n\twriteln(\"hi\");\n}"); 292 293 code = "import std.file;\nimport std.regex;\n\nvoid foo() {\n\twriteln(\"hi\");\n}"; 294 mod = add("std.stdio", code, 54); 295 assertEquals(mod.rename, ""); 296 assertEquals(mod.replacements.length, 1); 297 assertEquals(mod.replacements[0].apply(code), 298 "import std.file;\nimport std.regex;\nimport std.stdio;\n\nvoid foo() {\n\twriteln(\"hi\");\n}"); 299 300 code = "module a;\n\nvoid foo() {\n\twriteln(\"hi\");\n}"; 301 mod = add("std.stdio", code, 30); 302 assertEquals(mod.rename, ""); 303 assertEquals(mod.replacements.length, 1); 304 assertEquals(mod.replacements[0].apply(code), 305 "module a;\n\nimport std.stdio;\n\nvoid foo() {\n\twriteln(\"hi\");\n}"); 306 307 stop(); 308 }