From 9ca91a831342de8be5c6645ba02334d67d7dad10 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 11 Jan 2025 15:48:02 -0800 Subject: [PATCH 1/6] Error out if the language version of the target package is too low Much be at least 3.0 since we generate code with case statements Fixes https://github.com/google/json_serializable.dart/issues/1462 --- json_serializable/CHANGELOG.md | 5 + .../lib/src/check_dependencies.dart | 39 ++++++-- json_serializable/lib/src/constants.dart | 4 + json_serializable/pubspec.yaml | 2 +- .../test/annotation_version_test.dart | 92 +++++++++++++------ 5 files changed, 103 insertions(+), 39 deletions(-) diff --git a/json_serializable/CHANGELOG.md b/json_serializable/CHANGELOG.md index a0e2db27b..d8c5b5391 100644 --- a/json_serializable/CHANGELOG.md +++ b/json_serializable/CHANGELOG.md @@ -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`. diff --git a/json_serializable/lib/src/check_dependencies.dart b/json_serializable/lib/src/check_dependencies.dart index 7ea84bef4..0e5ef3078 100644 --- a/json_serializable/lib/src/check_dependencies.dart +++ b/json_serializable/lib/src/check_dependencies.dart @@ -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(supportLanguageConstraint); final requiredJsonAnnotationMinVersion = Version.parse('4.9.0'); Future pubspecHasRightVersion(BuildStep buildStep) async { @@ -37,21 +41,36 @@ Future _validatePubspec(bool production, BuildStep buildStep) async { return; } - Future 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 errorMessage = _checkAnnotationConstraint( - pubspec, - !production, - ); + final thisPackage = packageConfig[currentPackageName]!; - if (errorMessage == null) return; + // build_runner will error out without an SDK version - so this "should" be + // fine. + final thisPackageVersion = thisPackage.languageVersion!; - log.warning(errorMessage); + 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 ' + '`$supportLanguageConstraint`.', + ); + } } String? _checkAnnotationConstraint( diff --git a/json_serializable/lib/src/constants.dart b/json_serializable/lib/src/constants.dart index a151ff034..b28cf1fd5 100644 --- a/json_serializable/lib/src/constants.dart +++ b/json_serializable/lib/src/constants.dart @@ -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 supportLanguageConstraint = '^3.0.0'; diff --git a/json_serializable/pubspec.yaml b/json_serializable/pubspec.yaml index 4a3c00a20..80c9658aa 100644 --- a/json_serializable/pubspec.yaml +++ b/json_serializable/pubspec.yaml @@ -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. diff --git a/json_serializable/test/annotation_version_test.dart b/json_serializable/test/annotation_version_test.dart index 1aed36547..86a5be219 100644 --- a/json_serializable/test/annotation_version_test.dart +++ b/json_serializable/test/annotation_version_test.dart @@ -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'; @@ -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; @@ -29,11 +30,32 @@ 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}, + warningMessages: [ + 'The language version ($sdkLowerBound) of this package ' + '($_testPkgName) does not match the required range ' + '`$supportLanguageConstraint`.' + ], + ); + }); + + test('is at least the required `$supportLanguageConstraint`', () async { + await _structurePackage( + dependencies: {'json_annotation': _annotationLowerBound}, + warningMessages: [], + ); + }); + }); + test( 'missing dependency in production code', () => _structurePackage( - sourceDirectory: 'lib', - message: _missingProductionDep, + warningMessages: [_missingProductionDep], ), ); @@ -41,51 +63,51 @@ void main() { 'missing dependency in example code', () => _structurePackage( sourceDirectory: 'example', - message: - 'You are missing a required dependency on json_annotation with a ' - 'lower bound of at least "$_annotationLowerBound".', + warningMessages: [ + 'You are missing a required dependency on json_annotation with a ' + 'lower bound of at least "$_annotationLowerBound".' + ], ), ); test( 'dev dependency with a production usage', () => _structurePackage( - sourceDirectory: 'lib', devDependencies: {'json_annotation': _annotationLowerBound}, - message: _missingProductionDep, + warningMessages: [_missingProductionDep], ), ); test( 'dependency with `null` constraint', () => _structurePackage( - sourceDirectory: 'lib', dependencies: {'json_annotation': null}, - message: - 'The version constraint "any" on json_annotation allows versions ' - 'before $_annotationLowerBound which is not allowed.', + warningMessages: [ + 'The version constraint "any" on json_annotation allows versions ' + 'before $_annotationLowerBound which is not allowed.' + ], ), ); test( 'dependency with "any" constraint', () => _structurePackage( - sourceDirectory: 'lib', dependencies: {'json_annotation': 'any'}, - message: - 'The version constraint "any" on json_annotation allows versions ' - 'before $_annotationLowerBound which is not allowed.', + warningMessages: [ + 'The version constraint "any" on json_annotation allows versions ' + 'before $_annotationLowerBound which is not allowed.' + ], ), ); 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 ' - 'before $_annotationLowerBound which is not allowed.', + warningMessages: [ + 'The version constraint "^4.0.0" on json_annotation allows versions ' + 'before $_annotationLowerBound which is not allowed.' + ], ), ); } @@ -114,16 +136,19 @@ final _missingProductionDep = '"dependencies" section of your pubspec with a lower bound of at least ' '"$_annotationLowerBound".'; +const _testPkgName = '_test_pkg'; + Future _structurePackage({ - required String sourceDirectory, - required String message, + String sourceDirectory = 'lib', + required List warningMessages, + Map environment = const {'sdk': supportLanguageConstraint}, Map dependencies = const {}, Map 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, @@ -162,9 +187,8 @@ class SomeClass{} ) ], ).create(); - final proc = await TestProcess.start( - 'dart', + Platform.resolvedExecutable, ['run', 'build_runner', 'build'], workingDirectory: d.sandbox, ); @@ -175,9 +199,21 @@ class SomeClass{} print(line); } - expect(lines.toString(), contains(''' + final output = lines.toString(); + final warningCount = '[WARNING]'.allMatches(output).length; + expect( + warningCount, + warningMessages.length, + reason: + 'Expected the number of output warnings ($warningCount) to match the ' + 'number of expected warnings (${warningMessages.length}.', + ); + + for (var warningMessage in warningMessages) { + expect(output, contains(''' [WARNING] json_serializable on $sourceDirectory/sample.dart: -$message''')); +$warningMessage''')); + } await proc.shouldExit(0); } From 83956f377f81b086a503686746058d1287c03e3d Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 11 Jan 2025 15:53:53 -0800 Subject: [PATCH 2/6] cleaner --- .../test/annotation_version_test.dart | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/json_serializable/test/annotation_version_test.dart b/json_serializable/test/annotation_version_test.dart index 86a5be219..31e46b69e 100644 --- a/json_serializable/test/annotation_version_test.dart +++ b/json_serializable/test/annotation_version_test.dart @@ -36,18 +36,16 @@ void main() { await _structurePackage( environment: const {'sdk': '^$sdkLowerBound'}, dependencies: {'json_annotation': _annotationLowerBound}, - warningMessages: [ - 'The language version ($sdkLowerBound) of this package ' - '($_testPkgName) does not match the required range ' - '`$supportLanguageConstraint`.' - ], + warningMessage: 'The language version ($sdkLowerBound) of this package ' + '($_testPkgName) does not match the required range ' + '`$supportLanguageConstraint`.', ); }); test('is at least the required `$supportLanguageConstraint`', () async { await _structurePackage( dependencies: {'json_annotation': _annotationLowerBound}, - warningMessages: [], + warningMessage: null, ); }); }); @@ -55,7 +53,7 @@ void main() { test( 'missing dependency in production code', () => _structurePackage( - warningMessages: [_missingProductionDep], + warningMessage: _missingProductionDep, ), ); @@ -63,10 +61,9 @@ void main() { 'missing dependency in example code', () => _structurePackage( sourceDirectory: 'example', - warningMessages: [ - 'You are missing a required dependency on json_annotation with a ' - 'lower bound of at least "$_annotationLowerBound".' - ], + warningMessage: + 'You are missing a required dependency on json_annotation with a ' + 'lower bound of at least "$_annotationLowerBound".', ), ); @@ -74,7 +71,7 @@ void main() { 'dev dependency with a production usage', () => _structurePackage( devDependencies: {'json_annotation': _annotationLowerBound}, - warningMessages: [_missingProductionDep], + warningMessage: _missingProductionDep, ), ); @@ -82,10 +79,9 @@ void main() { 'dependency with `null` constraint', () => _structurePackage( dependencies: {'json_annotation': null}, - warningMessages: [ - 'The version constraint "any" on json_annotation allows versions ' - 'before $_annotationLowerBound which is not allowed.' - ], + warningMessage: + 'The version constraint "any" on json_annotation allows versions ' + 'before $_annotationLowerBound which is not allowed.', ), ); @@ -93,10 +89,9 @@ void main() { 'dependency with "any" constraint', () => _structurePackage( dependencies: {'json_annotation': 'any'}, - warningMessages: [ - 'The version constraint "any" on json_annotation allows versions ' - 'before $_annotationLowerBound which is not allowed.' - ], + warningMessage: + 'The version constraint "any" on json_annotation allows versions ' + 'before $_annotationLowerBound which is not allowed.', ), ); @@ -104,10 +99,9 @@ void main() { 'dependency with too low version range', () => _structurePackage( dependencies: {'json_annotation': '^4.0.0'}, - warningMessages: [ - 'The version constraint "^4.0.0" on json_annotation allows versions ' - 'before $_annotationLowerBound which is not allowed.' - ], + warningMessage: + 'The version constraint "^4.0.0" on json_annotation allows versions ' + 'before $_annotationLowerBound which is not allowed.', ), ); } @@ -140,7 +134,7 @@ const _testPkgName = '_test_pkg'; Future _structurePackage({ String sourceDirectory = 'lib', - required List warningMessages, + required String? warningMessage, Map environment = const {'sdk': supportLanguageConstraint}, Map dependencies = const {}, Map devDependencies = const {}, @@ -200,16 +194,17 @@ class SomeClass{} } final output = lines.toString(); + final expectedWarningCount = warningMessage == null ? 0 : 1; final warningCount = '[WARNING]'.allMatches(output).length; expect( warningCount, - warningMessages.length, + expectedWarningCount, reason: 'Expected the number of output warnings ($warningCount) to match the ' - 'number of expected warnings (${warningMessages.length}.', + 'number of expected warnings ($expectedWarningCount.', ); - for (var warningMessage in warningMessages) { + if (warningMessage != null) { expect(output, contains(''' [WARNING] json_serializable on $sourceDirectory/sample.dart: $warningMessage''')); From 46c1cfa2e034e2b04391d4bb74cabff538a59737 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 11 Jan 2025 15:54:19 -0800 Subject: [PATCH 3/6] cleaner --- .../test/annotation_version_test.dart | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/json_serializable/test/annotation_version_test.dart b/json_serializable/test/annotation_version_test.dart index 31e46b69e..0ccb8487f 100644 --- a/json_serializable/test/annotation_version_test.dart +++ b/json_serializable/test/annotation_version_test.dart @@ -36,7 +36,7 @@ void main() { await _structurePackage( environment: const {'sdk': '^$sdkLowerBound'}, dependencies: {'json_annotation': _annotationLowerBound}, - warningMessage: 'The language version ($sdkLowerBound) of this package ' + message: 'The language version ($sdkLowerBound) of this package ' '($_testPkgName) does not match the required range ' '`$supportLanguageConstraint`.', ); @@ -45,7 +45,7 @@ void main() { test('is at least the required `$supportLanguageConstraint`', () async { await _structurePackage( dependencies: {'json_annotation': _annotationLowerBound}, - warningMessage: null, + message: null, ); }); }); @@ -53,7 +53,7 @@ void main() { test( 'missing dependency in production code', () => _structurePackage( - warningMessage: _missingProductionDep, + message: _missingProductionDep, ), ); @@ -61,7 +61,7 @@ void main() { 'missing dependency in example code', () => _structurePackage( sourceDirectory: 'example', - warningMessage: + message: 'You are missing a required dependency on json_annotation with a ' 'lower bound of at least "$_annotationLowerBound".', ), @@ -71,7 +71,7 @@ void main() { 'dev dependency with a production usage', () => _structurePackage( devDependencies: {'json_annotation': _annotationLowerBound}, - warningMessage: _missingProductionDep, + message: _missingProductionDep, ), ); @@ -79,7 +79,7 @@ void main() { 'dependency with `null` constraint', () => _structurePackage( dependencies: {'json_annotation': null}, - warningMessage: + message: 'The version constraint "any" on json_annotation allows versions ' 'before $_annotationLowerBound which is not allowed.', ), @@ -89,7 +89,7 @@ void main() { 'dependency with "any" constraint', () => _structurePackage( dependencies: {'json_annotation': 'any'}, - warningMessage: + message: 'The version constraint "any" on json_annotation allows versions ' 'before $_annotationLowerBound which is not allowed.', ), @@ -99,7 +99,7 @@ void main() { 'dependency with too low version range', () => _structurePackage( dependencies: {'json_annotation': '^4.0.0'}, - warningMessage: + message: 'The version constraint "^4.0.0" on json_annotation allows versions ' 'before $_annotationLowerBound which is not allowed.', ), @@ -134,7 +134,7 @@ const _testPkgName = '_test_pkg'; Future _structurePackage({ String sourceDirectory = 'lib', - required String? warningMessage, + required String? message, Map environment = const {'sdk': supportLanguageConstraint}, Map dependencies = const {}, Map devDependencies = const {}, @@ -194,7 +194,7 @@ class SomeClass{} } final output = lines.toString(); - final expectedWarningCount = warningMessage == null ? 0 : 1; + final expectedWarningCount = message == null ? 0 : 1; final warningCount = '[WARNING]'.allMatches(output).length; expect( warningCount, @@ -204,10 +204,10 @@ class SomeClass{} 'number of expected warnings ($expectedWarningCount.', ); - if (warningMessage != null) { + if (message != null) { expect(output, contains(''' [WARNING] json_serializable on $sourceDirectory/sample.dart: -$warningMessage''')); +$message''')); } await proc.shouldExit(0); From 454c7a441771b3f68ff7b1bde84b9402700325b0 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 11 Jan 2025 16:02:27 -0800 Subject: [PATCH 4/6] even cleaner --- .../lib/src/check_dependencies.dart | 20 ++++++++++--------- json_serializable/lib/src/constants.dart | 2 +- .../test/annotation_version_test.dart | 15 +++++++++----- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/json_serializable/lib/src/check_dependencies.dart b/json_serializable/lib/src/check_dependencies.dart index 0e5ef3078..13117f268 100644 --- a/json_serializable/lib/src/check_dependencies.dart +++ b/json_serializable/lib/src/check_dependencies.dart @@ -13,7 +13,7 @@ import 'constants.dart'; const _productionDirectories = {'lib', 'bin'}; const _annotationPkgName = 'json_annotation'; final _supportLanguageRange = - VersionConstraint.parse(supportLanguageConstraint); + VersionConstraint.parse(supportedLanguageConstraint); final requiredJsonAnnotationMinVersion = Version.parse('4.9.0'); Future pubspecHasRightVersion(BuildStep buildStep) async { @@ -52,23 +52,25 @@ Future _validatePubspec(bool production, BuildStep buildStep) async { // // 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 this "should" be - // fine. + // build_runner will error out without an SDK version - so assuming + // `languageVersion` is not null. final thisPackageVersion = thisPackage.languageVersion!; 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 ' - '`$supportLanguageConstraint`.', + ''' +The language version ($thisPackageVer) of this package ($currentPackageName) does not match the required range `$supportedLanguageConstraint`. + +Edit pubspec.yaml to include an SDK constraint of at least $supportedLanguageConstraint. + +environment: + sdk: $supportedLanguageConstraint +''', ); } } diff --git a/json_serializable/lib/src/constants.dart b/json_serializable/lib/src/constants.dart index b28cf1fd5..70e85216f 100644 --- a/json_serializable/lib/src/constants.dart +++ b/json_serializable/lib/src/constants.dart @@ -16,4 +16,4 @@ const converterOrKeyInstructions = r''' /// This package generates code that uses case statements, which were introduced /// in Dart 3.0. -const supportLanguageConstraint = '^3.0.0'; +const supportedLanguageConstraint = '^3.0.0'; diff --git a/json_serializable/test/annotation_version_test.dart b/json_serializable/test/annotation_version_test.dart index 0ccb8487f..f28cb0d51 100644 --- a/json_serializable/test/annotation_version_test.dart +++ b/json_serializable/test/annotation_version_test.dart @@ -36,13 +36,18 @@ void main() { 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 ' - '`$supportLanguageConstraint`.', + 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 `$supportLanguageConstraint`', () async { + test('is at least the required `$supportedLanguageConstraint`', () async { await _structurePackage( dependencies: {'json_annotation': _annotationLowerBound}, message: null, @@ -135,7 +140,7 @@ const _testPkgName = '_test_pkg'; Future _structurePackage({ String sourceDirectory = 'lib', required String? message, - Map environment = const {'sdk': supportLanguageConstraint}, + Map environment = const {'sdk': supportedLanguageConstraint}, Map dependencies = const {}, Map devDependencies = const {}, }) async { From 85973f18d9861a47a6b42f098e46f1543a204d4d Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 11 Jan 2025 16:15:05 -0800 Subject: [PATCH 5/6] expression bodies are nice --- json_serializable/test/annotation_version_test.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/json_serializable/test/annotation_version_test.dart b/json_serializable/test/annotation_version_test.dart index f28cb0d51..6dc36ce68 100644 --- a/json_serializable/test/annotation_version_test.dart +++ b/json_serializable/test/annotation_version_test.dart @@ -47,12 +47,13 @@ environment: ); }); - test('is at least the required `$supportedLanguageConstraint`', () async { - await _structurePackage( + test( + 'is at least the required `$supportedLanguageConstraint`', + () async => await _structurePackage( dependencies: {'json_annotation': _annotationLowerBound}, message: null, - ); - }); + ), + ); }); test( From abe9f1911ae65f43bc07fdc75049cde857718715 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 13 Jan 2025 11:39:53 -0800 Subject: [PATCH 6/6] Update json_serializable/test/annotation_version_test.dart Co-authored-by: Nate Bosch --- json_serializable/test/annotation_version_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json_serializable/test/annotation_version_test.dart b/json_serializable/test/annotation_version_test.dart index 6dc36ce68..607634ee1 100644 --- a/json_serializable/test/annotation_version_test.dart +++ b/json_serializable/test/annotation_version_test.dart @@ -207,7 +207,7 @@ class SomeClass{} expectedWarningCount, reason: 'Expected the number of output warnings ($warningCount) to match the ' - 'number of expected warnings ($expectedWarningCount.', + 'number of expected warnings ($expectedWarningCount).', ); if (message != null) {