Skip to content
Snippets Groups Projects
dependency.dart 6.49 KiB
Newer Older
// Copyright (c) 2012, 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:pub_semver/pub_semver.dart';

import '../entrypoint.dart';
import '../exceptions.dart';
import '../package_name.dart';
import '../source/git.dart';
import '../source/hosted.dart';
import '../source/path.dart';
import '../validator.dart';

/// The first Dart SDK version that supported caret constraints.
final _firstCaretVersion = new Version.parse("1.8.0-dev.3.0");
/// The first Dart SDK version that supported Git path dependencies.
final _firstGitPathVersion = new Version.parse("1.25.0-dev.5.0");
/// A validator that validates a package's dependencies.
class DependencyValidator extends Validator {
Jacob MacDonald's avatar
Jacob MacDonald committed
  DependencyValidator(Entrypoint entrypoint) : super(entrypoint);
  Future validate() async {
    var hasCaretDep = false;
    for (var dependency in entrypoint.root.pubspec.dependencies) {
      var constraint = dependency.constraint;
      if (dependency.name == "flutter") {
        _warnAboutFlutterSdk(dependency);
      } else if (dependency.source is! HostedSource) {
        await _warnAboutSource(dependency);

        if (dependency.source is GitSource &&
            dependency.description['path'] != '.') {
          validateSdkConstraint(_firstGitPathVersion,
              "Older versions of pub don't support Git path dependencies.");
        }
      } else if (constraint.isAny) {
        _warnAboutNoConstraint(dependency);
      } else if (constraint is Version) {
        _warnAboutSingleVersionConstraint(dependency);
      } else if (constraint is VersionRange) {
        if (constraint.min == null) {
          _warnAboutNoConstraintLowerBound(dependency);
        } else if (constraint.max == null) {
          _warnAboutNoConstraintUpperBound(dependency);
        }
        hasCaretDep = hasCaretDep || constraint.toString().startsWith("^");
    if (hasCaretDep) {
      validateSdkConstraint(_firstCaretVersion,
          "Older versions of pub don't support ^ version constraints.");
  /// Warn about improper dependencies on Flutter.
  void _warnAboutFlutterSdk(PackageRange dep) {
    if (dep.source is SdkSource) return;

    errors.add('Don\'t depend on "${dep.name}" from the ${dep.source} '
        'source. Use the SDK source instead. For example:\n'
        '\n'
        'dependencies:\n'
        '  ${dep.name}:\n'
        '    sdk: ${dep.constraint}\n'
        '\n'
        'The Flutter SDK is downloaded and managed outside of pub.');
  }

  /// Warn that dependencies should use the hosted source.
  Future _warnAboutSource(PackageRange dep) async {
    List<Version> versions;
      var ids = await entrypoint.cache.hosted
          .getVersions(entrypoint.cache.sources.hosted.refFor(dep.name));
      versions = ids.map((id) => id.version).toList();
    } on ApplicationException catch (_) {
    var constraint;
    var primary = Version.primary(versions);
    if (primary != null) {
      constraint = "^$primary";
    } else {
      constraint = dep.constraint.toString();
      if (!dep.constraint.isAny && dep.constraint is! Version) {
        constraint = '"$constraint"';
    }

    // Path sources are errors. Other sources are just warnings.
    var messages = dep.source is PathSource ? errors : warnings;
    messages.add('Don\'t depend on "${dep.name}" from the ${dep.source} '
Jacob MacDonald's avatar
Jacob MacDonald committed
        'source. Use the hosted source instead. For example:\n'
        '\n'
        'dependencies:\n'
        '  ${dep.name}: $constraint\n'
        '\n'
        'Using the hosted source ensures that everyone can download your '
Jacob MacDonald's avatar
Jacob MacDonald committed
        'package\'s dependencies along with your package.');
  }

  /// Warn that dependencies should have version constraints.
  void _warnAboutNoConstraint(PackageRange dep) {
    var message = 'Your dependency on "${dep.name}" should have a version '
    var locked = entrypoint.lockFile.packages[dep.name];
    if (locked != null) {
      message = '$message For example:\n'
Jacob MacDonald's avatar
Jacob MacDonald committed
          '\n'
          'dependencies:\n'
          '  ${dep.name}: ^${locked.version}\n';
    }
    warnings.add("$message\n"
        'Without a constraint, you\'re promising to support ${log.bold("all")} '
Jacob MacDonald's avatar
Jacob MacDonald committed
        'future versions of "${dep.name}".');
  /// Warn that dependencies should allow more than a single version.
  void _warnAboutSingleVersionConstraint(PackageRange dep) {
    warnings.add(
        'Your dependency on "${dep.name}" should allow more than one version. '
Jacob MacDonald's avatar
Jacob MacDonald committed
        'For example:\n'
        '  ${dep.name}: ^${dep.constraint}\n'
        '\n'
        'Constraints that are too tight will make it difficult for people to '
Jacob MacDonald's avatar
Jacob MacDonald committed
        'use your package\n'
        'along with other packages that also depend on "${dep.name}".');
  }

  /// Warn that dependencies should have lower bounds on their constraints.
  void _warnAboutNoConstraintLowerBound(PackageRange dep) {
    var message = 'Your dependency on "${dep.name}" should have a lower bound.';
    var locked = entrypoint.lockFile.packages[dep.name];
    if (locked != null) {
      var constraint;
      if (locked.version == (dep.constraint as VersionRange).max) {
        constraint = "^${locked.version}";
      } else {
        constraint = '">=${locked.version} ${dep.constraint}"';
      }

      message = '$message For example:\n'
Jacob MacDonald's avatar
Jacob MacDonald committed
          '\n'
          'dependencies:\n'
          '  ${dep.name}: $constraint\n';
    }
    warnings.add("$message\n"
        'Without a constraint, you\'re promising to support ${log.bold("all")} '
Jacob MacDonald's avatar
Jacob MacDonald committed
        'previous versions of "${dep.name}".');
  /// Warn that dependencies should have upper bounds on their constraints.
  void _warnAboutNoConstraintUpperBound(PackageRange dep) {
    var constraint;
    if ((dep.constraint as VersionRange).includeMin) {
      constraint = "^${(dep.constraint as VersionRange).min}";
    } else {
      constraint = '"${dep.constraint} '
          '<${(dep.constraint as VersionRange).min.nextBreaking}"';
    }

Jacob MacDonald's avatar
Jacob MacDonald committed
    warnings
        .add('Your dependency on "${dep.name}" should have an upper bound. For '
Jacob MacDonald's avatar
Jacob MacDonald committed
            '\n'
            'dependencies:\n'
            '  ${dep.name}: $constraint\n'
            '\n'
            'Without an upper bound, you\'re promising to support '
            '${log.bold("all")} future versions of ${dep.name}.');