diff --git a/package.json b/package.json index 3e3d4c19f..594d42f4f 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,15 @@ ], "default": "off", "description": "Traces the communication between VSCode and the language server." + }, + "java.importOrder": { + "type": "string", + "enum": [ + "simple", + "chromium" + ], + "default": "simple", + "description": "Import order for organizing imports." } } }, diff --git a/src/main/java/org/javacs/JavaLanguageServer.java b/src/main/java/org/javacs/JavaLanguageServer.java index aa96ebd09..d86596935 100644 --- a/src/main/java/org/javacs/JavaLanguageServer.java +++ b/src/main/java/org/javacs/JavaLanguageServer.java @@ -16,6 +16,9 @@ import org.javacs.completion.SignatureProvider; import org.javacs.fold.FoldProvider; import org.javacs.hover.HoverProvider; +import org.javacs.imports.AutoImportProvider; +import org.javacs.imports.AutoImportProviderFactory; +import org.javacs.imports.SimpleAutoImportProvider; import org.javacs.index.SymbolProvider; import org.javacs.lens.CodeLensProvider; import org.javacs.lsp.*; @@ -33,6 +36,7 @@ class JavaLanguageServer extends LanguageServer { private JsonObject cacheSettings; private JsonObject settings = new JsonObject(); private boolean modifiedBuild = true; + private AutoImportProvider autoImportProvider = SimpleAutoImportProvider.INSTANCE; JavaCompilerService compiler() { if (needsCompiler()) { @@ -226,6 +230,7 @@ public void didChangeConfiguration(DidChangeConfigurationParams change) { var java = change.settings.getAsJsonObject().get("java"); LOG.info("Received java settings " + java); settings = java.getAsJsonObject(); + updateAutoImportProvider(); } @Override @@ -260,7 +265,7 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { public Optional completion(TextDocumentPositionParams params) { if (!FileStore.isJavaFile(params.textDocument.uri)) return Optional.empty(); var file = Paths.get(params.textDocument.uri); - var provider = new CompletionProvider(compiler()); + var provider = new CompletionProvider(compiler(), autoImportProvider); var list = provider.complete(file, params.position.line + 1, params.position.character + 1); if (list == CompletionProvider.NOT_SUPPORTED) return Optional.empty(); return Optional.of(list); @@ -510,7 +515,7 @@ public void didCloseTextDocument(DidCloseTextDocumentParams params) { @Override public List codeAction(CodeActionParams params) { - var provider = new CodeActionProvider(compiler()); + var provider = new CodeActionProvider(compiler(), autoImportProvider); if (params.context.diagnostics.isEmpty()) { return provider.codeActionsForCursor(params); } else { @@ -534,5 +539,19 @@ public void doAsyncWork() { } } + private void updateAutoImportProvider() { + if (!settings.has("importOrder")) { + return; + } + var name = settings.getAsJsonPrimitive("importOrder").getAsString(); + try { + autoImportProvider = AutoImportProviderFactory.getByName(name); + } catch (IllegalArgumentException e) { + LOG.warning("Unknown import order: " + name); + LOG.warning("Falling back to the default import order"); + autoImportProvider = SimpleAutoImportProvider.INSTANCE; + } + } + private static final Logger LOG = Logger.getLogger("main"); } diff --git a/src/main/java/org/javacs/action/CodeActionProvider.java b/src/main/java/org/javacs/action/CodeActionProvider.java index c3bac77da..5bc8b1079 100644 --- a/src/main/java/org/javacs/action/CodeActionProvider.java +++ b/src/main/java/org/javacs/action/CodeActionProvider.java @@ -13,14 +13,17 @@ import javax.lang.model.element.*; import org.javacs.*; import org.javacs.FindTypeDeclarationAt; +import org.javacs.imports.AutoImportProvider; import org.javacs.lsp.*; import org.javacs.rewrite.*; public class CodeActionProvider { private final CompilerProvider compiler; + private final AutoImportProvider autoImportProvider; - public CodeActionProvider(CompilerProvider compiler) { + public CodeActionProvider(CompilerProvider compiler, AutoImportProvider autoImportProvider) { this.compiler = compiler; + this.autoImportProvider = autoImportProvider; } public List codeActionsForCursor(CodeActionParams params) { @@ -171,7 +174,7 @@ private List codeActionForDiagnostic(CompileTask task, Path file, Di for (var qualifiedName : compiler.publicTopLevelTypes()) { if (qualifiedName.endsWith("." + simpleName)) { var title = "Import '" + qualifiedName + "'"; - var addImport = new AddImport(file, qualifiedName); + var addImport = new AddImport(file, qualifiedName, autoImportProvider); allImports.addAll(createQuickFix(title, addImport)); } } diff --git a/src/main/java/org/javacs/completion/CompletionProvider.java b/src/main/java/org/javacs/completion/CompletionProvider.java index 709cec91e..3de2e5a59 100644 --- a/src/main/java/org/javacs/completion/CompletionProvider.java +++ b/src/main/java/org/javacs/completion/CompletionProvider.java @@ -8,6 +8,7 @@ import com.sun.source.tree.Scope; import com.sun.source.tree.SwitchTree; import com.sun.source.tree.Tree; +import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import java.nio.file.Path; @@ -40,6 +41,7 @@ import org.javacs.ParseTask; import org.javacs.SourceFileObject; import org.javacs.StringSearch; +import org.javacs.imports.AutoImportProvider; import org.javacs.lsp.Command; import org.javacs.lsp.CompletionItem; import org.javacs.lsp.CompletionItemKind; @@ -48,6 +50,7 @@ public class CompletionProvider { private final CompilerProvider compiler; + private final AutoImportProvider autoImportProvider; public static final CompletionList NOT_SUPPORTED = new CompletionList(false, List.of()); public static final int MAX_COMPLETION_ITEMS = 50; @@ -116,8 +119,9 @@ public class CompletionProvider { "double", }; - public CompletionProvider(CompilerProvider compiler) { + public CompletionProvider(CompilerProvider compiler, AutoImportProvider autoImportProvider) { this.compiler = compiler; + this.autoImportProvider = autoImportProvider; } public CompletionList complete(Path file, int line, int column) { @@ -235,7 +239,7 @@ private CompletionList completeIdentifier(CompileTask task, TreePath path, Strin list.items = completeUsingScope(task, path, partial, endsWithParen); addStaticImports(task, path.getCompilationUnit(), partial, endsWithParen, list); if (!list.isIncomplete && partial.length() > 0 && Character.isUpperCase(partial.charAt(0))) { - addClassNames(path.getCompilationUnit(), partial, list); + addClassNames(path.getCompilationUnit(), Trees.instance(task.task).getSourcePositions(), partial, list); } addKeywords(path, partial, list); return list; @@ -332,24 +336,23 @@ private boolean memberMatchesImport(Name staticImport, Element member) { return staticImport.contentEquals("*") || staticImport.contentEquals(member.getSimpleName()); } - private void addClassNames(CompilationUnitTree root, String partial, CompletionList list) { + private void addClassNames(CompilationUnitTree root, SourcePositions sourcePositions, String partial, CompletionList list) { var packageName = Objects.toString(root.getPackageName(), ""); - var uniques = new HashSet(); var previousSize = list.items.size(); for (var className : compiler.packagePrivateTopLevelTypes(packageName)) { - if (!StringSearch.matchesPartialName(className, partial)) continue; - list.items.add(classItem(className)); - uniques.add(className); + var item = classItem(className); + item.additionalTextEdits = autoImportProvider.addImport(className, root, sourcePositions); + list.items.add(item); } for (var className : compiler.publicTopLevelTypes()) { if (!StringSearch.matchesPartialName(simpleName(className), partial)) continue; - if (uniques.contains(className)) continue; if (list.items.size() > MAX_COMPLETION_ITEMS) { list.isIncomplete = true; break; } - list.items.add(classItem(className)); - uniques.add(className); + var item = classItem(className); + item.additionalTextEdits = autoImportProvider.addImport(className, root, sourcePositions); + list.items.add(item); } LOG.info("...found " + (list.items.size() - previousSize) + " class names"); } diff --git a/src/main/java/org/javacs/imports/AutoImportProvider.java b/src/main/java/org/javacs/imports/AutoImportProvider.java new file mode 100644 index 000000000..76bd7d560 --- /dev/null +++ b/src/main/java/org/javacs/imports/AutoImportProvider.java @@ -0,0 +1,16 @@ +package org.javacs.imports; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.util.SourcePositions; +import java.util.List; +import org.javacs.lsp.TextEdit; + +/** + * Provides the functionality to auto-import classes. + */ +public interface AutoImportProvider { + /** + * Computes edits to add an import statement of the given class name to the Java file. + */ + List addImport(String className, CompilationUnitTree root, SourcePositions sourcePositions); +} diff --git a/src/main/java/org/javacs/imports/AutoImportProviderFactory.java b/src/main/java/org/javacs/imports/AutoImportProviderFactory.java new file mode 100644 index 000000000..836c11885 --- /dev/null +++ b/src/main/java/org/javacs/imports/AutoImportProviderFactory.java @@ -0,0 +1,21 @@ +package org.javacs.imports; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.util.SourcePositions; +import org.javacs.lsp.TextEdit; + +/** + * The factory of AutoImportProvider. + */ +public class AutoImportProviderFactory { + public static AutoImportProvider getByName(String name) { + switch (name) { + case "", "default", "simple": + return SimpleAutoImportProvider.INSTANCE; + case "chromium": + return ChromiumAutoImportProvider.INSTANCE; + default: + throw new IllegalArgumentException("Unknown import order: " + name); + } + } +} diff --git a/src/main/java/org/javacs/imports/ChromiumAutoImportProvider.java b/src/main/java/org/javacs/imports/ChromiumAutoImportProvider.java new file mode 100644 index 000000000..de79b298f --- /dev/null +++ b/src/main/java/org/javacs/imports/ChromiumAutoImportProvider.java @@ -0,0 +1,65 @@ +package org.javacs.imports; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.util.SourcePositions; +import java.util.List; +import javax.tools.Diagnostic; +import org.javacs.lsp.Position; +import org.javacs.lsp.Range; +import org.javacs.lsp.TextEdit; + +/** + * The import order for Chromium Java source files. + * + * https://chromium.googlesource.com/chromium/src/+/main/styleguide/java/java.md#import-order + */ +public class ChromiumAutoImportProvider implements AutoImportProvider { + public static final ChromiumAutoImportProvider INSTANCE = new ChromiumAutoImportProvider(); + + private final SectionedImportOrderHelper helper = new SectionedImportOrderHelper(ChromiumAutoImportProvider::sectionOf); + + private ChromiumAutoImportProvider() {} + + @Override + public List addImport(String className, CompilationUnitTree root, SourcePositions sourcePositions) { + return helper.addImport(className, root, sourcePositions); + } + + private static int sectionOf(String className, boolean isStatic) { + if (isStatic) { + return 0; + } + if (className.startsWith("com.google.android.apps.chrome.")) { + return 7; + } + if (className.startsWith("org.chromium.")) { + return 8; + } + if (className.startsWith("android.")) { + return 1; + } + if (className.startsWith("androidx.")) { + return 2; + } + if (className.startsWith("com.")) { + return 3; + } + if (className.startsWith("dalvik.")) { + return 4; + } + if (className.startsWith("junit.")) { + return 5; + } + if (className.startsWith("org.")) { + return 6; + } + if (className.startsWith("java.")) { + return 9; + } + if (className.startsWith("javax.")) { + return 10; + } + return 99; + } +} diff --git a/src/main/java/org/javacs/imports/SectionedImportOrderHelper.java b/src/main/java/org/javacs/imports/SectionedImportOrderHelper.java new file mode 100644 index 000000000..dca75b65d --- /dev/null +++ b/src/main/java/org/javacs/imports/SectionedImportOrderHelper.java @@ -0,0 +1,123 @@ +package org.javacs.imports; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.util.SourcePositions; +import java.util.List; +import javax.tools.Diagnostic; +import org.javacs.lsp.Position; +import org.javacs.lsp.Range; +import org.javacs.lsp.TextEdit; + +/** + * A common logic to implement AutoImportProvider that organizes imports into one or more sections. + */ +class SectionedImportOrderHelper { + /** + * Decides which section a class name belongs to. + * + * It should return an integer that represents a section. Sections are organized in the + * increasing order. + */ + public interface SectionFunction { + int sectionOf(String className, boolean isStatic); + } + + private final SectionFunction sectionFunction; + + public SectionedImportOrderHelper(SectionFunction sectionFunction) { + this.sectionFunction = sectionFunction; + } + + public List addImport(String className, CompilationUnitTree root, SourcePositions sourcePositions) { + String importCode = "import " + className + ";\n"; + var lineMap = root.getLineMap(); + var imports = root.getImports(); + + // No need to import java.lang.*. + if (className.startsWith("java.lang.")) { + return List.of(); + } + + // No need to import classes in the same package. + var packageTree = root.getPackage(); + if (packageTree != null) { + String packageName = packageTree.getPackageName().toString(); + if (className.startsWith(packageName + ".") && !className.substring(packageName.length() + 1).contains(".")) { + return List.of(); + } + } + + // If there is already an import, do not insert one. + for (var i : imports) { + String importedClassName = i.getQualifiedIdentifier().toString(); + if (importedClassName.equals(className)) { + return List.of(); + } + if (importedClassName.endsWith(".*")) { + String importedPackage = importedClassName.substring(0, importedClassName.length() - 1); + if (className.startsWith(importedPackage)) { + return List.of(); + } + } + } + + // Special logic to handle the case of no imports yet. + if (imports.isEmpty()) { + if (packageTree == null) { + // There is even no package declaration. + return List.of(new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), importCode)); + } + var packageStartPos = sourcePositions.getStartPosition(root, root.getPackage()); + var packageLine = (int) lineMap.getLineNumber(packageStartPos) - 1; + var editPosition = new Position(packageLine + 1, 0); + return List.of(new TextEdit(new Range(editPosition, editPosition), "\n" + importCode)); + } + + // Find the position to insert a new import. + int newImportSection = sectionFunction.sectionOf(className, false); + int insertPosition = imports.size(); + for (var i = 0; i < imports.size(); i++) { + var currentImport = imports.get(i); + String nextClassName = currentImport.getQualifiedIdentifier().toString(); + int nextImportSection = sectionFunction.sectionOf(nextClassName, currentImport.isStatic()); + if (nextImportSection > newImportSection || (nextImportSection == newImportSection && nextClassName.compareTo(className) > 0)) { + insertPosition = i; + break; + } + } + + if (insertPosition == imports.size()) { + // Add to the end. + var lastImport = imports.get(imports.size() - 1); + int lastImportSection = sectionFunction.sectionOf(lastImport.getQualifiedIdentifier().toString(), lastImport.isStatic()); + String insertCode = importCode; + if (lastImportSection < newImportSection) { + insertCode = "\n" + insertCode; + } + var lastImportStartPos = sourcePositions.getStartPosition(root, lastImport); + var lastImportLine = (int) lineMap.getLineNumber(lastImportStartPos) - 1; + var editPosition = new Position(lastImportLine + 1, 0); + return List.of(new TextEdit(new Range(editPosition, editPosition), insertCode)); + } + + // Insert to the beginning or the middle. + var nextImport = imports.get(insertPosition); + int nextImportSection = sectionFunction.sectionOf(nextImport.getQualifiedIdentifier().toString(), nextImport.isStatic()); + var nextImportStartPos = sourcePositions.getStartPosition(root, nextImport); + var nextImportLine = (int) lineMap.getLineNumber(nextImportStartPos) - 1; + var editPosition = new Position(nextImportLine, 0); + String insertCode = importCode; + if (newImportSection < nextImportSection) { + var previousImport = insertPosition > 0 ? imports.get(insertPosition - 1) : null; + if (previousImport != null && sectionFunction.sectionOf(previousImport.getQualifiedIdentifier().toString(), previousImport.isStatic()) == newImportSection) { + // Append an import to the end of the previous section. + editPosition.line--; + } else { + // Create a new section. + insertCode += "\n"; + } + } + return List.of(new TextEdit(new Range(editPosition, editPosition), insertCode)); + } +} diff --git a/src/main/java/org/javacs/imports/SimpleAutoImportProvider.java b/src/main/java/org/javacs/imports/SimpleAutoImportProvider.java new file mode 100644 index 000000000..deea36432 --- /dev/null +++ b/src/main/java/org/javacs/imports/SimpleAutoImportProvider.java @@ -0,0 +1,26 @@ +package org.javacs.imports; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.util.SourcePositions; +import java.util.List; +import javax.tools.Diagnostic; +import org.javacs.lsp.Position; +import org.javacs.lsp.Range; +import org.javacs.lsp.TextEdit; + +/** + * The default import order that simply sorts imports. + */ +public class SimpleAutoImportProvider implements AutoImportProvider { + public static final SimpleAutoImportProvider INSTANCE = new SimpleAutoImportProvider(); + + private final SectionedImportOrderHelper helper = new SectionedImportOrderHelper((className, isStatic) -> 0); + + private SimpleAutoImportProvider() {} + + @Override + public List addImport(String className, CompilationUnitTree root, SourcePositions sourcePositions) { + return helper.addImport(className, root, sourcePositions); + } +} diff --git a/src/main/java/org/javacs/rewrite/AddImport.java b/src/main/java/org/javacs/rewrite/AddImport.java index 511a5ae1e..528140b78 100644 --- a/src/main/java/org/javacs/rewrite/AddImport.java +++ b/src/main/java/org/javacs/rewrite/AddImport.java @@ -6,6 +6,7 @@ import java.util.Map; import org.javacs.CompilerProvider; import org.javacs.ParseTask; +import org.javacs.imports.AutoImportProvider; import org.javacs.lsp.Position; import org.javacs.lsp.Range; import org.javacs.lsp.TextEdit; @@ -13,50 +14,18 @@ public class AddImport implements Rewrite { final Path file; final String className; + final AutoImportProvider autoImportProvider; - public AddImport(Path file, String className) { + public AddImport(Path file, String className, AutoImportProvider autoImportProvider) { this.file = file; this.className = className; + this.autoImportProvider = autoImportProvider; } @Override public Map rewrite(CompilerProvider compiler) { var task = compiler.parse(file); - var point = insertPosition(task); - var text = "import " + className + ";\n"; - TextEdit[] edits = {new TextEdit(new Range(point, point), text)}; - return Map.of(file, edits); - } - - private Position insertPosition(ParseTask task) { - var imports = task.root.getImports(); - for (var i : imports) { - var next = i.getQualifiedIdentifier().toString(); - if (className.compareTo(next) < 0) { - return insertBefore(task, i); - } - } - if (!imports.isEmpty()) { - var last = imports.get(imports.size() - 1); - return insertAfter(task, last); - } - if (task.root.getPackage() != null) { - return insertAfter(task, task.root.getPackage()); - } - return new Position(0, 0); - } - - private Position insertBefore(ParseTask task, Tree i) { - var pos = Trees.instance(task.task).getSourcePositions(); - var offset = pos.getStartPosition(task.root, i); - var line = (int) task.root.getLineMap().getLineNumber(offset); - return new Position(line - 1, 0); - } - - private Position insertAfter(ParseTask task, Tree i) { - var pos = Trees.instance(task.task).getSourcePositions(); - var offset = pos.getStartPosition(task.root, i); - var line = (int) task.root.getLineMap().getLineNumber(offset); - return new Position(line, 0); + var edits = autoImportProvider.addImport(className, task.root, Trees.instance(task.task).getSourcePositions()); + return Map.of(file, edits.toArray(new TextEdit[0])); } } diff --git a/src/test/examples/maven-project/src/com/example/AutoImportTest1.java b/src/test/examples/maven-project/src/com/example/AutoImportTest1.java new file mode 100644 index 000000000..9e5198491 --- /dev/null +++ b/src/test/examples/maven-project/src/com/example/AutoImportTest1.java @@ -0,0 +1,3 @@ +package com.example; + +public class AutoImportTest1 {} diff --git a/src/test/examples/maven-project/src/com/example/AutoImportTest2.java b/src/test/examples/maven-project/src/com/example/AutoImportTest2.java new file mode 100644 index 000000000..c3adf01da --- /dev/null +++ b/src/test/examples/maven-project/src/com/example/AutoImportTest2.java @@ -0,0 +1,3 @@ +package com.example; + +public class AutoImportTest2 {} diff --git a/src/test/examples/maven-project/src/com/example/AutoImportTest3.java b/src/test/examples/maven-project/src/com/example/AutoImportTest3.java new file mode 100644 index 000000000..fc15146b3 --- /dev/null +++ b/src/test/examples/maven-project/src/com/example/AutoImportTest3.java @@ -0,0 +1,3 @@ +package com.example; + +public class AutoImportTest3 {} diff --git a/src/test/examples/maven-project/src/com/example/AutoImportTest4.java b/src/test/examples/maven-project/src/com/example/AutoImportTest4.java new file mode 100644 index 000000000..0c9ed7144 --- /dev/null +++ b/src/test/examples/maven-project/src/com/example/AutoImportTest4.java @@ -0,0 +1,3 @@ +package com.example; + +public class AutoImportTest4 {} diff --git a/src/test/examples/maven-project/src/com/example/AutoImportTest5.java b/src/test/examples/maven-project/src/com/example/AutoImportTest5.java new file mode 100644 index 000000000..e92cefe38 --- /dev/null +++ b/src/test/examples/maven-project/src/com/example/AutoImportTest5.java @@ -0,0 +1,3 @@ +package com.example; + +public class AutoImportTest5 {} diff --git a/src/test/examples/maven-project/src/com/example/AutoImportTestStatic.java b/src/test/examples/maven-project/src/com/example/AutoImportTestStatic.java new file mode 100644 index 000000000..505cf5dac --- /dev/null +++ b/src/test/examples/maven-project/src/com/example/AutoImportTestStatic.java @@ -0,0 +1,5 @@ +package com.example; + +public class AutoImportTestStatic { + public static final String CONSTANT = "CONSTANT"; +} diff --git a/src/test/examples/maven-project/src/org/javacs/imports/MultipleSections.java b/src/test/examples/maven-project/src/org/javacs/imports/MultipleSections.java new file mode 100644 index 000000000..dcc7f9591 --- /dev/null +++ b/src/test/examples/maven-project/src/org/javacs/imports/MultipleSections.java @@ -0,0 +1,9 @@ +package org.javacs.imports; + +import com.example.AutoImportTest2; +import com.example.AutoImportTest4; + +import java.util.List; +import java.util.Map; + +public class MultipleSections {} diff --git a/src/test/examples/maven-project/src/org/javacs/imports/NoImport.java b/src/test/examples/maven-project/src/org/javacs/imports/NoImport.java new file mode 100644 index 000000000..ecd9e50a2 --- /dev/null +++ b/src/test/examples/maven-project/src/org/javacs/imports/NoImport.java @@ -0,0 +1,3 @@ +package org.javacs.imports; + +public class NoImport {} diff --git a/src/test/examples/maven-project/src/org/javacs/imports/NoPackage.java b/src/test/examples/maven-project/src/org/javacs/imports/NoPackage.java new file mode 100644 index 000000000..1309d821e --- /dev/null +++ b/src/test/examples/maven-project/src/org/javacs/imports/NoPackage.java @@ -0,0 +1,3 @@ +// There is even no package declaration. + +public class NoPackage {} diff --git a/src/test/examples/maven-project/src/org/javacs/imports/SingleSection.java b/src/test/examples/maven-project/src/org/javacs/imports/SingleSection.java new file mode 100644 index 000000000..4a799062a --- /dev/null +++ b/src/test/examples/maven-project/src/org/javacs/imports/SingleSection.java @@ -0,0 +1,6 @@ +package org.javacs.imports; + +import com.example.AutoImportTest2; +import com.example.AutoImportTest4; + +public class SingleSection {} diff --git a/src/test/examples/maven-project/src/org/javacs/imports/StaticImports.java b/src/test/examples/maven-project/src/org/javacs/imports/StaticImports.java new file mode 100644 index 000000000..79533fa04 --- /dev/null +++ b/src/test/examples/maven-project/src/org/javacs/imports/StaticImports.java @@ -0,0 +1,5 @@ +package org.javacs.imports; + +import static com.example.AutoImportTestStatic.CONSTANT; + +public class StaticImports {} diff --git a/src/test/java/org/javacs/imports/ChromiumAutoImportProviderTest.java b/src/test/java/org/javacs/imports/ChromiumAutoImportProviderTest.java new file mode 100644 index 000000000..a93e7d240 --- /dev/null +++ b/src/test/java/org/javacs/imports/ChromiumAutoImportProviderTest.java @@ -0,0 +1,142 @@ +package org.javacs.imports; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import com.sun.source.util.Trees; +import java.util.List; +import java.util.stream.Collectors; +import org.javacs.CompilerProvider; +import org.javacs.LanguageServerFixture; +import org.javacs.lsp.TextEdit; +import org.junit.Test; + +public class ChromiumAutoImportProviderTest { + private static final CompilerProvider compiler = LanguageServerFixture.getCompilerProvider(); + + private List addImport(String fileName, String className) { + var path = LanguageServerFixture.DEFAULT_WORKSPACE_ROOT + .resolve("src/org/javacs/imports") + .resolve(fileName) + .toAbsolutePath(); + var task = compiler.parse(path); + var edits = ChromiumAutoImportProvider.INSTANCE.addImport(className, task.root, Trees.instance(task.task).getSourcePositions()); + return edits.stream().map(TextEdit::toString).collect(Collectors.toList()); + } + + @Test + public void noPackage() { + var edits = addImport("NoPackage.java", "com.example.AutoImportTest3"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("0,0-0,0/import com.example.AutoImportTest3;\n")); + } + + @Test + public void noImport() { + var edits = addImport("NoImport.java", "com.example.AutoImportTest3"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("1,0-1,0/\nimport com.example.AutoImportTest3;\n")); + } + + @Test + public void singleSectionFirst() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest1"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("2,0-2,0/import com.example.AutoImportTest1;\n")); + } + + @Test + public void singleSectionMiddle() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest3"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("3,0-3,0/import com.example.AutoImportTest3;\n")); + } + + @Test + public void singleSectionLast() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest5"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("4,0-4,0/import com.example.AutoImportTest5;\n")); + } + + @Test + public void multipleSectionsFirst() { + var edits = addImport("MultipleSections.java", "com.example.AutoImportTest1"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("2,0-2,0/import com.example.AutoImportTest1;\n")); + } + + @Test + public void multipleSectionsMiddle() { + var edits = addImport("MultipleSections.java", "com.example.AutoImportTest3"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("3,0-3,0/import com.example.AutoImportTest3;\n")); + } + + @Test + public void multipleSectionsLast() { + var edits = addImport("MultipleSections.java", "com.example.AutoImportTest5"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("4,0-4,0/import com.example.AutoImportTest5;\n")); + } + + @Test + public void newSectionFirst() { + var edits = addImport("MultipleSections.java", "android.Example"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("2,0-2,0/import android.Example;\n\n")); + } + + @Test + public void newSectionMiddle() { + var edits = addImport("MultipleSections.java", "dalvik.Example"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("5,0-5,0/import dalvik.Example;\n\n")); + } + + @Test + public void newSectionLast() { + var edits = addImport("MultipleSections.java", "javax.Example"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("7,0-7,0/\nimport javax.Example;\n")); + } + + @Test + public void ignoreStaticImports() { + var edits = addImport("StaticImports.java", "android.Example"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("3,0-3,0/\nimport android.Example;\n")); + } + + @Test + public void alreadyImported() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest4"); + assertThat(edits, hasSize(0)); + } + + @Test + public void samePackage() { + var edits = addImport("MultipleSections.java", "org.javacs.imports.SamePackage"); + assertThat(edits, hasSize(0)); + } + + @Test + public void childPackage() { + var edits = addImport("MultipleSections.java", "org.javacs.imports.child.ChildPackage"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("5,0-5,0/import org.javacs.imports.child.ChildPackage;\n\n")); + } +} diff --git a/src/test/java/org/javacs/imports/SimpleAutoImportProviderTest.java b/src/test/java/org/javacs/imports/SimpleAutoImportProviderTest.java new file mode 100644 index 000000000..f292acc12 --- /dev/null +++ b/src/test/java/org/javacs/imports/SimpleAutoImportProviderTest.java @@ -0,0 +1,72 @@ +package org.javacs.imports; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import com.sun.source.util.Trees; +import java.util.List; +import java.util.stream.Collectors; +import org.javacs.CompilerProvider; +import org.javacs.LanguageServerFixture; +import org.javacs.lsp.TextEdit; +import org.junit.Test; + +public class SimpleAutoImportProviderTest { + private static final CompilerProvider compiler = LanguageServerFixture.getCompilerProvider(); + + private List addImport(String fileName, String className) { + var path = LanguageServerFixture.DEFAULT_WORKSPACE_ROOT + .resolve("src/org/javacs/imports") + .resolve(fileName) + .toAbsolutePath(); + var task = compiler.parse(path); + var edits = ChromiumAutoImportProvider.INSTANCE.addImport(className, task.root, Trees.instance(task.task).getSourcePositions()); + return edits.stream().map(TextEdit::toString).collect(Collectors.toList()); + } + + @Test + public void noPackage() { + var edits = addImport("NoPackage.java", "com.example.AutoImportTest3"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("0,0-0,0/import com.example.AutoImportTest3;\n")); + } + + @Test + public void noImport() { + var edits = addImport("NoImport.java", "com.example.AutoImportTest3"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("1,0-1,0/\nimport com.example.AutoImportTest3;\n")); + } + + @Test + public void addFirst() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest1"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("2,0-2,0/import com.example.AutoImportTest1;\n")); + } + + @Test + public void addMiddle() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest3"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("3,0-3,0/import com.example.AutoImportTest3;\n")); + } + + @Test + public void addLast() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest5"); + assertThat(edits, hasSize(1)); + var edit = edits.get(0); + assertThat(edit, equalTo("4,0-4,0/import com.example.AutoImportTest5;\n")); + } + + @Test + public void alreadyImported() { + var edits = addImport("SingleSection.java", "com.example.AutoImportTest4"); + assertThat(edits, hasSize(0)); + } +}