Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error out if the language version of the target package is too low #1474

Merged
merged 6 commits into from
Jan 13, 2025
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
5 changes: 5 additions & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 6.9.3

- Error out if the target package does not have a language version of `3.0` or
greater.

## 6.9.2

- Support the latest `package:analyzer`.
Expand Down
41 changes: 31 additions & 10 deletions json_serializable/lib/src/check_dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import 'package:build/build.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';

import 'constants.dart';

const _productionDirectories = {'lib', 'bin'};
const _annotationPkgName = 'json_annotation';
final _supportLanguageRange =
VersionConstraint.parse(supportedLanguageConstraint);
final requiredJsonAnnotationMinVersion = Version.parse('4.9.0');

Future<void> pubspecHasRightVersion(BuildStep buildStep) async {
Expand Down Expand Up @@ -37,21 +41,38 @@ Future<void> _validatePubspec(bool production, BuildStep buildStep) async {
return;
}

Future<Pubspec> readPubspec(AssetId asset) async {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No clue why I had an inline function called once. Ditched it.

final string = await buildStep.readAsString(asset);
return Pubspec.parse(string, sourceUrl: asset.uri);
final string = await buildStep.readAsString(pubspecAssetId);
final pubspec = Pubspec.parse(string, sourceUrl: pubspecAssetId.uri);

if (_checkAnnotationConstraint(pubspec, !production)
Copy link
Collaborator Author

@kevmoo kevmoo Jan 11, 2025

Choose a reason for hiding this comment

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

Seemed the cleanest way to call the function and "do the thing" if the result is not null.

case final errorMessage?) {
log.warning(errorMessage);
}

final pubspec = await readPubspec(pubspecAssetId);
//
// Ensure the current package language version is at least the minimum.
//
final currentPackageName = pubspec.name;
final packageConfig = await buildStep.packageConfig;
final thisPackage = packageConfig[currentPackageName]!;

// build_runner will error out without an SDK version - so assuming
// `languageVersion` is not null.
final thisPackageVersion = thisPackage.languageVersion!;

final errorMessage = _checkAnnotationConstraint(
pubspec,
!production,
);
final thisPackageVer = Version.parse('$thisPackageVersion.0');
if (!_supportLanguageRange.allows(thisPackageVer)) {
log.warning(
'''
The language version ($thisPackageVer) of this package ($currentPackageName) does not match the required range `$supportedLanguageConstraint`.

if (errorMessage == null) return;
Edit pubspec.yaml to include an SDK constraint of at least $supportedLanguageConstraint.

log.warning(errorMessage);
environment:
sdk: $supportedLanguageConstraint
''',
);
}
}

String? _checkAnnotationConstraint(
Expand Down
4 changes: 4 additions & 0 deletions json_serializable/lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ const converterOrKeyInstructions = r'''
* Use `JsonKey` fields `fromJson` and `toJson`
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/fromJson.html
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/toJson.html''';

/// This package generates code that uses case statements, which were introduced
/// in Dart 3.0.
const supportedLanguageConstraint = '^3.0.0';
2 changes: 1 addition & 1 deletion json_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_serializable
version: 6.9.2
version: 6.9.3
description: >-
Automatically generate code for converting to and from JSON by annotating
Dart classes.
Expand Down
63 changes: 50 additions & 13 deletions json_serializable/test/annotation_version_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ library;
import 'dart:io';

import 'package:json_serializable/src/check_dependencies.dart';
import 'package:json_serializable/src/constants.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
Expand All @@ -20,7 +21,7 @@ import 'package:test_process/test_process.dart';
import 'test_utils.dart';

void main() {
test('validate pubspec constraint', () {
test('validate pubspec constraint', () async {
final annotationConstraint =
_jsonSerialPubspec.dependencies['json_annotation'] as HostedDependency;
final versionRange = annotationConstraint.version as VersionRange;
Expand All @@ -29,10 +30,35 @@ void main() {
expect(versionRange.min, requiredJsonAnnotationMinVersion);
});

group('language version', () {
test('is less than required', () async {
const sdkLowerBound = '2.12.0';
await _structurePackage(
environment: const {'sdk': '^$sdkLowerBound'},
dependencies: {'json_annotation': _annotationLowerBound},
message: '''
The language version ($sdkLowerBound) of this package ($_testPkgName) does not match the required range `$supportedLanguageConstraint`.

Edit pubspec.yaml to include an SDK constraint of at least $supportedLanguageConstraint.

environment:
sdk: $supportedLanguageConstraint
''',
);
});

test(
'is at least the required `$supportedLanguageConstraint`',
() async => await _structurePackage(
dependencies: {'json_annotation': _annotationLowerBound},
message: null,
),
);
});

test(
'missing dependency in production code',
() => _structurePackage(
sourceDirectory: 'lib',
message: _missingProductionDep,
),
);
Expand All @@ -50,7 +76,6 @@ void main() {
test(
'dev dependency with a production usage',
() => _structurePackage(
sourceDirectory: 'lib',
devDependencies: {'json_annotation': _annotationLowerBound},
message: _missingProductionDep,
),
Expand All @@ -59,7 +84,6 @@ void main() {
test(
'dependency with `null` constraint',
() => _structurePackage(
sourceDirectory: 'lib',
dependencies: {'json_annotation': null},
message:
'The version constraint "any" on json_annotation allows versions '
Expand All @@ -70,7 +94,6 @@ void main() {
test(
'dependency with "any" constraint',
() => _structurePackage(
sourceDirectory: 'lib',
dependencies: {'json_annotation': 'any'},
message:
'The version constraint "any" on json_annotation allows versions '
Expand All @@ -81,7 +104,6 @@ void main() {
test(
'dependency with too low version range',
() => _structurePackage(
sourceDirectory: 'lib',
dependencies: {'json_annotation': '^4.0.0'},
message:
'The version constraint "^4.0.0" on json_annotation allows versions '
Expand Down Expand Up @@ -114,16 +136,19 @@ final _missingProductionDep =
'"dependencies" section of your pubspec with a lower bound of at least '
'"$_annotationLowerBound".';

const _testPkgName = '_test_pkg';

Future<void> _structurePackage({
required String sourceDirectory,
required String message,
String sourceDirectory = 'lib',
required String? message,
Map<String, dynamic> environment = const {'sdk': supportedLanguageConstraint},
Map<String, dynamic> dependencies = const {},
Map<String, dynamic> devDependencies = const {},
}) async {
final pubspec = loudEncode(
{
'name': '_test_pkg',
'environment': {'sdk': '>=2.14.0 <3.0.0'},
'name': _testPkgName,
'environment': environment,
'dependencies': dependencies,
'dev_dependencies': {
...devDependencies,
Expand Down Expand Up @@ -162,9 +187,8 @@ class SomeClass{}
)
],
).create();

final proc = await TestProcess.start(
'dart',
Platform.resolvedExecutable,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tests should run on windows now!

['run', 'build_runner', 'build'],
workingDirectory: d.sandbox,
);
Expand All @@ -175,9 +199,22 @@ class SomeClass{}
print(line);
}

expect(lines.toString(), contains('''
final output = lines.toString();
final expectedWarningCount = message == null ? 0 : 1;
final warningCount = '[WARNING]'.allMatches(output).length;
expect(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Make sure there are only the warnings we are expecting and no more!

warningCount,
expectedWarningCount,
reason:
'Expected the number of output warnings ($warningCount) to match the '
'number of expected warnings ($expectedWarningCount).',
);

if (message != null) {
expect(output, contains('''
[WARNING] json_serializable on $sourceDirectory/sample.dart:
$message'''));
}

await proc.shouldExit(0);
}
Loading