Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 27 additions & 18 deletions Dart/src/com/jetbrains/lang/dart/DartFileListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@ public ChangeApplier prepareChange(@NotNull List<? extends @NotNull VFileEvent>

if (event instanceof VFilePropertyChangeEvent) {
if (((VFilePropertyChangeEvent)event).isRename()) {
if (DotPackagesFileUtil.DOT_PACKAGES.equals(((VFilePropertyChangeEvent)event).getOldValue()) ||
DotPackagesFileUtil.DOT_PACKAGES.equals(((VFilePropertyChangeEvent)event).getNewValue())) {
if (
DotPackagesFileUtil.PACKAGE_CONFIG_JSON.equals(((VFilePropertyChangeEvent)event).getOldValue()) ||
DotPackagesFileUtil.PACKAGE_CONFIG_JSON.equals(((VFilePropertyChangeEvent)event).getNewValue()) ||
DotPackagesFileUtil.DOT_PACKAGES.equals(((VFilePropertyChangeEvent)event).getOldValue()) ||
DotPackagesFileUtil.DOT_PACKAGES.equals(((VFilePropertyChangeEvent)event).getNewValue())
) {
dotPackageEvents.add(event);
}

Expand All @@ -80,7 +84,10 @@ public ChangeApplier prepareChange(@NotNull List<? extends @NotNull VFileEvent>
}
}
else {
if (DotPackagesFileUtil.DOT_PACKAGES.equals(PathUtil.getFileName(event.getPath()))) {
if (
DotPackagesFileUtil.PACKAGE_CONFIG_JSON.equals(PathUtil.getFileName(event.getPath())) ||
DotPackagesFileUtil.DOT_PACKAGES.equals(PathUtil.getFileName(event.getPath()))
) {
dotPackageEvents.add(event);
}

Expand Down Expand Up @@ -155,9 +162,9 @@ private static DartLibInfo collectPackagesLibraryRoots(@NotNull final Project pr
final Module module = fileIndex.getModuleForFile(pubspecFile);
if (module == null || !DartSdkLibUtil.isDartSdkEnabled(module)) continue;

final VirtualFile dotPackagesFile = findDotPackagesFile(pubspecFile.getParent());
if (dotPackagesFile != null) {
final Map<String, String> packagesMap = DotPackagesFileUtil.getPackagesMap(dotPackagesFile);
final VirtualFile packageConfigJsonFile = DotPackagesFileUtil.findPackageConfigJsonFile(pubspecFile.getParent());
if (packageConfigJsonFile != null) {
final Map<String, String> packagesMap = DotPackagesFileUtil.getPackagesMapFromPackageConfigJsonFile(packageConfigJsonFile);
if (packagesMap != null) {
for (Map.Entry<String, String> entry : packagesMap.entrySet()) {
final String packageName = entry.getKey();
Expand All @@ -167,24 +174,26 @@ private static DartLibInfo collectPackagesLibraryRoots(@NotNull final Project pr
}
}
}
} else {
final VirtualFile dotPackagesFile = DotPackagesFileUtil.findDotPackagesFile(pubspecFile.getParent());
if (dotPackagesFile != null) {
final Map<String, String> packagesMap = DotPackagesFileUtil.getPackagesMap(dotPackagesFile);
if (packagesMap != null) {
for (Map.Entry<String, String> entry : packagesMap.entrySet()) {
final String packageName = entry.getKey();
final String packagePath = entry.getValue();
if (isPathOutsideProjectContent(fileIndex, packagePath)) {
libInfo.addPackage(packageName, packagePath);
}
}
}
}
}
}

return libInfo;
}

@Nullable
private static VirtualFile findDotPackagesFile(@Nullable VirtualFile dir) {
while (dir != null) {
final VirtualFile file = dir.findChild(DotPackagesFileUtil.DOT_PACKAGES);
if (file != null && !file.isDirectory()) {
return file;
}
dir = dir.getParent();
}
return null;
}

@NotNull
public static Library updatePackagesLibraryRoots(@NotNull final Project project, @NotNull final DartLibInfo libInfo) {
final LibraryTable projectLibraryTable = LibraryTablesRegistrar.getInstance().getLibraryTable(project);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ public final class DartAnalysisServerService implements Disposable {
// https://github.com/dart-lang/webdev/blob/master/webdev/pubspec.yaml#L11
public static final String MIN_WEBDEV_SDK_VERSION = "2.6.0";

// As of the Dart SDK version 2.8.0, the file .dart_tool/package_config.json is preferred over the .packages file.
// https://github.com/dart-lang/sdk/issues/48272
public static final String MIN_PACKAGE_CONFIG_JSON_SDK_VERSION = "2.8.0";

// The dart cli command provides a language server command, `dart language-server`, which
// should be used going forward instead of `dart .../analysis_server.dart.snapshot`.
public static final String MIN_DART_LANG_SERVER_SDK_VERSION = "2.16.0";
Expand Down Expand Up @@ -459,6 +463,10 @@ public static boolean isDartSdkVersionSufficientForWebdev(@NotNull final DartSdk
return StringUtil.compareVersionNumbers(sdk.getVersion(), MIN_WEBDEV_SDK_VERSION) >= 0;
}

public static boolean isDartSdkVersionSufficientForPackageConfigJson(@NotNull final DartSdk sdk) {
return StringUtil.compareVersionNumbers(sdk.getVersion(), MIN_PACKAGE_CONFIG_JSON_SDK_VERSION) >= 0;
}

public static boolean isDartSdkVersionSufficientForDartLangServer(@NotNull final DartSdk sdk) {
return StringUtil.compareVersionNumbers(sdk.getVersion(), MIN_DART_LANG_SERVER_SDK_VERSION) >= 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.jetbrains.lang.dart.DartBundle;
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
import com.jetbrains.lang.dart.flutter.FlutterUtil;
import com.jetbrains.lang.dart.ide.actions.DartPubActionBase;
import com.jetbrains.lang.dart.psi.DartFile;
Expand Down Expand Up @@ -67,14 +68,28 @@ public final class DartOutdatedDependenciesInspection extends LocalInspectionToo

if (FlutterUtil.isPubspecDeclaringFlutter(pubspecFile)) return null; // 'pub get' will fail anyway

final VirtualFile dotPackagesFile = pubspecFile.getParent().findChild(DotPackagesFileUtil.DOT_PACKAGES);
if (DartAnalysisServerService.isDartSdkVersionSufficientForPackageConfigJson(sdk)) {
final VirtualFile packageConfigJsonFile = DotPackagesFileUtil.getPackageConfigJsonFile(pubspecFile);

if (dotPackagesFile == null) {
return createProblemDescriptors(manager, psiFile, pubspecFile, DartBundle.message("pub.get.never.done"));
if (packageConfigJsonFile == null) {
return createProblemDescriptors(manager, psiFile, pubspecFile, DartBundle.message("pub.get.never.done"));
}

if (FileDocumentManager.getInstance().isFileModified(pubspecFile) ||
pubspecFile.getTimeStamp() > packageConfigJsonFile.getTimeStamp()) {
return createProblemDescriptors(manager, psiFile, pubspecFile, DartBundle.message("pubspec.edited"));
}
}
else {
final VirtualFile dotPackagesFile = pubspecFile.getParent().findChild(DotPackagesFileUtil.DOT_PACKAGES);

if (FileDocumentManager.getInstance().isFileModified(pubspecFile) || pubspecFile.getTimeStamp() > dotPackagesFile.getTimeStamp()) {
return createProblemDescriptors(manager, psiFile, pubspecFile, DartBundle.message("pubspec.edited"));
if (dotPackagesFile == null) {
return createProblemDescriptors(manager, psiFile, pubspecFile, DartBundle.message("pub.get.never.done"));
}

if (FileDocumentManager.getInstance().isFileModified(pubspecFile) || pubspecFile.getTimeStamp() > dotPackagesFile.getTimeStamp()) {
return createProblemDescriptors(manager, psiFile, pubspecFile, DartBundle.message("pubspec.edited"));
}
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ class DartPackageAwareFileReference extends FileReference {
@NotNull private final DartUrlResolver myDartResolver;

DartPackageAwareFileReference(@NotNull final FileReferenceSet fileReferenceSet,
final TextRange range,
final int index,
final String text,
@NotNull final DartUrlResolver dartResolver) {
final TextRange range,
final int index,
final String text,
@NotNull final DartUrlResolver dartResolver) {
super(fileReferenceSet, range, index, text);
myDartResolver = dartResolver;
}
Expand All @@ -41,10 +41,23 @@ class DartPackageAwareFileReference extends FileReference {
if (psiDirectory != null) {
return new ResolveResult[]{new PsiElementResolveResult(psiDirectory)};
}
VirtualFile dotPackages = pubspecYamlFile == null ? null : pubspecYamlFile.getParent().findChild(DotPackagesFileUtil.DOT_PACKAGES);
final PsiFile psiFile = dotPackages == null ? null : containingFile.getManager().findFile(dotPackages);
if (psiFile != null) {
return new ResolveResult[]{new PsiElementResolveResult(psiFile)};

// Starting in Dart 2.8.0, the .packages file is deprecated in favor of .dart_tool/package_config.json, this code preserves the
// older .packages functionality for older projects, but now looks for the .dart_tool/package_config.json file first.
VirtualFile packagesConfigJsonFile = pubspecYamlFile == null ? null : DotPackagesFileUtil.getPackageConfigJsonFile(pubspecYamlFile);
if (packagesConfigJsonFile != null) {
final PsiFile psiFile = containingFile.getManager().findFile(packagesConfigJsonFile);
if (psiFile != null) {
return new ResolveResult[]{new PsiElementResolveResult(psiFile)};
}
}
else {
// Pre-.packages deprecation fallback for older projects and SDK usages
VirtualFile dotPackages = pubspecYamlFile == null ? null : pubspecYamlFile.getParent().findChild(DotPackagesFileUtil.DOT_PACKAGES);
final PsiFile psiFile = dotPackages == null ? null : containingFile.getManager().findFile(dotPackages);
if (psiFile != null) {
return new ResolveResult[]{new PsiElementResolveResult(psiFile)};
}
}
}

Expand Down
24 changes: 21 additions & 3 deletions Dart/src/com/jetbrains/lang/dart/util/DartUrlResolverImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,28 @@ private static String getUrlIfFileFromDartPackagesLib(final @NotNull VirtualFile

private void initLivePackageNameToDirMap() {
final VirtualFile baseDir = myPubspecYamlFile == null ? null : myPubspecYamlFile.getParent();
if (myPubspecYamlFile == null || baseDir == null) return;
final VirtualFile dotPackagesFile = baseDir.findChild(DotPackagesFileUtil.DOT_PACKAGES);
if (myPubspecYamlFile == null || baseDir == null) {
return;
}

if (dotPackagesFile != null && !dotPackagesFile.isDirectory()) {
final VirtualFile packageConfigJsonFile = DotPackagesFileUtil.getPackageConfigJsonFile(baseDir);
final VirtualFile dotPackagesFile = baseDir.findChild(DotPackagesFileUtil.DOT_PACKAGES);
if (packageConfigJsonFile != null && !packageConfigJsonFile.isDirectory()) {
// Use the generated .dart_tool/package_config.json if it exists.
final Map<String, String> packagesMap = DotPackagesFileUtil.getPackagesMapFromPackageConfigJsonFile(packageConfigJsonFile);
if (packagesMap != null) {
for (Map.Entry<String, String> entry : packagesMap.entrySet()) {
final String packageName = entry.getKey();
final String packagePath = entry.getValue();
final VirtualFile packageDir = myPubspecYamlFile.getFileSystem().findFileByPath(packagePath);
if (packageDir != null) {
myLivePackageNameToDirMap.put(packageName, packageDir);
}
}
}
}
else if (dotPackagesFile != null && !dotPackagesFile.isDirectory()) {
// Otherwise, fall back to the .packages file.
final Map<String, String> packagesMap = DotPackagesFileUtil.getPackagesMap(dotPackagesFile);
if (packagesMap != null) {
for (Map.Entry<String, String> entry : packagesMap.entrySet()) {
Expand Down
138 changes: 138 additions & 0 deletions Dart/src/com/jetbrains/lang/dart/util/DotPackagesFileUtil.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.lang.dart.util;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
Expand All @@ -15,15 +19,149 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public final class DotPackagesFileUtil {

public static final String DOT_PACKAGES = ".packages";

public static final String DART_TOOL_DIR = ".dart_tool";
public static final String PACKAGE_CONFIG_JSON = "package_config.json";

private static final Key<Pair<Long, Map<String, String>>> MOD_STAMP_TO_PACKAGES_MAP = Key.create("MOD_STAMP_TO_PACKAGES_MAP");

/**
* Starting with some {@link VirtualFile}, search the parent directories for some instance of .dart_tool/package_config.json.
*/
@Nullable
public static VirtualFile findPackageConfigJsonFile(@Nullable final VirtualFile vFile) {
if (vFile == null) {
return null;
}
VirtualFile dir = vFile.isDirectory() ? vFile : vFile.getParent();
while (dir != null) {
VirtualFile dartToolDir = dir.findChild(DART_TOOL_DIR);
if (dartToolDir != null && dartToolDir.isDirectory()) {
return dartToolDir.findChild(PACKAGE_CONFIG_JSON);
}
dir = dir.getParent();
}
return null;
}

/**
* Given a file Dart pub root (either the yaml pubspec {@link VirtualFile} or the parent of a yaml pubspec), return the
* .dart_tool/package_config.json {@link VirtualFile}, if it exists.
*/
@Nullable
public static VirtualFile getPackageConfigJsonFile(@Nullable final VirtualFile vFile) {
if (vFile == null) {
return null;
}
VirtualFile dir = vFile.isDirectory() ? vFile : vFile.getParent();
VirtualFile dartToolDir = dir.findChild(DART_TOOL_DIR);
if (dartToolDir != null && dartToolDir.isDirectory()) {
return dartToolDir.findChild(PACKAGE_CONFIG_JSON);
}
return null;
}

@Nullable
public static VirtualFile findDotPackagesFile(@Nullable VirtualFile dir) {
while (dir != null) {
final VirtualFile file = dir.findChild(DOT_PACKAGES);
if (file != null && !file.isDirectory()) {
return file;
}
dir = dir.getParent();
}
return null;
}

@Nullable
public static Map<String, String> getPackagesMapFromPackageConfigJsonFile(@NotNull final VirtualFile packageConfigJsonFile) {
Pair<Long, Map<String, String>> data = packageConfigJsonFile.getUserData(MOD_STAMP_TO_PACKAGES_MAP);

final Long currentTimestamp = packageConfigJsonFile.getModificationCount();
final Long cachedTimestamp = Pair.getFirst(data);

if (cachedTimestamp == null || !cachedTimestamp.equals(currentTimestamp)) {
data = null;
packageConfigJsonFile.putUserData(MOD_STAMP_TO_PACKAGES_MAP, null);
final Map<String, String> packagesMap = packagesMapFromJson(packageConfigJsonFile);

if (packagesMap != null) {
data = Pair.create(currentTimestamp, packagesMap);
packageConfigJsonFile.putUserData(MOD_STAMP_TO_PACKAGES_MAP, data);
}
}

return Pair.getSecond(data);
}

/**
* Example JSON parsed:
* ```
* {
* "configVersion": 2,
* "packages": [
* {
* "name": "pedantic",
* "rootUri": "file:///Users/jwren/.pub-cache/hosted/pub.dartlang.org/pedantic-1.11.0",
* "packageUri": "lib/",
* "languageVersion": "2.12"
* },
* {
* "name": "console_dart",
* "rootUri": "../",
* "packageUri": "lib/",
* "languageVersion": "2.12"
* }
* ],
* "generated": "2022-03-04T22:10:17.132325Z",
* "generator": "pub",
* "generatorVersion": "2.17.0-edge.f9147d933ef019c7e304c19ac039f57226ce1e37"
* }
* ```
*/
@Nullable
private static Map<String, String> packagesMapFromJson(@NotNull final VirtualFile packageConfigJsonFile) {
String fileContentsStr = FileUtil.loadFileOrNull(packageConfigJsonFile.getPath());
if (fileContentsStr == null) {
return null;
}

final JsonElement jsonElement = JsonParser.parseString(fileContentsStr);
if (jsonElement instanceof JsonObject &&
((JsonObject)jsonElement).get("packages") != null &&
((JsonObject)jsonElement).get("packages").isJsonArray()) {
final Map<String, String> result = new HashMap<>();
final JsonArray jsonArray = ((JsonObject)jsonElement).get("packages").getAsJsonArray();
for (JsonElement element : jsonArray) {
JsonObject jsonObjectPackage = element.getAsJsonObject();
if (jsonObjectPackage.get("name") != null &&
jsonObjectPackage.get("rootUri") != null &&
jsonObjectPackage.get("packageUri") != null
) {
final String packageName = jsonObjectPackage.get("name").getAsString();
final String rootUriValue = jsonObjectPackage.get("rootUri").getAsString();
final String packageUriValue = jsonObjectPackage.get("packageUri").getAsString();
// need to protect '+' chars because URLDecoder.decode replaces '+' with space
final String encodedUriWithoutPluses = StringUtil.replace(rootUriValue + "/" + packageUriValue, "+", "%2B");
final String uri = URLUtil.decode(encodedUriWithoutPluses);
final String packageUri = getAbsolutePackageRootPath(packageConfigJsonFile.getParent().getParent(), uri);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwren Not sure packageConfigJsonFile.getParent().getParent() is correct here. See https://youtrack.jetbrains.com/issue/WEB-55363. I guess it should be packageConfigJsonFile.getParent().

if (!packageName.isEmpty() && packageUri != null) {
result.put(packageName, packageUri);
}
}
}
return result;
}
return null;
}

@Nullable
public static Map<String, String> getPackagesMap(@NotNull final VirtualFile dotPackagesFile) {
Pair<Long, Map<String, String>> data = dotPackagesFile.getUserData(MOD_STAMP_TO_PACKAGES_MAP);
Expand Down