blob: 110bc6353081937e03bdb829c620e57fc76feeeb [file] [log] [blame]
// Copyright (c) 2015, 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 'dart:io';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/lint/io.dart';
import 'package:analyzer/src/lint/pub.dart';
import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;
Pubspec? _findAndParsePubspec(Directory root) {
if (root.existsSync()) {
var pubspec = root
.listSync(followLinks: false)
.whereType<File>()
.firstWhereOrNull((f) => isPubspecFile(f));
if (pubspec != null) {
return Pubspec.parse(pubspec.readAsStringSync(),
sourceUrl: p.toUri(pubspec.path));
}
}
return null;
}
/// A semantic representation of a Dart project.
///
/// Projects provide a semantic model of a Dart project based on the
/// [pub package layout conventions](https://dart.dev/tools/pub/package-layout).
/// This model allows clients to traverse project contents in a convenient and
/// standardized way, access global information (such as whether elements are
/// in the "public API") and resources that have special meanings in the
/// context of pub package layout conventions.
class DartProject {
late final _ApiModel _apiModel;
String? _name;
Pubspec? _pubspec;
/// Project root.
final Directory root;
/// Create a Dart project for the corresponding [driver] and [sources].
/// If a [dir] is unspecified the current working directory will be
/// used.
///
/// Note: clients should call [create] which performs API model initialization.
DartProject._(AnalysisDriver driver, List<Source> sources, {Directory? dir})
: root = dir ?? Directory.current {
_pubspec = _findAndParsePubspec(root);
_apiModel = _ApiModel(driver, sources, root);
}
/// The project's name.
///
/// Project names correspond to the package name as specified in the project's
/// [pubspec]. The pubspec is found relative to the project [root]. If no
/// pubspec can be found, the name defaults to the project root basename.
String get name => _name ??= _calculateName();
/// The project's pubspec.
Pubspec? get pubspec => _pubspec;
/// Returns `true` if the given element is part of this project's public API.
///
/// Public API elements are defined as all elements that are in the packages's
/// `lib` directory, *less* those in `lib/src` (which are treated as private
/// *implementation files*), plus elements having been explicitly exported
/// via an `export` directive.
bool isApi(Element element) => _apiModel.contains(element);
String _calculateName() {
if (pubspec != null) {
var nameEntry = pubspec!.name;
if (nameEntry != null) {
return nameEntry.value.text!;
}
}
return p.basename(root.path);
}
/// Create an initialized Dart project for the corresponding [driver] and
/// [sources].
/// If a [dir] is unspecified the current working directory will be
/// used.
static Future<DartProject> create(AnalysisDriver driver, List<Source> sources,
{Directory? dir}) async {
DartProject project = DartProject._(driver, sources, dir: dir);
await project._apiModel._calculate();
return project;
}
}
/// An object that can be used to visit Dart project structure.
abstract class ProjectVisitor<T> {
T? visit(DartProject project) => null;
}
/// Captures the project's API as defined by pub package layout standards.
class _ApiModel {
final AnalysisDriver driver;
final List<Source>? sources;
final Directory root;
final Set<Element> elements = {};
_ApiModel(this.driver, this.sources, this.root) {
_calculate();
}
/// Return `true` if this element is part of the public API for this package.
bool contains(Element? element) {
while (element != null) {
if (!element.isPrivate && elements.contains(element)) {
return true;
}
element = element.enclosingElement;
}
return false;
}
Future<void> _calculate() async {
if (sources == null || sources!.isEmpty) {
return;
}
String libDir = root.path + '/lib';
String libSrcDir = libDir + '/src';
for (Source source in sources!) {
String path = source.uri.path;
if (path.startsWith(libDir) && !path.startsWith(libSrcDir)) {
ResolvedUnitResult result = (await driver.getResult(source.fullName))!;
LibraryElement library = result.libraryElement;
NamespaceBuilder namespaceBuilder = NamespaceBuilder();
Namespace exports =
namespaceBuilder.createExportNamespaceForLibrary(library);
Namespace public =
namespaceBuilder.createPublicNamespaceForLibrary(library);
elements.addAll(exports.definedNames.values);
elements.addAll(public.definedNames.values);
}
}
}
}