dependencies.dart 5.32 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:analyzer/analyzer.dart' as analyzer;
6

7
import '../base/file_system.dart';
8
import '../dart/package_map.dart';
9

10 11 12 13 14 15 16 17
// 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 = '';
18
  for (analyzer.SimpleIdentifier identifier in dottedName.components) {
19 20 21 22 23 24 25 26 27
    if (result.isEmpty) {
      result += identifier.token.lexeme;
    } else {
      result += '.' + identifier.token.lexeme;
    }
  }
  return result;
}

28
class DartDependencySetBuilder {
29
  DartDependencySetBuilder(String mainScriptPath, String packagesFilePath) :
30
    _mainScriptPath = canonicalizePath(mainScriptPath),
31
    _mainScriptUri = fs.path.toUri(mainScriptPath),
32
    _packagesFilePath = canonicalizePath(packagesFilePath);
33

34 35
  final String _mainScriptPath;
  final String _packagesFilePath;
36

37
  final Uri _mainScriptUri;
38 39

  Set<String> build() {
40 41 42 43 44 45
    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();
46 47
      final analyzer.CompilationUnit unit = _parse(currentUri.toFilePath());
      for (analyzer.Directive directive in unit.directives) {
48
        if (!(directive is analyzer.UriBasedDirective))
49
          continue;
50 51 52

        String uriAsString;
        if (directive is analyzer.NamespaceDirective) {
53
          final analyzer.NamespaceDirective namespaceDirective = directive;
54 55 56 57 58 59 60 61 62 63 64 65 66 67
          // 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;
        }

68 69 70 71 72 73 74
        Uri uri;
        try {
          uri = Uri.parse(uriAsString);
        } on FormatException {
          throw new DartDependencyException('Unable to parse URI: $uriAsString');
        }
        Uri resolvedUri = analyzer.resolveRelativeUri(currentUri, uri);
75 76
        if (resolvedUri.scheme.startsWith('dart'))
          continue;
77 78 79 80 81 82 83 84 85 86 87 88 89 90
        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;
        }
91
        final String path = canonicalizePath(resolvedUri.toFilePath());
92
        if (!dependencies.contains(path)) {
93 94 95 96 97 98 99 100 101
          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.'
            );
          }
102 103 104 105 106 107
          dependencies.add(path);
          toProcess.add(resolvedUri);
        }
      }
    }
    return dependencies.toSet();
108
  }
109

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
  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,
      );
    }
  }
140
}
141 142 143 144 145 146 147

class DartDependencyException implements Exception {
  DartDependencyException(this.message, [this.parent]);
  final String message;
  final Exception parent;
  @override
  String toString() => message;
148
}