diff --git a/build.gradle.kts b/build.gradle.kts index b1314479f..ec5f11b3a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -114,6 +114,11 @@ dependencies { // https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html#project-setup bundledPlugins(bundledPluginList) plugins(pluginList) + + if (sinceBuildInput == "243" || sinceBuildInput == "251") { + bundledModule("intellij.platform.coverage") + bundledModule("intellij.platform.coverage.agent") + } pluginVerifier() } } diff --git a/flutter-idea/build.gradle.kts b/flutter-idea/build.gradle.kts index a5e47284d..07efc2bb5 100644 --- a/flutter-idea/build.gradle.kts +++ b/flutter-idea/build.gradle.kts @@ -79,7 +79,8 @@ dependencies { "Git4Idea", "org.jetbrains.kotlin", "org.jetbrains.plugins.gradle", - "org.intellij.intelliLang") + "org.intellij.intelliLang", + ) if (ideaProduct == "android-studio") { bundledPluginList.add("org.jetbrains.android") bundledPluginList.add("com.android.tools.idea.smali") @@ -93,6 +94,11 @@ dependencies { // https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html#project-setup bundledPlugins(bundledPluginList) plugins(pluginList) + + if (sinceBuildInput == "243" || sinceBuildInput == "251") { + bundledModule("intellij.platform.coverage") + bundledModule("intellij.platform.coverage.agent") + } pluginVerifier() } } diff --git a/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageAnnotator.java b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageAnnotator.java new file mode 100644 index 000000000..d24cde8b3 --- /dev/null +++ b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageAnnotator.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.coverage; + +import com.intellij.coverage.CoverageDataManager; +import com.intellij.coverage.CoverageSuitesBundle; +import com.intellij.coverage.SimpleCoverageAnnotator; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.vfs.VirtualFile; +import io.flutter.utils.FlutterModuleUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class FlutterCoverageAnnotator extends SimpleCoverageAnnotator { + + @Nullable + public static FlutterCoverageAnnotator getInstance(Project project) { + return project.getService(FlutterCoverageAnnotator.class); + } + + public FlutterCoverageAnnotator(Project project) { + super(project); + } + + @Override + protected FileCoverageInfo fillInfoForUncoveredFile(@NotNull File file) { + return new FileCoverageInfo(); + } + + @Override + protected boolean shouldCollectCoverageInsideLibraryDirs() { + return false; + } + + @Override + protected VirtualFile[] getRoots(Project project, + @NotNull CoverageDataManager dataManager, + CoverageSuitesBundle suite) { + return dataManager.doInReadActionIfProjectOpen(() -> { + final List roots = new ArrayList<>(); + for (Module module : FlutterModuleUtils.findModulesWithFlutterContents(project)) { + final ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + roots.addAll(Arrays.asList(rootManager.getContentRoots())); + } + return roots.toArray(VirtualFile.EMPTY_ARRAY); + }); + } +} \ No newline at end of file diff --git a/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageEnabledConfiguration.java b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageEnabledConfiguration.java new file mode 100644 index 000000000..0774163df --- /dev/null +++ b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageEnabledConfiguration.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.coverage; + +import com.intellij.coverage.CoverageDataManager; +import com.intellij.coverage.CoverageRunner; +import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.execution.configurations.coverage.CoverageEnabledConfiguration; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ModalityUiUtil; +import io.flutter.pub.PubRoot; +import io.flutter.run.test.TestConfig; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class FlutterCoverageEnabledConfiguration extends CoverageEnabledConfiguration { + private static final Logger LOG = Logger.getInstance(FlutterCoverageEnabledConfiguration.class.getName()); + + public FlutterCoverageEnabledConfiguration(@NotNull RunConfigurationBase configuration) { + super(configuration); + super.setCoverageRunner(CoverageRunner.getInstance(FlutterCoverageRunner.class)); + createCoverageFile(); + ModalityUiUtil.invokeLaterIfNeeded( + ModalityState.any(), + () -> setCurrentCoverageSuite(CoverageDataManager.getInstance(configuration.getProject()).addCoverageSuite(this))); + } + + @Override + protected String createCoverageFile() { + if (myCoverageFilePath == null) { + if (!(getConfiguration() instanceof TestConfig)) { + return ""; + } + VirtualFile file = ((TestConfig)getConfiguration()).getFields().getFileOrDir(); + final VirtualFile root = PubRoot.forFile(file).getRoot(); + myCoverageFilePath = root.getPath() + "/coverage/lcov.info"; + } + return myCoverageFilePath; + } + + @Override + public void setCoverageRunner(@Nullable final CoverageRunner coverageRunner) { + // Save and restore myCoverageFilePath because the super method clears it. + final String path = myCoverageFilePath; + super.setCoverageRunner(coverageRunner); + myCoverageFilePath = path; + } + + @Override + public void coverageRunnerExtensionRemoved(@NotNull CoverageRunner runner) { + final String path = myCoverageFilePath; + super.coverageRunnerExtensionRemoved(runner); + myCoverageFilePath = path; + } +} \ No newline at end of file diff --git a/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageEngine.java b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageEngine.java new file mode 100644 index 000000000..93adc4764 --- /dev/null +++ b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageEngine.java @@ -0,0 +1,163 @@ +/* + * Copyright 2021 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.coverage; + +import com.intellij.coverage.CoverageAnnotator; +import com.intellij.coverage.CoverageEngine; +import com.intellij.coverage.CoverageFileProvider; +import com.intellij.coverage.CoverageRunner; +import com.intellij.coverage.CoverageSuite; +import com.intellij.coverage.CoverageSuitesBundle; +import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.WrappingRunConfiguration; +import com.intellij.execution.configurations.coverage.CoverageEnabledConfiguration; +import com.intellij.execution.testframework.AbstractTestProxy; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.jetbrains.lang.dart.DartFileType; +import com.jetbrains.lang.dart.psi.DartFile; +import io.flutter.FlutterBundle; +import io.flutter.FlutterUtils; +import io.flutter.pub.PubRoot; +import io.flutter.run.test.TestConfig; + +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class FlutterCoverageEngine extends CoverageEngine { + + public static FlutterCoverageEngine getInstance() { + return CoverageEngine.EP_NAME.findExtensionOrFail(FlutterCoverageEngine.class); + } + + @Override + public boolean isApplicableTo(@NotNull RunConfigurationBase conf) { + return unwrapRunProfile(conf) instanceof TestConfig; + } + + @Override + public boolean canHavePerTestCoverage(@NotNull RunConfigurationBase conf) { + return true; + } + + @Override + public @NotNull CoverageEnabledConfiguration createCoverageEnabledConfiguration(@NotNull RunConfigurationBase conf) { + return new FlutterCoverageEnabledConfiguration(conf); + } + + @Override + public @Nullable CoverageSuite createCoverageSuite(@NotNull CoverageRunner covRunner, + @NotNull String name, + @NotNull CoverageFileProvider coverageDataFileProvider, + @Nullable String[] filters, + long lastCoverageTimeStamp, + @Nullable String suiteToMerge, + boolean coverageByTestEnabled, + boolean tracingEnabled, + boolean trackTestFolders, + Project project) { + return null; + } + + @Override + public @Nullable CoverageSuite createCoverageSuite(@NotNull CoverageRunner covRunner, + @NotNull String name, + @NotNull CoverageFileProvider coverageDataFileProvider, + @NotNull CoverageEnabledConfiguration config) { + if (config instanceof FlutterCoverageEnabledConfiguration) { + return new FlutterCoverageSuite(covRunner, name, coverageDataFileProvider, + config.getConfiguration().getProject(), this); + } + return null; + } + + @Override + public @Nullable CoverageSuite createEmptyCoverageSuite(@NotNull CoverageRunner coverageRunner) { + return new FlutterCoverageSuite(this); + } + + @Override + public @NotNull CoverageAnnotator getCoverageAnnotator(Project project) { + return FlutterCoverageAnnotator.getInstance(project); + } + + @Override + public boolean coverageEditorHighlightingApplicableTo(@NotNull PsiFile psiFile) { + final PubRoot root = PubRoot.forPsiFile(psiFile); + if (root == null) return false; + final VirtualFile file = psiFile.getVirtualFile(); + if (file == null) return false; + final String path = root.getRelativePath(file); + if (path == null) return false; + return path.startsWith("lib") && FlutterUtils.isDartFile(file); + } + + @Override + public boolean coverageProjectViewStatisticsApplicableTo(VirtualFile fileOrDir) { + return !fileOrDir.isDirectory() && fileOrDir.getFileType() instanceof DartFileType; + } + + @Override + public boolean acceptedByFilters(@NotNull PsiFile psiFile, @NotNull CoverageSuitesBundle suite) { + return psiFile instanceof DartFile; + } + + @Override + public boolean recompileProjectAndRerunAction(@NotNull Module module, + @NotNull CoverageSuitesBundle suite, + @NotNull Runnable chooseSuiteAction) { + return false; + } + + @Override + public String getQualifiedName(@NotNull final File outputFile, + @NotNull final PsiFile sourceFile) { + return getQName(sourceFile); + } + + @Override + public @NotNull Set getQualifiedNames(@NotNull PsiFile sourceFile) { + final Set qualifiedNames = new HashSet<>(); + qualifiedNames.add(getQName(sourceFile)); + return qualifiedNames; + } + + @Override + public List findTestsByNames(@NotNull String[] testNames, @NotNull Project project) { + return null; + } + + @Override + public @Nullable String getTestMethodName(@NotNull PsiElement element, @NotNull AbstractTestProxy testProxy) { + return null; + } + + @Override + public String getPresentableText() { + return FlutterBundle.message("flutter.coverage.presentable.text"); + } + + @NotNull + private static String getQName(@NotNull PsiFile sourceFile) { + return sourceFile.getVirtualFile().getPath(); + } + + static @NotNull RunProfile unwrapRunProfile(@NotNull RunProfile runProfile) { + if (runProfile instanceof WrappingRunConfiguration) { + return ((WrappingRunConfiguration)runProfile).getPeer(); + } + return runProfile; + } +} \ No newline at end of file diff --git a/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageProgramRunner.java b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageProgramRunner.java new file mode 100644 index 000000000..98b257f60 --- /dev/null +++ b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageProgramRunner.java @@ -0,0 +1,105 @@ +/* + * Copyright 2021 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.coverage; + +import com.intellij.coverage.CoverageDataManager; +import com.intellij.coverage.CoverageExecutor; +import com.intellij.coverage.CoverageRunnerData; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.ConfigurationInfoProvider; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.configurations.coverage.CoverageEnabledConfiguration; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.runners.DefaultProgramRunnerKt; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.GenericProgramRunner; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import io.flutter.FlutterBundle; +import io.flutter.run.test.TestConfig; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FlutterCoverageProgramRunner extends GenericProgramRunner { + private static final Logger LOG = Logger.getInstance(FlutterCoverageProgramRunner.class.getName()); + + private static final String ID = "FlutterCoverageProgramRunner"; + private ProcessHandler handler; + private ProcessAdapter listener; + + @Override + public @NotNull + @NonNls + String getRunnerId() { + return ID; + } + + @Override + public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) { + return executorId.equals(CoverageExecutor.EXECUTOR_ID) && profile instanceof TestConfig; + } + + @Override + public RunnerSettings createConfigurationData(@NotNull final ConfigurationInfoProvider settingsProvider) { + return new CoverageRunnerData(); + } + + @Override + @Nullable + protected RunContentDescriptor doExecute(final @NotNull RunProfileState state, + final @NotNull ExecutionEnvironment env) throws ExecutionException { + final RunContentDescriptor result = DefaultProgramRunnerKt.executeState(state, env, this); + if (result == null) { + return null; + } + handler = result.getProcessHandler(); + if (handler != null) { + listener = new ProcessAdapter() { + @Override + public void processTerminated(@NotNull ProcessEvent event) { + ApplicationManager.getApplication().invokeLater(() -> processCoverage(env)); + } + }; + handler.addProcessListener(listener); + } + return result; + } + + private void processCoverage(ExecutionEnvironment env) { + if (!(env.getRunProfile() instanceof TestConfig runConfig)) return; + final CoverageEnabledConfiguration configuration = CoverageEnabledConfiguration.getOrCreate(runConfig); + if (configuration.getCoverageFilePath() == null) return; + + final Path path = Paths.get(configuration.getCoverageFilePath()); + final Path cov = path.getParent(); + VfsUtil.markDirtyAndRefresh(false, false, true, LocalFileSystem.getInstance().findFileByPath(cov.getParent().toString())); + VfsUtil.markDirtyAndRefresh(false, true, true, LocalFileSystem.getInstance().findFileByPath(cov.toString())); + if (Files.exists(path)) { + @Nullable final RunnerSettings settings = env.getRunnerSettings(); + if (settings != null) { + CoverageDataManager.getInstance(env.getProject()).processGatheredCoverage(runConfig, settings); + handler.removeProcessListener(listener); + handler = null; + listener = null; + } + } + else { + LOG.error(FlutterBundle.message("coverage.path.not.found", path)); + } + } +} \ No newline at end of file diff --git a/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageRunner.java b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageRunner.java new file mode 100644 index 000000000..46e5e3ce6 --- /dev/null +++ b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageRunner.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.coverage; + +import com.intellij.coverage.CoverageEngine; +import com.intellij.coverage.CoverageRunner; +import com.intellij.coverage.CoverageSuite; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.rt.coverage.data.ProjectData; +import io.flutter.FlutterBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; + +public class FlutterCoverageRunner extends CoverageRunner { + private static final String ID = "FlutterCoverageRunner"; + private static final Logger LOG = Logger.getInstance(FlutterCoverageRunner.class.getName()); + + @Nullable + @Override + public ProjectData loadCoverageData(@NotNull final File sessionDataFile, @Nullable CoverageSuite baseCoverageSuite) { + if (!(baseCoverageSuite instanceof FlutterCoverageSuite)) { + return null; + } + return doLoadCoverageData(sessionDataFile, (FlutterCoverageSuite)baseCoverageSuite); + } + + @Nullable + private static ProjectData doLoadCoverageData(@NotNull final File sessionDataFile, @NotNull final FlutterCoverageSuite coverageSuite) { + final ProjectData projectData = new ProjectData(); + try { + LcovInfo.readInto(projectData, sessionDataFile); + } + catch (IOException ex) { + LOG.warn(FlutterBundle.message("coverage.data.not.read", sessionDataFile.getAbsolutePath())); + return null; + } + return projectData; + } + + @NotNull + @Override + public String getPresentableName() { + return "Flutter"; + } + + @NotNull + @Override + public String getId() { + return ID; + } + + @NotNull + @Override + public String getDataFileExtension() { + return "info"; + } + + @Override + public boolean acceptsCoverageEngine(@NotNull CoverageEngine engine) { + return engine instanceof FlutterCoverageEngine; + } +} diff --git a/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageSuite.java b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageSuite.java new file mode 100644 index 000000000..6ba6f1810 --- /dev/null +++ b/flutter-idea/src/io/flutter/run/coverage/FlutterCoverageSuite.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.coverage; + +import com.intellij.coverage.BaseCoverageSuite; +import com.intellij.coverage.CoverageEngine; +import com.intellij.coverage.CoverageFileProvider; +import com.intellij.coverage.CoverageRunner; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +public class FlutterCoverageSuite extends BaseCoverageSuite { + + @NotNull final private FlutterCoverageEngine coverageEngine; + + public FlutterCoverageSuite(@NotNull FlutterCoverageEngine coverageEngine) { + this.coverageEngine = coverageEngine; + } + + public FlutterCoverageSuite(CoverageRunner runner, + String name, + CoverageFileProvider coverageDataFileProvider, + Project project, + @NotNull FlutterCoverageEngine coverageEngine + ) { + super(name, coverageDataFileProvider, System.currentTimeMillis(), false, false, + false, runner, project); + this.coverageEngine = coverageEngine; + } + + @Override + public @NotNull CoverageEngine getCoverageEngine() { + return coverageEngine; + } + + @Override + public void deleteCachedCoverageData() { + } +} diff --git a/flutter-idea/src/io/flutter/run/coverage/LcovInfo.java b/flutter-idea/src/io/flutter/run/coverage/LcovInfo.java new file mode 100644 index 000000000..a27618558 --- /dev/null +++ b/flutter-idea/src/io/flutter/run/coverage/LcovInfo.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.coverage; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.rt.coverage.data.ClassData; +import com.intellij.rt.coverage.data.LineData; +import com.intellij.rt.coverage.data.ProjectData; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +public class LcovInfo { + private static final Logger LOG = Logger.getInstance(LcovInfo.class.getName()); + + private static final String FILE_LABEL = "SF:"; + private static final String DATA_LABEL = "DA:"; + private static final String END_LABEL = "end_of_record"; + + private final Map> counts = new HashMap<>(); + + private Path currentFile = null; + private List lineCounts = null; + private final String base; + + private LcovInfo(String base) { + this.base = base; + } + + public static void readInto(@NotNull ProjectData data, @NotNull File file) throws IOException { + final String filePath = file.getAbsolutePath(); + final int index = filePath.indexOf("coverage"); + if (index < 0) { + // TODO Define at least one class in data + return; + } + final LcovInfo lcov = new LcovInfo(filePath.substring(0, index)); + try (final Stream lines = Files.lines(file.toPath())) { + lines.forEach(lcov::processLine); + } + for (String path : lcov.counts.keySet()) { + final List list = lcov.counts.get(path); + if (list == null || list.isEmpty()) { + continue; + } + final ClassData classData = data.getOrCreateClassData(path); + classData.setSource(fullPath(path)); + final int max = list.get(list.size() - 1).lineNum + 1; + final LineData[] lines = new LineData[max]; + for (LineCount line : list) { + final LineData lineData = new LineData(line.lineNum, null); + lineData.setHits(line.execCount); + lines[line.lineNum] = lineData; + classData.registerMethodSignature(lineData); + } + classData.setLines(lines); + } + } + + void processLine(String line) { + line = line.trim(); + if (line.startsWith(DATA_LABEL)) { + assert currentFile != null; + assert lineCounts != null; + addLineCount(line.substring(DATA_LABEL.length())); + } + else if (line.startsWith(FILE_LABEL)) { + //currentFile = Paths.get(line.substring(FILE_LABEL.length())).normalize(); + final File file = new File(base, line.substring(FILE_LABEL.length())); + final URI normalize = file.toURI().normalize(); + currentFile = Paths.get(normalize); + lineCounts = new ArrayList<>(); + } + else if (line.equals(END_LABEL)) { + storeLineCounts(); + currentFile = null; + lineCounts = null; + } + } + + private void addLineCount(String data) { + final String[] parts = data.split(","); + assert parts.length >= 2; + final int lineNum = safelyParse(parts[0]); + final int execCount = safelyParse(parts[1]); + lineCounts.add(new LineCount(lineNum, execCount)); + } + + private static int safelyParse(String val) { + try { + return Integer.parseInt(val); + } + catch (NumberFormatException ex) { + return 0; + } + } + + private void storeLineCounts() { + final String path = fullPath(currentFile.toString()); + counts.put(path, lineCounts); + } + + private static String fullPath(String path) { + String absPath = new File(path).getAbsolutePath(); + if (SystemInfo.isWindows) { + absPath = absPath.replaceAll("\\\\", "/"); + } + return absPath; + } + + private static class LineCount { + int lineNum; + int execCount; + + public LineCount(int num, int count) { + lineNum = num; + execCount = count; + } + } +} \ No newline at end of file diff --git a/flutter-studio/build.gradle.kts b/flutter-studio/build.gradle.kts index b392414e2..3420ac776 100644 --- a/flutter-studio/build.gradle.kts +++ b/flutter-studio/build.gradle.kts @@ -94,6 +94,11 @@ dependencies { // https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html#project-setup bundledPlugins(bundledPluginList) plugins(pluginList) + + if (sinceBuildInput == "243" || sinceBuildInput == "251") { + bundledModule("intellij.platform.coverage") + bundledModule("intellij.platform.coverage.agent") + } } } diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 6f328f759..a4fde2e1d 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -36,6 +36,7 @@ com.intellij.modules.java + com.intellij.modules.coverage com.google.tools.ij.aiplugin diff --git a/resources/META-INF/plugin_template.xml b/resources/META-INF/plugin_template.xml index 9512b13f8..66d3d917b 100644 --- a/resources/META-INF/plugin_template.xml +++ b/resources/META-INF/plugin_template.xml @@ -34,6 +34,7 @@ com.intellij.modules.java + com.intellij.modules.coverage com.google.tools.ij.aiplugin