// 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; }