Skip to content

Commit

Permalink
Error out if the language version of the target package is too low (#…
Browse files Browse the repository at this point in the history
…1474)

Much be at least 3.0 since we generate code with case statements

Fixes #1462
  • Loading branch information
kevmoo authored Jan 13, 2025
1 parent 3318916 commit 3dd6767
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 24 deletions.
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 {
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)
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,
['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(
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);
}

0 comments on commit 3dd6767

Please sign in to comment.