diff --git a/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/FormatterProvider.java b/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/FormatterProvider.java index c110a2bab..20bd4df31 100644 --- a/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/FormatterProvider.java +++ b/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/FormatterProvider.java @@ -18,44 +18,37 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; -import com.google.common.base.Preconditions; -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.PluginManager; -import com.intellij.openapi.extensions.PluginId; +import com.intellij.formatting.service.FormattingService; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SystemInfo; import com.palantir.javaformat.bootstrap.BootstrappingFormatterService; import com.palantir.javaformat.bootstrap.NativeImageFormatterService; import com.palantir.javaformat.java.FormatterService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static java.util.Optional.ofNullable; final class FormatterProvider { private static final Logger log = LoggerFactory.getLogger(FormatterProvider.class); - static final String PLUGIN_ID = "open-java-format"; - // Cache to avoid resolving the formatter every time we want to format from IntelliJ private final LoadingCache> implementationCache = Caffeine.newBuilder().maximumSize(1).build(FormatterProvider::createFormatter); - static IdeaPluginDescriptor getPluginDescriptor() { - return Preconditions.checkNotNull( - PluginManager.getInstance().findEnabledPlugin(PluginId.getId(PLUGIN_ID)), - "Couldn't find our own plugin: %s", - PLUGIN_ID); - } - Optional get(Project project, PalantirJavaFormatSettings settings) { return implementationCache.get(new FormatterCacheKey( project, @@ -89,9 +82,14 @@ private static List getProvidedImplementationUrls(List implementation @SuppressWarnings("for-rollout:Slf4jLogsafeArgs") private static List getBundledImplementationUrls() { // Load from the jars bundled with the plugin. - IdeaPluginDescriptor ourPlugin = getPluginDescriptor(); - Path implDir = ourPlugin.getPluginPath().resolve("impl"); + Path implDir = ofNullable(FormattingService.EP_NAME.findExtension(PalantirJavaFormatFormattingService.class)) + .flatMap(PalantirJavaFormatFormattingService::getPluginDescriptor) + .map(PluginDescriptor::getPluginPath) + .orElseThrow(() -> new NoSuchElementException("The platform has not set the plugin descriptor")) + .resolve("impl"); + log.debug("Using open-java-format implementation bundled with plugin: {}", implDir); + return listDirAsUrlsUnchecked(implDir); } diff --git a/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingService.java b/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingService.java index fff97a830..b7b3d7763 100644 --- a/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingService.java +++ b/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingService.java @@ -16,8 +16,6 @@ package com.palantir.javaformat.intellij; -import static java.util.Comparator.comparing; - import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; import com.intellij.formatting.service.AsyncDocumentFormattingService; @@ -25,6 +23,8 @@ import com.intellij.ide.highlighter.JavaFileType; import com.intellij.ide.impl.TrustedProjects; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.PluginAware; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.NlsSafe; @@ -33,17 +33,37 @@ import com.palantir.javaformat.java.FormatterException; import com.palantir.javaformat.java.FormatterService; import com.palantir.javaformat.java.Replacement; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; -import org.jetbrains.annotations.NotNull; -class PalantirJavaFormatFormattingService extends AsyncDocumentFormattingService { +import static java.util.Comparator.comparing; +import static java.util.Optional.ofNullable; + +class PalantirJavaFormatFormattingService extends AsyncDocumentFormattingService implements PluginAware { private static final Logger logger = Logger.getInstance(PalantirJavaFormatFormattingService.class); private final FormatterProvider formatterProvider = new FormatterProvider(); + // The platform sets this right after creating the service from plugin.xml: the descriptor of the plugin that + // declared it, which is how the plugin learns its own path and version (see FormatterProvider.getPluginDescriptor). + // A service created with `new`, as the tests do, has to be given the descriptor itself. + @Nullable + private PluginDescriptor pluginDescriptor; + + @Override + public void setPluginDescriptor(@NotNull PluginDescriptor pluginDescriptor) { + this.pluginDescriptor = pluginDescriptor; + } + + Optional getPluginDescriptor() { + return ofNullable(pluginDescriptor); + } + @Override protected FormattingTask createFormattingTask(@NotNull AsyncFormattingRequest request) { Project project = request.getContext().getProject(); @@ -108,7 +128,7 @@ public void run() { if (logger.isDebugEnabled()) { logger.debug(String.format( "Received request to format file=%s, length=%s with ranges=%s", - Optional.ofNullable(request.getIOFile()) + ofNullable(request.getIOFile()) .map(file -> file.toPath().toString()) .orElse("null"), preFormatText.length(), diff --git a/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatSettings.java b/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatSettings.java index 219bbc817..4271f3764 100644 --- a/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatSettings.java +++ b/open-java-format-idea-plugin/src/main/java/com/palantir/javaformat/intellij/PalantirJavaFormatSettings.java @@ -16,12 +16,16 @@ package com.palantir.javaformat.intellij; +import com.intellij.formatting.service.FormattingService; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.project.Project; import com.palantir.javaformat.java.FormatterService; import com.palantir.javaformat.java.JavaFormatterOptions; + +import javax.annotation.Nullable; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; @@ -30,7 +34,8 @@ import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.Nullable; + +import static java.util.Optional.ofNullable; @SuppressWarnings("for-rollout:SameNameButDifferent") @State( @@ -109,7 +114,9 @@ boolean injectedVersionIsOutdated() { } Optional getImplementationVersion() { - return Optional.ofNullable(FormatterProvider.getPluginDescriptor().getVersion()); + return ofNullable(FormattingService.EP_NAME.findExtension(PalantirJavaFormatFormattingService.class)) + .flatMap(PalantirJavaFormatFormattingService::getPluginDescriptor) + .map(PluginDescriptor::getVersion); } Optional computeFormatterVersion() { diff --git a/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatConfigurableTest.java b/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatConfigurableTest.java index a54fd93af..9c8cc5b5b 100644 --- a/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatConfigurableTest.java +++ b/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatConfigurableTest.java @@ -92,6 +92,15 @@ public void readsAndWritesTheEnabledSetting() throws Throwable { assertThat(settings.isEnabled()).isTrue(); } + @Test + public void showsTheVersionOfThePlugin() { + // The "Plugin version" row: the version the platform read from the plugin's own descriptor, not "unknown". + // Whatever the build stamped: CI checks out without tags, so there it is a commit hash rather than a + // release-like version. + assertThat(settings.getImplementationVersion()) + .hasValueSatisfying(version -> assertThat(version).isNotBlank()); + } + private static Optional findCheckBox(Component root) { if (root instanceof JCheckBox checkBox) { return Optional.of(checkBox); diff --git a/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingServiceTest.java b/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingServiceTest.java index 6cf9f76c6..5cd397600 100644 --- a/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingServiceTest.java +++ b/open-java-format-idea-plugin/src/test/java/com/palantir/javaformat/intellij/PalantirJavaFormatFormattingServiceTest.java @@ -16,9 +16,6 @@ package com.palantir.javaformat.intellij; -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.common.collect.ImmutableList; import com.intellij.codeInsight.actions.ReformatCodeProcessor; import com.intellij.formatting.service.AsyncFormattingRequest; import com.intellij.formatting.service.FormattingService; @@ -39,16 +36,20 @@ import com.intellij.testFramework.fixtures.JavaTestFixtureFactory; import com.intellij.testFramework.fixtures.TestFixtureBuilder; import com.palantir.javaformat.intellij.PalantirJavaFormatSettings.State; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; public class PalantirJavaFormatFormattingServiceTest { private JavaCodeInsightTestFixture fixture; @@ -63,10 +64,12 @@ public void setUp() throws Exception { fixture.setUp(); delegatingFormatter = new DelegatingFormatter(); + ExtensionTestUtil.maskExtensions( - FormattingService.EP_NAME, ImmutableList.of(delegatingFormatter), fixture.getProjectDisposable()); + FormattingService.EP_NAME, List.of(delegatingFormatter), fixture.getProjectDisposable()); settings = PalantirJavaFormatSettings.getInstance(fixture.getProject()); + State resetState = new State(); resetState.setEnabled("true"); settings.loadState(resetState); @@ -126,6 +129,13 @@ private PsiFile createPsiFile(String path, String... contents) throws IOExceptio } private static final class DelegatingFormatter extends PalantirJavaFormatFormattingService { + @SuppressWarnings("DataFlowIssue") + public DelegatingFormatter() { + this.setPluginDescriptor(FormattingService.EP_NAME + .findExtension(PalantirJavaFormatFormattingService.class) + .getPluginDescriptor() + .orElseThrow()); + } private boolean invoked = false;