blob: a7431680adad92726a459a50c22010c390de89a0 [file] [log] [blame]
// Copyright (c) 2016, 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:async';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/generated/parser.dart';
import 'package:front_end/file_system.dart';
import 'package:front_end/src/async_dependency_walker.dart';
import 'package:front_end/src/base/uri_resolver.dart';
import 'package:front_end/src/scanner/scanner.dart';
import 'package:package_config/packages_file.dart' as package_config;
import 'compiler_options.dart';
/// Generates a representation of the dependency graph of a program.
///
/// Given the Uri of one or more files, this function follows `import`,
/// `export`, and `part` declarations to discover a graph of all files involved
/// in the program.
Future<Graph> graphForProgram(
List<Uri> sources, CompilerOptions options) async {
Map<String, Uri> packages;
if (options.packagesFilePath == null) {
throw new UnimplementedError(); // TODO(paulberry): search for .packages
} else if (options.packagesFilePath.isEmpty) {
packages = {};
} else {
var contents = await options.fileSystem
.entityForPath(options.packagesFilePath)
.readAsBytes();
var baseLocation =
options.fileSystem.context.toUri(options.packagesFilePath);
packages = package_config.parse(contents, baseLocation);
}
var sdkLibraries = <String, Uri>{}; // TODO(paulberry): support SDK libraries
var uriResolver =
new UriResolver(packages, sdkLibraries, options.fileSystem.context);
var walker = new _Walker(options.fileSystem, uriResolver, options.compileSdk);
var startingPoint = new _StartingPoint(walker, sources);
await walker.walk(startingPoint);
return walker.graph;
}
/// A representation of the dependency graph of a program.
///
/// Not intended to be extended, implemented, or mixed in by clients.
class Graph {
/// A list of all library cycles in the program, in topologically sorted order
/// (each cycle only depends on libraries in the cycles that precede it).
final topologicallySortedCycles = <LibraryCycleNode>[];
Graph._();
}
/// A representation of a single library cycle in the dependency graph of a
/// program.
///
/// Not intended to be extended, implemented, or mixed in by clients.
class LibraryCycleNode {
/// A map of all the libraries in the cycle, keyed by the URI of their
/// defining compilation unit.
final libraries = <Uri, LibraryNode>{};
LibraryCycleNode._();
}
/// A representation of a single library in the dependency graph of a program.
///
/// Not intended to be extended, implemented, or mixed in by clients.
class LibraryNode {
/// The URI of this library's defining compilation unit.
final Uri uri;
/// A list of the URIs of all of this library's "part" files.
final parts = <Uri>[];
/// A list of all the other libraries this library directly depends on.
final dependencies = <LibraryNode>[];
LibraryNode._(this.uri);
}
class _Scanner extends Scanner {
_Scanner(String contents) : super(new CharSequenceReader(contents)) {
preserveComments = false;
}
@override
void reportError(errorCode, int offset, List<Object> arguments) {
// TODO(paulberry): report errors.
}
}
class _StartingPoint extends _WalkerNode {
final List<Uri> sources;
_StartingPoint(_Walker walker, this.sources) : super(walker, null);
@override
Future<List<_WalkerNode>> computeDependencies() async =>
sources.map(walker.nodeForUri).toList();
}
class _Walker extends AsyncDependencyWalker<_WalkerNode> {
final FileSystem fileSystem;
final UriResolver uriResolver;
final _nodesByUri = <Uri, _WalkerNode>{};
final graph = new Graph._();
final bool compileSdk;
_Walker(this.fileSystem, this.uriResolver, this.compileSdk);
@override
Future<Null> evaluate(_WalkerNode v) {
if (v is _StartingPoint) return new Future.value();
return evaluateScc([v]);
}
@override
Future<Null> evaluateScc(List<_WalkerNode> scc) {
var cycle = new LibraryCycleNode._();
for (var walkerNode in scc) {
cycle.libraries[walkerNode.uri] = walkerNode.library;
}
graph.topologicallySortedCycles.add(cycle);
return new Future.value();
}
_WalkerNode nodeForUri(Uri referencedUri) {
var dependencyNode = _nodesByUri.putIfAbsent(
referencedUri, () => new _WalkerNode(this, referencedUri));
return dependencyNode;
}
}
class _WalkerNode extends Node<_WalkerNode> {
static final dartCoreUri = Uri.parse('dart:core');
final _Walker walker;
final Uri uri;
final LibraryNode library;
_WalkerNode(this.walker, Uri uri)
: uri = uri,
library = new LibraryNode._(uri);
@override
Future<List<_WalkerNode>> computeDependencies() async {
var dependencies = <_WalkerNode>[];
// TODO(paulberry): add error recovery if the file can't be read.
var path = walker.uriResolver.resolve(uri);
if (path == null) {
// TODO(paulberry): If an error reporter was provided, report the error
// in the proper way and continue.
throw new StateError('Invalid URI: $uri');
}
var contents = await walker.fileSystem.entityForPath(path).readAsString();
var scanner = new _Scanner(contents);
var token = scanner.tokenize();
// TODO(paulberry): report errors.
var parser = new Parser(null, AnalysisErrorListener.NULL_LISTENER);
var unit = parser.parseDirectives(token);
bool coreUriFound = false;
void handleDependency(Uri referencedUri) {
_WalkerNode dependencyNode = walker.nodeForUri(referencedUri);
library.dependencies.add(dependencyNode.library);
if (referencedUri.scheme != 'dart' || walker.compileSdk) {
dependencies.add(dependencyNode);
}
if (referencedUri == dartCoreUri) {
coreUriFound = true;
}
}
for (var directive in unit.directives) {
if (directive is UriBasedDirective) {
// TODO(paulberry): when we support SDK libraries, we'll need more
// complex logic here to find SDK parts correctly.
var referencedUri = uri.resolve(directive.uri.stringValue);
if (directive is PartDirective) {
library.parts.add(referencedUri);
} else {
handleDependency(referencedUri);
}
}
}
if (!coreUriFound) {
handleDependency(dartCoreUri);
}
return dependencies;
}
}