Skip to content

Commit e26ffb3

Browse files
authored
[CQ] introduce null-safe OpenApi wrappers (#8139)
Currently there are a number of common platform accesses that are not safe, prone to runtime exceptions and spam the inspection log with nullability warnings. This change introduces a host of wrappers around common IntelliJ Open API calls that introduce null-safety, reducing NPE risk, quieting 86 "Nullability and data flow problems" inspections. Specifically, this change introduces and migrates calls to new `OpenApiUtils` utility methods: * `safeRunWriteAction` * `safeInvokeAndWait` * `safeInvokeLater` * `getModules` * `getLibraryTable` * `getContentRoots` --- - [x] I’ve reviewed the contributor guide and applied the relevant portions to this PR. <details> <summary>Contribution guidelines:</summary><br> - See our [contributor guide]([https://github.com/dart-lang/sdk/blob/main/CONTRIBUTING.md](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview) for general expectations for PRs. - Larger or significant changes should be discussed in an issue before creating a PR. - Dart contributions to our repos should follow the [Dart style guide](https://dart.dev/guides/language/effective-dart) and use `dart format`. - Java and Kotlin contributions should strive to follow Java and Kotlin best practices ([discussion](#8098)). </details>
1 parent bd618ad commit e26ffb3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+258
-187
lines changed

flutter-idea/src/io/flutter/FlutterInitializer.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import com.intellij.ide.browsers.BrowserLauncher;
1111
import com.intellij.ide.plugins.PluginManagerCore;
1212
import com.intellij.ide.ui.UISettingsListener;
13-
import com.intellij.notification.*;
13+
import com.intellij.notification.Notification;
14+
import com.intellij.notification.NotificationType;
15+
import com.intellij.notification.Notifications;
1416
import com.intellij.openapi.actionSystem.AnAction;
1517
import com.intellij.openapi.actionSystem.AnActionEvent;
1618
import com.intellij.openapi.application.ApplicationManager;
@@ -20,7 +22,6 @@
2022
import com.intellij.openapi.extensions.PluginId;
2123
import com.intellij.openapi.fileEditor.FileEditorManager;
2224
import com.intellij.openapi.module.Module;
23-
import com.intellij.openapi.module.ModuleManager;
2425
import com.intellij.openapi.project.ModuleListener;
2526
import com.intellij.openapi.project.Project;
2627
import com.intellij.openapi.roots.ProjectRootManager;
@@ -50,6 +51,7 @@
5051
import io.flutter.settings.FlutterSettings;
5152
import io.flutter.survey.FlutterSurveyNotifications;
5253
import io.flutter.utils.FlutterModuleUtils;
54+
import io.flutter.utils.OpenApiUtils;
5355
import io.flutter.view.FlutterViewFactory;
5456
import org.jetbrains.annotations.NotNull;
5557

@@ -90,7 +92,7 @@ public void runActivity(@NotNull Project project) {
9092
// If the project declares a Flutter dependency, do some extra initialization.
9193
boolean hasFlutterModule = false;
9294

93-
for (Module module : ModuleManager.getInstance(project).getModules()) {
95+
for (Module module : OpenApiUtils.getModules(project)) {
9496
final boolean declaresFlutter = FlutterModuleUtils.declaresFlutter(module);
9597

9698
hasFlutterModule = hasFlutterModule || declaresFlutter;
@@ -289,7 +291,7 @@ private void checkSdkVersionNotification(@NotNull Project project) {
289291
final FlutterSettings settings = FlutterSettings.getInstance();
290292
if (settings == null || settings.isSdkVersionOutdatedWarningAcknowledged(version.getVersionText())) return;
291293

292-
ApplicationManager.getApplication().invokeLater(() -> {
294+
OpenApiUtils.safeInvokeLater(() -> {
293295
final Notification notification = new Notification(FlutterMessages.FLUTTER_NOTIFICATION_GROUP_ID,
294296
"Flutter SDK requires update",
295297
"Support for v" +
@@ -383,6 +385,6 @@ private void ensureAndroidSdk(@NotNull Project project) {
383385
return; // ANDROID_HOME not set or Android SDK not created in IDEA; not clear what to do.
384386
}
385387

386-
ApplicationManager.getApplication().runWriteAction(() -> wanted.setCurrent(project));
388+
OpenApiUtils.safeRunWriteAction(() -> wanted.setCurrent(project));
387389
}
388390
}

flutter-idea/src/io/flutter/FlutterUtils.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import com.intellij.openapi.diagnostic.Logger;
1313
import com.intellij.openapi.extensions.PluginId;
1414
import com.intellij.openapi.module.Module;
15-
import com.intellij.openapi.module.ModuleManager;
1615
import com.intellij.openapi.project.Project;
1716
import com.intellij.openapi.project.ProjectManager;
1817
import com.intellij.openapi.roots.*;
@@ -34,6 +33,7 @@
3433
import io.flutter.settings.FlutterSettings;
3534
import io.flutter.utils.AndroidUtils;
3635
import io.flutter.utils.FlutterModuleUtils;
36+
import io.flutter.utils.OpenApiUtils;
3737
import io.flutter.view.EmbeddedBrowser;
3838
import io.flutter.view.EmbeddedJcefBrowser;
3939
import org.jetbrains.annotations.NotNull;
@@ -479,8 +479,7 @@ private static VirtualFile getFlutterManagedAndroidDir(VirtualFile dir) {
479479

480480
@Nullable
481481
public static Module findModuleNamed(@NotNull Project project, @NotNull String name) {
482-
final Module[] modules = ModuleManager.getInstance(project).getModules();
483-
assert modules != null;
482+
final Module[] modules = OpenApiUtils.getModules(project);
484483
for (Module module : modules) {
485484
assert module != null;
486485
if (module.getName().equals(name)) {

flutter-idea/src/io/flutter/actions/AttachDebuggerAction.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import com.intellij.execution.process.ProcessHandler;
1111
import com.intellij.execution.runners.ExecutionEnvironment;
1212
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
13-
import com.intellij.openapi.actionSystem.*;
14-
import com.intellij.openapi.application.ApplicationManager;
13+
import com.intellij.openapi.actionSystem.ActionUpdateThread;
14+
import com.intellij.openapi.actionSystem.AnActionEvent;
15+
import com.intellij.openapi.actionSystem.DataContext;
1516
import com.intellij.openapi.application.ModalityState;
1617
import com.intellij.openapi.project.Project;
1718
import com.intellij.openapi.ui.DialogWrapper;
@@ -30,6 +31,7 @@
3031
import io.flutter.run.bazel.BazelRunConfig;
3132
import io.flutter.sdk.FlutterSdk;
3233
import io.flutter.sdk.FlutterSdkUtil;
34+
import io.flutter.utils.OpenApiUtils;
3335
import org.jetbrains.annotations.NotNull;
3436
import org.jetbrains.annotations.Nullable;
3537

@@ -200,7 +202,7 @@ public void processTerminated(@NotNull String executorId,
200202
}
201203

202204
private static void showSelectConfigDialog() {
203-
ApplicationManager.getApplication().invokeLater(() -> new SelectConfigDialog().show(), ModalityState.nonModal());
205+
OpenApiUtils.safeInvokeLater(() -> new SelectConfigDialog().show(), ModalityState.nonModal());
204206
}
205207

206208
private static class SelectConfigDialog extends DialogWrapper {

flutter-idea/src/io/flutter/actions/DeviceSelectorRefresherAction.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ public void actionPerformed(@NotNull AnActionEvent e) {
3131

3232
@Override
3333
public void update(@NotNull AnActionEvent e) {
34-
if (e.getProject() != null) {
35-
e.getPresentation().setVisible(FlutterModuleUtils.hasInternalDartSdkPath(e.getProject()));
34+
var project = e.getProject();
35+
if (project != null) {
36+
e.getPresentation().setVisible(FlutterModuleUtils.hasInternalDartSdkPath(project));
3637
}
3738
}
3839
}

flutter-idea/src/io/flutter/actions/FlutterBuildActionGroup.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
import com.intellij.openapi.module.Module;
1313
import com.intellij.openapi.module.ModuleUtilCore;
1414
import com.intellij.openapi.project.Project;
15-
import com.intellij.openapi.roots.ModuleRootManager;
1615
import com.intellij.openapi.util.SystemInfo;
1716
import com.intellij.openapi.vfs.VirtualFile;
1817
import io.flutter.FlutterMessages;
1918
import io.flutter.pub.PubRoot;
2019
import io.flutter.pub.PubRoots;
2120
import io.flutter.sdk.FlutterSdk;
2221
import io.flutter.utils.FlutterModuleUtils;
22+
import io.flutter.utils.OpenApiUtils;
2323
import io.flutter.utils.ProgressHelper;
2424
import org.jetbrains.annotations.NotNull;
2525
import org.jetbrains.annotations.Nullable;
@@ -105,7 +105,7 @@ public static Module findFlutterModule(@NotNull Project project, @NotNull Virtua
105105
return module;
106106
}
107107
// We may get here if the file is in the Android module of a Flutter module project.
108-
final VirtualFile parent = ModuleRootManager.getInstance(module).getContentRoots()[0].getParent();
108+
final VirtualFile parent = OpenApiUtils.getContentRoots(module)[0].getParent();
109109
module = ModuleUtilCore.findModuleForFile(parent, project);
110110
if (module == null) {
111111
return null;

flutter-idea/src/io/flutter/analytics/UnifiedAnalytics.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
import com.intellij.notification.Notifications;
1313
import com.intellij.openapi.actionSystem.AnAction;
1414
import com.intellij.openapi.actionSystem.AnActionEvent;
15-
import com.intellij.openapi.application.ApplicationManager;
1615
import com.intellij.openapi.diagnostic.Logger;
1716
import com.intellij.openapi.project.Project;
1817
import com.jetbrains.lang.dart.ide.toolingDaemon.DartToolingDaemonService;
1918
import de.roderick.weberknecht.WebSocketException;
2019
import io.flutter.dart.DtdUtils;
2120
import io.flutter.sdk.FlutterSdkUtil;
21+
import io.flutter.utils.OpenApiUtils;
2222
import org.jetbrains.annotations.NotNull;
2323
import org.jetbrains.annotations.Nullable;
2424

@@ -118,7 +118,9 @@ private CompletableFuture<JsonObject> makeUnifiedAnalyticsRequest(String request
118118
});
119119
}
120120

121-
private @Nullable CompletableFuture<Boolean> setTelemetry(@NotNull DartToolingDaemonService service, @NotNull JsonObject params, Boolean canSendAnalytics) {
121+
private @Nullable CompletableFuture<Boolean> setTelemetry(@NotNull DartToolingDaemonService service,
122+
@NotNull JsonObject params,
123+
Boolean canSendAnalytics) {
122124
params.addProperty("enable", canSendAnalytics);
123125
return makeUnifiedAnalyticsRequest("setTelemetry", service, params).thenCompose(result -> {
124126
assert result != null;
@@ -135,7 +137,7 @@ private CompletableFuture<JsonObject> makeUnifiedAnalyticsRequest(String request
135137

136138
private CompletableFuture<Boolean> showMessage(@NotNull String message) {
137139
CompletableFuture<Boolean> finalResult = new CompletableFuture<>();
138-
ApplicationManager.getApplication().invokeLater(() -> {
140+
OpenApiUtils.safeInvokeLater(() -> {
139141
final Notification notification = new Notification(
140142
"Flutter Usage Statistics", // Analytics.GROUP_DISPLAY_ID,
141143
"Welcome to Flutter!",

flutter-idea/src/io/flutter/android/AndroidEmulator.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.intellij.util.ReflectionUtil;
2222
import io.flutter.FlutterMessages;
2323
import io.flutter.utils.MostlySilentColoredProcessHandler;
24+
import io.flutter.utils.OpenApiUtils;
2425
import org.jetbrains.annotations.NotNull;
2526

2627
import java.lang.reflect.InvocationTargetException;
@@ -119,7 +120,7 @@ private void openEmulatorToolWindow(boolean shouldLaunchEmulatorInToolWindow) {
119120
}
120121

121122
assert ApplicationManager.getApplication() != null;
122-
ApplicationManager.getApplication().invokeLater(() -> {
123+
OpenApiUtils.safeInvokeLater(() -> {
123124
tw.setAutoHide(false);
124125
tw.show();
125126
}, ModalityState.stateForComponent(tw.getComponent()));

flutter-idea/src/io/flutter/bazel/Workspace.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import com.intellij.openapi.diagnostic.Logger;
1313
import com.intellij.openapi.module.Module;
1414
import com.intellij.openapi.project.Project;
15-
import com.intellij.openapi.roots.ModuleRootManager;
1615
import com.intellij.openapi.roots.ProjectRootManager;
1716
import com.intellij.openapi.util.Computable;
1817
import com.intellij.openapi.vfs.InvalidVirtualFileAccessException;
@@ -90,7 +89,7 @@ private Workspace(@NotNull VirtualFile root,
9089
@NotNull
9190
public ImmutableSet<String> getContentPaths(@NotNull final Module module) {
9291
// Find all the content roots within this workspace.
93-
final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
92+
final VirtualFile[] contentRoots = OpenApiUtils.getContentRoots(module);
9493
final ImmutableSet.Builder<String> result = ImmutableSet.builder();
9594
for (VirtualFile root : contentRoots) {
9695
final String path = getRelativePath(root);

flutter-idea/src/io/flutter/console/FlutterConsoleFilter.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
import com.intellij.openapi.editor.markup.TextAttributes;
1919
import com.intellij.openapi.module.Module;
2020
import com.intellij.openapi.project.Project;
21-
import com.intellij.openapi.roots.ModuleRootManager;
2221
import com.intellij.openapi.ui.Messages;
2322
import com.intellij.openapi.vfs.LocalFileSystem;
2423
import com.intellij.openapi.vfs.VirtualFile;
2524
import com.intellij.util.ui.UIUtil;
2625
import io.flutter.FlutterMessages;
2726
import io.flutter.FlutterUtils;
2827
import io.flutter.sdk.FlutterSdk;
28+
import io.flutter.utils.OpenApiUtils;
2929
import org.jetbrains.annotations.NotNull;
3030
import org.jetbrains.annotations.Nullable;
3131

@@ -91,7 +91,7 @@ public VirtualFile fileAtPath(@NotNull String pathPart) {
9191
return null;
9292
}
9393

94-
final VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots();
94+
final VirtualFile[] roots = OpenApiUtils.getContentRoots(module);
9595
for (VirtualFile root : roots) {
9696
if (!pathPart.isEmpty()) {
9797
final String baseDirPath = root.getPath();
@@ -237,7 +237,7 @@ else if (split.length == 4 && Objects.equals(split[0], "file")) {
237237
}
238238

239239
private String findRelativePath(String threeSlashFileName) {
240-
final VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots();
240+
final VirtualFile[] roots = OpenApiUtils.getContentRoots(module);
241241
for (VirtualFile root : roots) {
242242
final String path = root.getPath();
243243
int index = threeSlashFileName.indexOf(path);

flutter-idea/src/io/flutter/console/FlutterConsoles.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
import com.intellij.execution.process.ColoredProcessHandler;
99
import com.intellij.execution.ui.ConsoleViewContentType;
10-
import com.intellij.openapi.application.ApplicationManager;
1110
import com.intellij.openapi.module.Module;
1211
import com.intellij.openapi.project.Project;
1312
import com.intellij.openapi.util.Key;
1413
import com.intellij.ui.content.Content;
1514
import com.intellij.ui.content.MessageView;
15+
import io.flutter.utils.OpenApiUtils;
1616
import org.jetbrains.annotations.NotNull;
1717
import org.jetbrains.annotations.Nullable;
1818

@@ -37,7 +37,7 @@ public static void displayProcessLater(@NotNull ColoredProcessHandler process,
3737
@NotNull Runnable onReady) {
3838

3939
// Getting a MessageView has to happen on the UI thread.
40-
ApplicationManager.getApplication().invokeLater(() -> {
40+
OpenApiUtils.safeInvokeLater(() -> {
4141
final MessageView messageView = MessageView.getInstance(project);
4242
messageView.runWhenInitialized(() -> {
4343
final FlutterConsole console = findOrCreate(project, module);
@@ -54,7 +54,7 @@ public static void displayMessage(@NotNull Project project, @Nullable Module mod
5454

5555
public static void displayMessage(@NotNull Project project, @Nullable Module module, @NotNull String message, boolean clearContent) {
5656
// Getting a MessageView has to happen on the UI thread.
57-
ApplicationManager.getApplication().invokeLater(() -> {
57+
OpenApiUtils.safeInvokeLater(() -> {
5858
final MessageView messageView = MessageView.getInstance(project);
5959
messageView.runWhenInitialized(() -> {
6060
final FlutterConsole console = findOrCreate(project, module);

flutter-idea/src/io/flutter/deeplinks/DeepLinksViewFactory.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
package io.flutter.deeplinks;
77

8-
import com.intellij.openapi.application.ApplicationManager;
98
import com.intellij.openapi.project.Project;
109
import com.intellij.openapi.wm.ToolWindow;
1110
import com.intellij.openapi.wm.ToolWindowFactory;
@@ -18,6 +17,7 @@
1817
import io.flutter.sdk.FlutterSdk;
1918
import io.flutter.sdk.FlutterSdkVersion;
2019
import io.flutter.utils.AsyncUtils;
20+
import io.flutter.utils.OpenApiUtils;
2121
import kotlin.coroutines.Continuation;
2222
import org.jetbrains.annotations.NotNull;
2323

@@ -65,7 +65,7 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo
6565
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
6666
.build();
6767

68-
ApplicationManager.getApplication().invokeLater(() -> {
68+
OpenApiUtils.safeInvokeLater(() -> {
6969
Optional.ofNullable(
7070
FlutterUtils.embeddedBrowser(project))
7171
.ifPresent(embeddedBrowser -> embeddedBrowser.openPanel(toolWindow, "Deep Links", devToolsUrl, System.out::println));

flutter-idea/src/io/flutter/devtools/DevToolsExtensionsViewFactory.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
package io.flutter.devtools;
77

8-
import com.intellij.openapi.application.ApplicationManager;
98
import com.intellij.openapi.project.Project;
109
import com.intellij.openapi.wm.ToolWindow;
1110
import com.intellij.openapi.wm.ToolWindowFactory;
@@ -17,6 +16,7 @@
1716
import io.flutter.sdk.FlutterSdk;
1817
import io.flutter.sdk.FlutterSdkVersion;
1918
import io.flutter.utils.AsyncUtils;
19+
import io.flutter.utils.OpenApiUtils;
2020
import io.flutter.view.FlutterViewMessages;
2121
import kotlin.coroutines.Continuation;
2222
import org.jetbrains.annotations.NotNull;
@@ -73,7 +73,7 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo
7373
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
7474
.build();
7575

76-
ApplicationManager.getApplication().invokeLater(() -> {
76+
OpenApiUtils.safeInvokeLater(() -> {
7777
Optional.ofNullable(
7878
FlutterUtils.embeddedBrowser(project))
7979
.ifPresent(embeddedBrowser -> {

flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
package io.flutter.devtools;
77

8-
import com.intellij.openapi.application.ApplicationManager;
98
import com.intellij.openapi.project.Project;
109
import com.intellij.openapi.wm.ToolWindow;
1110
import com.intellij.openapi.wm.ToolWindowFactory;
@@ -17,6 +16,7 @@
1716
import io.flutter.sdk.FlutterSdk;
1817
import io.flutter.sdk.FlutterSdkVersion;
1918
import io.flutter.utils.AsyncUtils;
19+
import io.flutter.utils.OpenApiUtils;
2020
import io.flutter.view.FlutterViewMessages;
2121
import kotlin.coroutines.Continuation;
2222
import org.jetbrains.annotations.NotNull;
@@ -27,6 +27,7 @@
2727

2828
public class RemainingDevToolsViewFactory implements ToolWindowFactory {
2929
@NotNull private static String TOOL_WINDOW_ID = "Flutter DevTools";
30+
3031
public static void init(Project project) {
3132
project.getMessageBus().connect().subscribe(
3233
FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)event -> initView(project, event)
@@ -73,7 +74,7 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo
7374
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
7475
.build();
7576

76-
ApplicationManager.getApplication().invokeLater(() -> {
77+
OpenApiUtils.safeInvokeLater(() -> {
7778
Optional.ofNullable(
7879
FlutterUtils.embeddedBrowser(project))
7980
.ifPresent(embeddedBrowser -> {

0 commit comments

Comments
 (0)