|  | // Copyright (c) 2020, 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:collection/collection.dart' show IterableExtension; | 
|  | import 'package:pub_semver/pub_semver.dart'; | 
|  |  | 
|  | import '../exceptions.dart'; | 
|  | import '../package_name.dart'; | 
|  | import '../validator.dart'; | 
|  |  | 
|  | /// Gives an info if the version number has skipped since the last released, or | 
|  | /// if the version is not sequentially following the latest. | 
|  | /// | 
|  | /// Gives an info when publishing a new version, if the latest published | 
|  | /// version lower to this was not opted into null-safety. | 
|  | class RelativeVersionNumberingValidator extends Validator { | 
|  | static const String semverUrl = | 
|  | 'https://dart.dev/tools/pub/versioning#semantic-versions'; | 
|  |  | 
|  | static const String nullSafetyGuideUrl = | 
|  | 'https://dart.dev/null-safety/migration-guide'; | 
|  |  | 
|  | @override | 
|  | Future<void> validate() async { | 
|  | final hostedSource = cache.hosted; | 
|  | List<PackageId> existingVersions; | 
|  | try { | 
|  | existingVersions = await cache.getVersions( | 
|  | hostedSource.refFor( | 
|  | package.name, | 
|  | url: serverUrl.toString(), | 
|  | ), | 
|  | ); | 
|  | } on PackageNotFoundException { | 
|  | existingVersions = []; | 
|  | } | 
|  | existingVersions.sort((a, b) => a.version.compareTo(b.version)); | 
|  |  | 
|  | final currentVersion = package.pubspec.version; | 
|  |  | 
|  | final latestVersion = | 
|  | existingVersions.isEmpty ? null : existingVersions.last.version; | 
|  | if (latestVersion != null && latestVersion > currentVersion) { | 
|  | hints.add(''' | 
|  | The latest published version is $latestVersion. | 
|  | Your version $currentVersion is earlier than that.'''); | 
|  | } | 
|  |  | 
|  | final previousRelease = | 
|  | existingVersions.lastWhereOrNull((id) => id.version < package.version); | 
|  |  | 
|  | if (previousRelease == null) return; | 
|  |  | 
|  | final previousVersion = previousRelease.version; | 
|  | final noPrerelease = Version( | 
|  | currentVersion.major, | 
|  | currentVersion.minor, | 
|  | currentVersion.patch, | 
|  | ); | 
|  | if (noPrerelease != previousVersion.nextMajor && | 
|  | noPrerelease != previousVersion.nextMinor && | 
|  | noPrerelease != previousVersion.nextPatch && | 
|  | currentVersion.withoutBuild() != previousVersion) { | 
|  | final hint = ''' | 
|  | The previous version is $previousVersion. | 
|  |  | 
|  | It seems you are not publishing an incremental update. | 
|  |  | 
|  | Consider one of: | 
|  | '''; | 
|  | final String suggestion; | 
|  |  | 
|  | if (previousVersion.major == 0) { | 
|  | suggestion = ''' | 
|  | * ${previousVersion.nextMajor} for a first major release. | 
|  | * ${previousVersion.nextBreaking} for a breaking release. | 
|  | * ${previousVersion.nextPatch} for a minor release. | 
|  | '''; | 
|  | } else { | 
|  | suggestion = ''' | 
|  | * ${previousVersion.nextBreaking} for a breaking release. | 
|  | * ${previousVersion.nextMinor} for a minor release. | 
|  | * ${previousVersion.nextPatch} for a patch release.'''; | 
|  | } | 
|  | hints.add(hint + suggestion); | 
|  | } | 
|  |  | 
|  | final previousPubspec = await cache.describe(previousRelease); | 
|  |  | 
|  | final currentOptedIn = package.pubspec.languageVersion.supportsNullSafety; | 
|  | final previousOptedIn = previousPubspec.languageVersion.supportsNullSafety; | 
|  |  | 
|  | if (currentOptedIn && !previousOptedIn) { | 
|  | hints.add( | 
|  | 'You\'re about to publish a package that opts into null safety.\n' | 
|  | 'The previous version ($previousVersion) isn\'t opted in.\n' | 
|  | 'See $nullSafetyGuideUrl for best practices.'); | 
|  | } else if (!currentOptedIn && previousOptedIn) { | 
|  | hints.add( | 
|  | 'You\'re about to publish a package that doesn\'t opt into null safety,\n' | 
|  | 'but the previous version ($previousVersion) was opted in.\n' | 
|  | 'This change is likely to be backwards incompatible.\n' | 
|  | 'See $semverUrl for information about versioning.'); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | extension on Version { | 
|  | Version withoutBuild() => Version( | 
|  | major, | 
|  | minor, | 
|  | patch, | 
|  | pre: preRelease.isEmpty ? null : preRelease.join('.'), | 
|  | ); | 
|  | } |