blob: e5059d7cc577098bd5a2f8bca66639aaa55bb1bb [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/packages.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/lint/pub.dart';
import 'package:analyzer/src/summary/api_signature.dart';
import 'package:analyzer/src/workspace/simple.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:meta/meta.dart';
/// Information about a Pub workspace.
class PubWorkspace extends SimpleWorkspace {
/// The name of the file that identifies the root of the workspace.
static const String _pubspecName = 'pubspec.yaml';
/// The singular package in this workspace.
///
/// Each Pub workspace is itself one package.
late final PubWorkspacePackage _theOnlyPackage;
/// The associated pubspec file.
final File _pubspecFile;
/// The content of the `pubspec.yaml` file.
/// We read it once, so that all usages return consistent results.
final String? _pubspecContent;
PubWorkspace._(
ResourceProvider provider,
Map<String, List<Folder>> packageMap,
String root,
File pubspecFile,
) : _pubspecFile = pubspecFile,
_pubspecContent = _fileContentOrNull(pubspecFile),
super(provider, packageMap, root) {
_theOnlyPackage = PubWorkspacePackage(root, this);
}
@override
bool get isConsistentWithFileSystem {
return _fileContentOrNull(_pubspecFile) == _pubspecContent;
}
@internal
@override
void contributeToResolutionSalt(ApiSignature buffer) {
buffer.addString(_pubspecContent ?? '');
}
@override
WorkspacePackage? findPackageFor(String filePath) {
final Folder folder = provider.getFolder(filePath);
if (provider.pathContext.isWithin(root, folder.path)) {
return _theOnlyPackage;
} else {
return null;
}
}
/// Find the pub workspace that contains the given [filePath].
static PubWorkspace? find(
ResourceProvider provider,
Map<String, List<Folder>> packageMap,
String filePath,
) {
var start = provider.getFolder(filePath);
for (var current in start.withAncestors) {
var pubspec = current.getChildAssumingFile(_pubspecName);
if (pubspec.exists) {
var root = current.path;
return PubWorkspace._(provider, packageMap, root, pubspec);
}
}
}
/// Return the content of the [file], `null` if cannot be read.
static String? _fileContentOrNull(File file) {
try {
return file.readAsStringSync();
} catch (_) {}
}
}
/// Information about a package defined in a [PubWorkspace].
///
/// Separate from [Packages] or package maps, this class is designed to simply
/// understand whether arbitrary file paths represent libraries declared within
/// a given package in a [PubWorkspace].
class PubWorkspacePackage extends WorkspacePackage {
@override
final String root;
Pubspec? _pubspec;
/// A flag to indicate if we've tried to parse the pubspec.
bool _parsedPubspec = false;
@override
final PubWorkspace workspace;
PubWorkspacePackage(this.root, this.workspace);
/// Get the associated parsed [Pubspec], or `null` if there was an error in
/// reading or parsing.
Pubspec? get pubspec {
if (!_parsedPubspec) {
_parsedPubspec = true;
final content = workspace._pubspecContent;
if (content != null) {
_pubspec = Pubspec.parse(content);
}
}
return _pubspec;
}
@override
bool contains(Source source) {
var filePath = filePathFromSource(source);
if (filePath == null) return false;
// There is a 1-1 relationship between [PubWorkspace]s and
// [PubWorkspacePackage]s. If a file is in a package's workspace, then it
// is in the package as well.
return workspace.provider.pathContext.isWithin(root, filePath);
}
@override
Map<String, List<Folder>> packagesAvailableTo(String libraryPath) {
// TODO(brianwilkerson) Consider differentiating based on whether the
// [libraryPath] is inside the `lib` directory.
return workspace.packageMap;
}
@override
/// A Pub package's public API consists of libraries found in the top-level
/// "lib" directory, and any subdirectories, excluding the "src" directory
/// just inside the top-level "lib" directory.
bool sourceIsInPublicApi(Source source) {
var filePath = filePathFromSource(source);
if (filePath == null) return false;
var libFolder = workspace.provider.pathContext.join(root, 'lib');
if (!workspace.provider.pathContext.isWithin(libFolder, filePath)) {
return false;
}
var libSrcFolder = workspace.provider.pathContext.join(root, 'lib', 'src');
return !workspace.provider.pathContext.isWithin(libSrcFolder, filePath);
}
}