| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library pub.validator.dependency; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'package:pub_semver/pub_semver.dart'; | |
| 10 | |
| 11 import '../entrypoint.dart'; | |
| 12 import '../log.dart' as log; | |
| 13 import '../package.dart'; | |
| 14 import '../validator.dart'; | |
| 15 | |
| 16 /// The range of all pub versions that don't support `^` version constraints. | |
| 17 /// | |
| 18 /// This is the actual range of pub versions, whereas [_postCaretPubVersions] is | |
| 19 /// the nicer-looking range that doesn't include a prerelease tag. | |
| 20 final _preCaretPubVersions = new VersionConstraint.parse("<1.8.0-dev.3.0"); | |
| 21 | |
| 22 /// The range of all pub versions that do support `^` version constraints. | |
| 23 /// | |
| 24 /// This is intersected with the user's SDK constraint to provide a suggested | |
| 25 /// constraint. | |
| 26 final _postCaretPubVersions = new VersionConstraint.parse("^1.8.0"); | |
| 27 | |
| 28 /// A validator that validates a package's dependencies. | |
| 29 class DependencyValidator extends Validator { | |
| 30 /// Whether the SDK constraint guarantees that `^` version constraints are | |
| 31 /// safe. | |
| 32 bool get _caretAllowed => entrypoint.root.pubspec.environment.sdkVersion | |
| 33 .intersect(_preCaretPubVersions).isEmpty; | |
| 34 | |
| 35 DependencyValidator(Entrypoint entrypoint) | |
| 36 : super(entrypoint); | |
| 37 | |
| 38 Future validate() async { | |
| 39 var caretDeps = []; | |
| 40 | |
| 41 for (var dependency in entrypoint.root.pubspec.dependencies) { | |
| 42 if (dependency.source != "hosted") { | |
| 43 await _warnAboutSource(dependency); | |
| 44 } else if (dependency.constraint.isAny) { | |
| 45 _warnAboutNoConstraint(dependency); | |
| 46 } else if (dependency.constraint is Version) { | |
| 47 _warnAboutSingleVersionConstraint(dependency); | |
| 48 } else if (dependency.constraint is VersionRange) { | |
| 49 if (dependency.constraint.min == null) { | |
| 50 _warnAboutNoConstraintLowerBound(dependency); | |
| 51 } else if (dependency.constraint.max == null) { | |
| 52 _warnAboutNoConstraintUpperBound(dependency); | |
| 53 } | |
| 54 | |
| 55 if (dependency.constraint.toString().startsWith("^")) { | |
| 56 caretDeps.add(dependency); | |
| 57 } | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 if (caretDeps.isNotEmpty && !_caretAllowed) { | |
| 62 _errorAboutCaretConstraints(caretDeps); | |
| 63 } | |
| 64 } | |
| 65 | |
| 66 /// Warn that dependencies should use the hosted source. | |
| 67 Future _warnAboutSource(PackageDep dep) async { | |
| 68 var versions; | |
| 69 try { | |
| 70 var pubspecs = await entrypoint.cache.sources['hosted'] | |
| 71 .getVersions(dep.name, dep.name); | |
| 72 versions = pubspecs.map((pubspec) => pubspec.version).toList(); | |
| 73 } catch (error) { | |
| 74 versions = []; | |
| 75 } | |
| 76 | |
| 77 var constraint; | |
| 78 var primary = Version.primary(versions); | |
| 79 if (primary != null) { | |
| 80 constraint = _constraintForVersion(primary); | |
| 81 } else { | |
| 82 constraint = dep.constraint.toString(); | |
| 83 if (!dep.constraint.isAny && dep.constraint is! Version) { | |
| 84 constraint = '"$constraint"'; | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 // Path sources are errors. Other sources are just warnings. | |
| 89 var messages = warnings; | |
| 90 if (dep.source == "path") { | |
| 91 messages = errors; | |
| 92 } | |
| 93 | |
| 94 messages.add('Don\'t depend on "${dep.name}" from the ${dep.source} ' | |
| 95 'source. Use the hosted source instead. For example:\n' | |
| 96 '\n' | |
| 97 'dependencies:\n' | |
| 98 ' ${dep.name}: $constraint\n' | |
| 99 '\n' | |
| 100 'Using the hosted source ensures that everyone can download your ' | |
| 101 'package\'s dependencies along with your package.'); | |
| 102 } | |
| 103 | |
| 104 /// Warn that dependencies should have version constraints. | |
| 105 void _warnAboutNoConstraint(PackageDep dep) { | |
| 106 var message = 'Your dependency on "${dep.name}" should have a version ' | |
| 107 'constraint.'; | |
| 108 var locked = entrypoint.lockFile.packages[dep.name]; | |
| 109 if (locked != null) { | |
| 110 message = '$message For example:\n' | |
| 111 '\n' | |
| 112 'dependencies:\n' | |
| 113 ' ${dep.name}: ${_constraintForVersion(locked.version)}\n'; | |
| 114 } | |
| 115 warnings.add("$message\n" | |
| 116 'Without a constraint, you\'re promising to support ${log.bold("all")} ' | |
| 117 'future versions of "${dep.name}".'); | |
| 118 } | |
| 119 | |
| 120 /// Warn that dependencies should allow more than a single version. | |
| 121 void _warnAboutSingleVersionConstraint(PackageDep dep) { | |
| 122 warnings.add( | |
| 123 'Your dependency on "${dep.name}" should allow more than one version. ' | |
| 124 'For example:\n' | |
| 125 '\n' | |
| 126 'dependencies:\n' | |
| 127 ' ${dep.name}: ${_constraintForVersion(dep.constraint)}\n' | |
| 128 '\n' | |
| 129 'Constraints that are too tight will make it difficult for people to ' | |
| 130 'use your package\n' | |
| 131 'along with other packages that also depend on "${dep.name}".'); | |
| 132 } | |
| 133 | |
| 134 /// Warn that dependencies should have lower bounds on their constraints. | |
| 135 void _warnAboutNoConstraintLowerBound(PackageDep dep) { | |
| 136 var message = 'Your dependency on "${dep.name}" should have a lower bound.'; | |
| 137 var locked = entrypoint.lockFile.packages[dep.name]; | |
| 138 if (locked != null) { | |
| 139 var constraint; | |
| 140 if (locked.version == (dep.constraint as VersionRange).max) { | |
| 141 constraint = _constraintForVersion(locked.version); | |
| 142 } else { | |
| 143 constraint = '">=${locked.version} ${dep.constraint}"'; | |
| 144 } | |
| 145 | |
| 146 message = '$message For example:\n' | |
| 147 '\n' | |
| 148 'dependencies:\n' | |
| 149 ' ${dep.name}: $constraint\n'; | |
| 150 } | |
| 151 warnings.add("$message\n" | |
| 152 'Without a constraint, you\'re promising to support ${log.bold("all")} ' | |
| 153 'previous versions of "${dep.name}".'); | |
| 154 } | |
| 155 | |
| 156 /// Warn that dependencies should have upper bounds on their constraints. | |
| 157 void _warnAboutNoConstraintUpperBound(PackageDep dep) { | |
| 158 var constraint; | |
| 159 if ((dep.constraint as VersionRange).includeMin) { | |
| 160 constraint = _constraintForVersion((dep.constraint as VersionRange).min); | |
| 161 } else { | |
| 162 constraint = '"${dep.constraint} ' | |
| 163 '<${(dep.constraint as VersionRange).min.nextBreaking}"'; | |
| 164 } | |
| 165 | |
| 166 warnings.add( | |
| 167 'Your dependency on "${dep.name}" should have an upper bound. For ' | |
| 168 'example:\n' | |
| 169 '\n' | |
| 170 'dependencies:\n' | |
| 171 ' ${dep.name}: $constraint\n' | |
| 172 '\n' | |
| 173 'Without an upper bound, you\'re promising to support ' | |
| 174 '${log.bold("all")} future versions of ${dep.name}.'); | |
| 175 } | |
| 176 | |
| 177 /// Emits an error for any version constraints that use `^` without an | |
| 178 /// appropriate SDK constraint. | |
| 179 void _errorAboutCaretConstraints(List<PackageDep> caretDeps) { | |
| 180 var newSdkConstraint = entrypoint.root.pubspec.environment.sdkVersion | |
| 181 .intersect(_postCaretPubVersions); | |
| 182 | |
| 183 if (newSdkConstraint.isEmpty) newSdkConstraint = _postCaretPubVersions; | |
| 184 | |
| 185 var buffer = new StringBuffer( | |
| 186 "Older versions of pub don't support ^ version constraints.\n" | |
| 187 "Make sure your SDK constraint excludes those old versions:\n" | |
| 188 "\n" | |
| 189 "environment:\n" | |
| 190 " sdk: \"$newSdkConstraint\"\n" | |
| 191 "\n"); | |
| 192 | |
| 193 if (caretDeps.length == 1) { | |
| 194 buffer.writeln("Or use a fully-expanded constraint:"); | |
| 195 } else { | |
| 196 buffer.writeln("Or use fully-expanded constraints:"); | |
| 197 } | |
| 198 | |
| 199 buffer.writeln(); | |
| 200 buffer.writeln("dependencies:"); | |
| 201 | |
| 202 caretDeps.forEach((dep) { | |
| 203 VersionRange constraint = dep.constraint; | |
| 204 buffer.writeln( | |
| 205 " ${dep.name}: \">=${constraint.min} <${constraint.max}\""); | |
| 206 }); | |
| 207 | |
| 208 errors.add(buffer.toString().trim()); | |
| 209 } | |
| 210 | |
| 211 /// Returns the suggested version constraint for a dependency that was tested | |
| 212 /// against [version]. | |
| 213 String _constraintForVersion(Version version) { | |
| 214 if (_caretAllowed) return "^$version"; | |
| 215 return '">=$version <${version.nextBreaking}"'; | |
| 216 } | |
| 217 } | |
| OLD | NEW |