blob: fd7c7e58e549fd3cad9494be3d08195b113699d9 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/package_config_json.dart';
import 'package:analyzer/src/util/uri.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:package_config/src/packages_file.dart'
as package_config_packages_file;
import 'package:pub_semver/pub_semver.dart';
/// Find [Packages] starting from the given [start] resource.
///
/// Looks for `.dart_tool/package_config.json` or `.packages` in the given
/// and parent directories.
Packages findPackagesFrom(ResourceProvider provider, Resource start) {
for (Resource? current = start; current != null; current = current.parent) {
if (current is Folder) {
try {
var jsonFile = current
.getChildAssumingFolder('.dart_tool')
.getChildAssumingFile('package_config.json');
if (jsonFile.exists) {
return parsePackageConfigJsonFile(provider, jsonFile);
}
} catch (e) {
return Packages.empty;
}
try {
var dotFile = current.getChildAssumingFile('.packages');
if (dotFile.exists) {
return parseDotPackagesFile(provider, dotFile);
}
} catch (e) {
return Packages.empty;
}
}
}
return Packages.empty;
}
/// Parse the [file] as a `.packages` file.
Packages parseDotPackagesFile(ResourceProvider provider, File file) {
var uri = file.toUri();
var content = file.readAsBytesSync();
// TODO(srawlins): Replacing these two imports with public imports currently
// requires a sweeping breaking change, from synchronous code to asynchronous,
// including in our public APIs. When synchronous public APIs become
// available, use those.
// See https://github.com/dart-lang/package_config/issues/95.
var packageConfig = package_config_packages_file.parse(
content, uri, (Object error) => throw error);
var map = <String, Package>{};
for (var package in packageConfig.packages) {
var libUri = package.packageUriRoot;
var libPath = fileUriToNormalizedPath(
provider.pathContext,
libUri,
);
var libFolder = provider.getFolder(libPath);
map[package.name] = Package(
name: package.name,
rootFolder: libFolder,
libFolder: libFolder,
languageVersion: null,
);
}
return Packages(map);
}
/// Parse the [file] as a `package_config.json` file.
Packages parsePackageConfigJsonFile(ResourceProvider provider, File file) {
var uri = file.toUri();
var content = file.readAsStringSync();
var jsonConfig = parsePackageConfigJson(uri, content);
var map = <String, Package>{};
for (var jsonPackage in jsonConfig.packages) {
var name = jsonPackage.name;
var rootPath = fileUriToNormalizedPath(
provider.pathContext,
jsonPackage.rootUri,
);
var libPath = fileUriToNormalizedPath(
provider.pathContext,
jsonPackage.packageUri,
);
Version? languageVersion;
var jsonLanguageVersion = jsonPackage.languageVersion;
if (jsonLanguageVersion != null) {
languageVersion = Version(
jsonLanguageVersion.major,
jsonLanguageVersion.minor,
0,
);
// New features were added in `2.2.2` over `2.2.0`.
// But `2.2.2` is not representable, so we special case it.
if (languageVersion.major == 2 && languageVersion.minor == 2) {
languageVersion = Version(2, 2, 2);
}
}
map[name] = Package(
name: name,
rootFolder: provider.getFolder(rootPath),
libFolder: provider.getFolder(libPath),
languageVersion: languageVersion,
);
}
return Packages(map);
}
/// Check the content of the [file], and parse it as either `.packages` file,
/// or as a `package_config.json` file, depending on its content (not its
/// location). OTOH, if the file has the `.packages` format, still look
/// for a `.dart_tool/package_config.json` relative to the specified [file].
Packages parsePackagesFile(ResourceProvider provider, File file) {
try {
var content = file.readAsStringSync();
var isJson = content.trimLeft().startsWith('{');
if (isJson) {
return parsePackageConfigJsonFile(provider, file);
} else {
var relativePackageConfigFile = file.parent!
.getChildAssumingFolder('.dart_tool')
.getChildAssumingFile('package_config.json');
if (relativePackageConfigFile.exists) {
return parsePackageConfigJsonFile(provider, relativePackageConfigFile);
}
return parseDotPackagesFile(provider, file);
}
} catch (e) {
return Packages.empty;
}
}
class Package {
final String name;
final Folder rootFolder;
final Folder libFolder;
/// The language version for this package, `null` not specified explicitly.
final Version? languageVersion;
Package({
required this.name,
required this.rootFolder,
required this.libFolder,
required this.languageVersion,
});
}
class Packages {
static final empty = Packages({});
final Map<String, Package> _map;
Packages(Map<String, Package> map) : _map = map;
Iterable<Package> get packages => _map.values;
/// Return the [Package] with the given [name], or `null`.
Package? operator [](String name) => _map[name];
/// Return the inner-most [Package] that contains the [path], `null` if none.
Package? packageForPath(String path) {
Package? result;
int resultPathLength = 1 << 20;
for (var package in packages) {
if (package.rootFolder.contains(path)) {
var packagePathLength = package.rootFolder.path.length;
if (result == null || resultPathLength < packagePathLength) {
result = package;
resultPathLength = packagePathLength;
}
}
}
return result;
}
}