| // 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 = 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 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 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 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 DartDependencyException( |
| 'Could not read "$path" when determining Dart dependencies.', |
| error, |
| ); |
| } |
| try { |
| return analyzer.parseDirectives(body, name: path); |
| } on analyzer.AnalyzerError catch (error) { |
| throw 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 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; |
| } |