blob: 28c74710fc67d513b86184fd2d2f68096f18b3af [file] [log] [blame]
// Copyright 2016 The Chromium Authors. 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/analyzer.dart' as analyzer;
import '../base/file_system.dart';
import '../dart/package_map.dart';
// List of flutter specific environment configurations.
// See https://github.com/munificent/dep-interface-libraries/blob/master/Proposal.md
// We will populate this list as required. Potentially, all of dart:* libraries
// supported by flutter would end up here.
final List<String> _configurationConstants = <String>['dart.library.io'];
String _dottedNameToString(analyzer.DottedName dottedName) {
String result = '';
for (analyzer.SimpleIdentifier identifier in dottedName.components) {
if (result.isEmpty) {
result += identifier.token.lexeme;
} else {
result += '.' + identifier.token.lexeme;
}
}
return result;
}
class DartDependencySetBuilder {
DartDependencySetBuilder(String mainScriptPath, String packagesFilePath) :
_mainScriptPath = canonicalizePath(mainScriptPath),
_mainScriptUri = fs.path.toUri(mainScriptPath),
_packagesFilePath = canonicalizePath(packagesFilePath);
final String _mainScriptPath;
final String _packagesFilePath;
final Uri _mainScriptUri;
Set<String> build() {
final List<String> dependencies = <String>[_mainScriptPath, _packagesFilePath];
final List<Uri> toProcess = <Uri>[_mainScriptUri];
final PackageMap packageMap = new PackageMap(_packagesFilePath);
while (toProcess.isNotEmpty) {
final Uri currentUri = toProcess.removeLast();
final analyzer.CompilationUnit unit = _parse(currentUri.toFilePath());
for (analyzer.Directive directive in unit.directives) {
if (!(directive is analyzer.UriBasedDirective))
continue;
String uriAsString;
if (directive is analyzer.NamespaceDirective) {
final analyzer.NamespaceDirective namespaceDirective = directive;
// If the directive is a conditional import directive, we should
// select the imported uri based on the condition.
for (analyzer.Configuration configuration in namespaceDirective.configurations) {
if (_configurationConstants.contains(_dottedNameToString(configuration.name))) {
uriAsString = configuration.uri.stringValue;
break;
}
}
}
if (uriAsString == null) {
final analyzer.UriBasedDirective uriBasedDirective = directive;
uriAsString = uriBasedDirective.uri.stringValue;
}
Uri uri;
try {
uri = Uri.parse(uriAsString);
} on FormatException {
throw new DartDependencyException('Unable to parse URI: $uriAsString');
}
Uri resolvedUri = analyzer.resolveRelativeUri(currentUri, uri);
if (resolvedUri.scheme.startsWith('dart'))
continue;
if (resolvedUri.scheme == 'package') {
final Uri newResolvedUri = packageMap.uriForPackage(resolvedUri);
if (newResolvedUri == null) {
throw new DartDependencyException(
'The following Dart file:\n'
' ${currentUri.toFilePath()}\n'
'...refers, in an import, to the following library:\n'
' $resolvedUri\n'
'That library is in a package that is not known. Maybe you forgot to '
'mention it in your pubspec.yaml file?'
);
}
resolvedUri = newResolvedUri;
}
final String path = canonicalizePath(resolvedUri.toFilePath());
if (!dependencies.contains(path)) {
if (!fs.isFileSync(path)) {
throw new DartDependencyException(
'The following Dart file:\n'
' ${currentUri.toFilePath()}\n'
'...refers, in an import, to the following library:\n'
' $path\n'
'Unfortunately, that library does not appear to exist on your file system.'
);
}
dependencies.add(path);
toProcess.add(resolvedUri);
}
}
}
return dependencies.toSet();
}
analyzer.CompilationUnit _parse(String path) {
String body;
try {
body = fs.file(path).readAsStringSync();
} on FileSystemException catch (error) {
throw new DartDependencyException(
'Could not read "$path" when determining Dart dependencies.',
error,
);
}
try {
return analyzer.parseDirectives(body, name: path);
} on analyzer.AnalyzerError catch (error) {
throw new DartDependencyException(
'When trying to parse this Dart file to find its dependencies:\n'
' $path\n'
'...the analyzer failed with the following error:\n'
' ${error.toString().trimRight()}',
error,
);
} on analyzer.AnalyzerErrorGroup catch (error) {
throw new DartDependencyException(
'When trying to parse this Dart file to find its dependencies:\n'
' $path\n'
'...the analyzer failed with the following error:\n'
' ${error.toString().trimRight()}',
error,
);
}
}
}
class DartDependencyException implements Exception {
DartDependencyException(this.message, [this.parent]);
final String message;
final Exception parent;
@override
String toString() => message;
}