Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions script/tool/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
the new `native-test` command.
- Commands that print a run summary at the end now track and log exclusions
similarly to skips for easier auditing.
- `version-check` now validates that `NEXT` is not present when changing
the version.

## 0.4.1

Expand Down
84 changes: 63 additions & 21 deletions script/tool/lib/src/version_check_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ enum NextVersionType {
RELEASE,
}

/// The state of a package's version relative to the comparison base.
enum _CurrentVersionState {
/// The version is unchanged.
unchanged,

/// The version has changed, and the transition is valid.
validChange,

/// The version has changed, and the transition is invalid.
invalidChange,

/// There was an error determining the version state.
unknown,
}

/// Returns the set of allowed next versions, with their change type, for
/// [version].
///
Expand Down Expand Up @@ -140,11 +155,28 @@ class VersionCheckCommand extends PackageLoopingCommand {

final List<String> errors = <String>[];

if (!await _hasValidVersionChange(package, pubspec: pubspec)) {
errors.add('Disallowed version change.');
bool versionChanged;
final _CurrentVersionState versionState =
await _getVersionState(package, pubspec: pubspec);
switch (versionState) {
case _CurrentVersionState.unchanged:
versionChanged = false;
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd put versionChanged = false here since setting it above isn't clearly associated with the unchanged branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to no initialization of the variable so that the compiler forces setting it in each branch, for better clarity/future-proofing.

case _CurrentVersionState.validChange:
versionChanged = true;
break;
case _CurrentVersionState.invalidChange:
versionChanged = true;
errors.add('Disallowed version change.');
break;
case _CurrentVersionState.unknown:
versionChanged = false;
errors.add('Unable to determine previous version.');
break;
}

if (!(await _hasConsistentVersion(package, pubspec: pubspec))) {
if (!(await _validateChangelogVersion(package,
pubspec: pubspec, pubspecVersionChanged: versionChanged))) {
errors.add('pubspec.yaml and CHANGELOG.md have different versions');
}

Expand Down Expand Up @@ -195,10 +227,9 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
return await gitVersionFinder.getPackageVersion(gitPath);
}

/// Returns true if the version of [package] is either unchanged relative to
/// the comparison base (git or pub, depending on flags), or is a valid
/// version transition.
Future<bool> _hasValidVersionChange(
/// Returns the state of the verison of [package] relative to the comparison
/// base (git or pub, depending on flags).
Future<_CurrentVersionState> _getVersionState(
Directory package, {
required Pubspec pubspec,
}) async {
Expand All @@ -208,7 +239,7 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
if (getBoolArg(_againstPubFlag)) {
previousVersion = await _fetchPreviousVersionFromPub(pubspec.name);
if (previousVersion == null) {
return false;
return _CurrentVersionState.unknown;
}
if (previousVersion != Version.none) {
print(
Expand All @@ -225,12 +256,12 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
'${getBoolArg(_againstPubFlag) ? 'on pub server' : 'at git base'}.');
logWarning(
'${indentation}If this plugin is not new, something has gone wrong.');
return true;
return _CurrentVersionState.validChange; // Assume new, thus valid.
}

if (previousVersion == currentVersion) {
print('${indentation}No version change.');
return true;
return _CurrentVersionState.unchanged;
}

// Check for reverts when doing local validation.
Expand All @@ -241,9 +272,9 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
// to be a revert rather than a typo by checking that the transition
// from the lower version to the new version would have been valid.
if (possibleVersionsFromNewVersion.containsKey(previousVersion)) {
print('${indentation}New version is lower than previous version. '
logWarning('${indentation}New version is lower than previous version. '
'This is assumed to be a revert.');
return true;
return _CurrentVersionState.validChange;
}
}

Expand All @@ -257,7 +288,7 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
printError('${indentation}Incorrectly updated version.\n'
'${indentation}HEAD: $currentVersion, $source: $previousVersion.\n'
'${indentation}Allowed versions: $allowedNextVersions');
return false;
return _CurrentVersionState.invalidChange;
}

final bool isPlatformInterface =
Expand All @@ -268,16 +299,20 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
allowedNextVersions[currentVersion] == NextVersionType.BREAKING_MAJOR) {
printError('${indentation}Breaking change detected.\n'
'${indentation}Breaking changes to platform interfaces are strongly discouraged.\n');
return false;
return _CurrentVersionState.invalidChange;
}
return true;
return _CurrentVersionState.validChange;
}

/// Returns whether or not the pubspec version and CHANGELOG version for
/// [plugin] match.
Future<bool> _hasConsistentVersion(
/// Checks whether or not [package]'s CHANGELOG's versioning is correct,
/// both that it matches [pubspec] and that NEXT is used correctly, printing
/// the results of its checks.
///
/// Returns false if the CHANGELOG fails validation.
Future<bool> _validateChangelogVersion(
Directory package, {
required Pubspec pubspec,
required bool pubspecVersionChanged,
}) async {
// This method isn't called unless `version` is non-null.
final Version fromPubspec = pubspec.version!;
Expand All @@ -296,10 +331,19 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
// Remove all leading mark down syntax from the version line.
String? versionString = firstLineWithText?.split(' ').last;

final String badNextErrorMessage = '${indentation}When bumping the version '
'for release, the NEXT section should be incorporated into the new '
'version\'s release notes.';

// Skip validation for the special NEXT version that's used to accumulate
// changes that don't warrant publishing on their own.
final bool hasNextSection = versionString == 'NEXT';
if (hasNextSection) {
// NEXT should not be present in a commit that changes the version.
if (pubspecVersionChanged) {
printError(badNextErrorMessage);
return false;
}
print(
'${indentation}Found NEXT; validating next version in the CHANGELOG.');
// Ensure that the version in pubspec hasn't changed without updating
Expand Down Expand Up @@ -334,9 +378,7 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog.
if (!hasNextSection) {
final RegExp nextRegex = RegExp(r'^#+\s*NEXT\s*$');
if (lines.any((String line) => nextRegex.hasMatch(line))) {
printError('${indentation}When bumping the version for release, the '
'NEXT section should be incorporated into the new version\'s '
'release notes.');
printError(badNextErrorMessage);
return false;
}
}
Expand Down
54 changes: 50 additions & 4 deletions script/tool/test/version_check_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@ void main() {
* Some other changes.
''';
createFakeCHANGELOG(pluginDirectory, changelog);
gitShowResponses = <String, String>{
'master:packages/plugin/pubspec.yaml': 'version: 1.0.0',
};

final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=master']);
await expectLater(
Expand All @@ -384,8 +388,7 @@ void main() {
);
});

test('Fail if NEXT is left in the CHANGELOG when adding a version bump',
() async {
test('Fail if NEXT appears after a version', () async {
const String version = '1.0.1';
final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, version: version);
Expand Down Expand Up @@ -419,6 +422,45 @@ void main() {
);
});

test('Fail if NEXT is left in the CHANGELOG when adding a version bump',
() async {
const String version = '1.0.1';
final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, version: version);

const String changelog = '''
## NEXT
* Some changes that should have been folded in 1.0.1.
## $version
* Some changes.
## 1.0.0
* Some other changes.
''';
createFakeCHANGELOG(pluginDirectory, changelog);
gitShowResponses = <String, String>{
'master:packages/plugin/pubspec.yaml': 'version: 1.0.0',
};

bool hasError = false;
final List<String> output = await runCapturingPrint(runner, <String>[
'version-check',
'--base-sha=master',
'--against-pub'
], errorHandler: (Error e) {
expect(e, isA<ToolExit>());
hasError = true;
});
expect(hasError, isTrue);

expect(
output,
containsAllInOrder(<Matcher>[
contains('When bumping the version for release, the NEXT section '
'should be incorporated into the new version\'s release notes.')
]),
);
});

test('Fail if the version changes without replacing NEXT', () async {
final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, version: '1.0.1');
Expand All @@ -430,6 +472,10 @@ void main() {
* Some other changes.
''';
createFakeCHANGELOG(pluginDirectory, changelog);
gitShowResponses = <String, String>{
'master:packages/plugin/pubspec.yaml': 'version: 1.0.0',
};

bool hasError = false;
final List<String> output = await runCapturingPrint(runner, <String>[
'version-check',
Expand All @@ -444,8 +490,8 @@ void main() {
expect(
output,
containsAllInOrder(<Matcher>[
contains('Found NEXT; validating next version in the CHANGELOG.'),
contains('Versions in CHANGELOG.md and pubspec.yaml do not match.'),
contains('When bumping the version for release, the NEXT section '
'should be incorporated into the new version\'s release notes.')
]),
);
});
Expand Down