diff --git a/.fvmrc b/.fvmrc index cf986e3..86a0226 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,4 +1,4 @@ { - "flutter": "3.35.2", + "flutter": "3.35.6", "flavors": {} } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 5ba347e..3cdef37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.35.2", + "dart.flutterSdkPath": ".fvm/versions/3.35.6", "dart.sdkPath": ".fvm/flutter_sdk/bin/cache/dart-sdk", "search.exclude": { "**/.fvm": true diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a5ce5..1c24cf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Version 0.23.1-dev +- Fix type hierarchy calculations when the type no longer is known by the new public API ## Version 0.23.0 - technical: use package_config to interact with package configs diff --git a/lib/src/diff/package_api_differ.dart b/lib/src/diff/package_api_differ.dart index 2056c38..28dd940 100644 --- a/lib/src/diff/package_api_differ.dart +++ b/lib/src/diff/package_api_differ.dart @@ -42,6 +42,11 @@ class PackageApiDiffer { 'Given models have different semantics. Old Package: ${oldApi.semantics}, New Package: ${newApi.semantics}'); } + final mergedTypeHierarchy = _mergeTypeHierarchy( + base: newApi.typeHierarchy, + filler: oldApi.typeHierarchy, + ); + try { final changes = [ ..._calculateInterfacesDiff( @@ -49,21 +54,21 @@ class PackageApiDiffer { newApi.interfaceDeclarations, Stack(), isExperimental: false, - typeHierarchy: newApi.typeHierarchy, + typeHierarchy: mergedTypeHierarchy, ), ..._calculateExecutablesDiff( oldApi.executableDeclarations, newApi.executableDeclarations, Stack(), isExperimental: false, - typeHierarchy: newApi.typeHierarchy, + typeHierarchy: mergedTypeHierarchy, ), ..._calculateFieldsDiff( oldApi.fieldDeclarations, newApi.fieldDeclarations, Stack(), isExperimental: false, - typeHierarchy: newApi.typeHierarchy, + typeHierarchy: mergedTypeHierarchy, ), ..._calculateIOSPlatformConstraintsDiff( oldApi.iosPlatformConstraints, @@ -95,6 +100,23 @@ class PackageApiDiffer { } } + TypeHierarchy _mergeTypeHierarchy({ + required TypeHierarchy base, + required TypeHierarchy filler, + }) { + final merged = base.clone(); + + // add types that were known in the old hierarchy but are no longer known in the new hierarchy + for (final oldTypeId in filler.registeredTypes) { + if (!merged.containsType(oldTypeId)) { + final baseTypes = filler.baseTypesOf(oldTypeId); + merged.registerType(oldTypeId, baseTypes ?? {}); + } + } + + return merged; + } + String _interfaceNameWithoutNamespace(String fullName) { final lastDotIndex = fullName.lastIndexOf('.'); if (lastDotIndex == -1) { @@ -1329,7 +1351,7 @@ class PackageApiDiffer { return result; } - _compareParameterTypesAndAddChange( + void _compareParameterTypesAndAddChange( TypeIdentifier oldTypeidentifier, TypeIdentifier newTypeIdentifier, Stack context, @@ -1360,7 +1382,7 @@ class PackageApiDiffer { } } - _comparePropertiesAndAddChange( + void _comparePropertiesAndAddChange( T oldValue, T newValue, Stack context, diff --git a/lib/src/model/type_hierarchy.dart b/lib/src/model/type_hierarchy.dart index 14087e5..aec4ef2 100644 --- a/lib/src/model/type_hierarchy.dart +++ b/lib/src/model/type_hierarchy.dart @@ -232,6 +232,57 @@ class TypeHierarchy { ); } + /// clones this type hierarchy + TypeHierarchy clone() { + final cloned = TypeHierarchy.empty(); + for (final entry in _types.entries) { + cloned._types[entry.key] = {...entry.value}; + } + return cloned; + } + + /// returns all registered types in this type hierarchy + List get registeredTypes { + final result = []; + for (final items in _types.values) { + for (final item in items) { + result.add(item.typeIdentifier); + } + } + return result; + } + + /// returns the base types of the given [typeIdentifier] + /// if the base types are not retrievable then [null] is returned + Set? baseTypesOf(TypeIdentifier typeIdentifier) { + final items = _types[typeIdentifier.packageAndTypeName]; + if (items == null || items.isEmpty) { + return null; + } + + if (items.length > 1) { + // there is more than one type with the same name in one package => we need to check the full library name + // and remove all occurences that don't match + final matchingItems = items.where((i) => + i.typeIdentifier.packageRelativeLibraryPath == + typeIdentifier.packageRelativeLibraryPath); + + try { + // finally we try to get that single entry + return matchingItems.single.baseTypeIdentifiers; + } catch (e) { + // and if this fails we treat the base types as not retrievable and return [null] + return null; + } + } + return items.single.baseTypeIdentifiers; + } + + /// checks if this type hierarchy contains the given [typeIdentifier] + bool containsType(TypeIdentifier typeIdentifier) { + return _types.containsKey(typeIdentifier.packageAndTypeName); + } + /// checks if [newTypeIdentifier] is a compatible replacement for [oldTypeIdentifier] bool isCompatibleReplacement({ required TypeIdentifier oldTypeIdentifier, diff --git a/test/integration_tests/diff/native_test.dart b/test/integration_tests/diff/native_test.dart new file mode 100644 index 0000000..aafa099 --- /dev/null +++ b/test/integration_tests/diff/native_test.dart @@ -0,0 +1,44 @@ +import 'package:dart_apitool/api_tool.dart'; +import 'package:test/test.dart'; + +import '../helper/integration_test_helper.dart'; + +void main() { + final gitUrl = 'https://github.com/dart-lang/native.git'; + group('native diff tests', () { + group('widening in extensions', () { + final refParent = '0924cb0e80ed6ac39298363fabe0916808a4a1fe'; + final refChild = '29cf243b4b3452354ef905f62874a8c975ef1538'; + final parentRetriever = GitPackageApiRetriever( + gitUrl, + refParent, + relativePackagePath: 'pkgs/data_assets', + ); + final childRetriever = GitPackageApiRetriever( + gitUrl, + refChild, + relativePackagePath: 'pkgs/data_assets', + ); + + late PackageApiDiffResult diffResult; + + setUpAll(() async { + final oldApi = await parentRetriever.retrieve(); + final newApi = await childRetriever.retrieve(); + diffResult = PackageApiDiffer( + options: PackageApiDifferOptions( + doCheckSdkVersion: false, + )).diff( + oldApi: oldApi, + newApi: newApi, + ); + }); + + test('should not be breaking', () { + final breakingChanges = + diffResult.apiChanges.where((change) => change.isBreaking).toList(); + expect(breakingChanges, []); + }); + }); + }); +} diff --git a/test/integration_tests/helper/integration_test_helper.dart b/test/integration_tests/helper/integration_test_helper.dart index c6a9501..3cd34e1 100644 --- a/test/integration_tests/helper/integration_test_helper.dart +++ b/test/integration_tests/helper/integration_test_helper.dart @@ -63,11 +63,16 @@ class GitPackageApiRetriever { packagePath = p.join(tempDir.path, relativePackagePath); } + await DartInteraction.runDartOrFlutterCommand(packagePath, + args: ['pub', 'get']); + final analyzer = PackageApiAnalyzer( packagePath: packagePath, doConsiderNonSrcAsEntryPoints: doConsiderNonSrcAsEntryPoints, ); - print('Analyzing $gitUrl $gitRef'); + final logSuffix = + (relativePackagePath != null) ? ' at $relativePackagePath' : ''; + print('Analyzing $gitUrl $gitRef$logSuffix'); final result = await analyzer.analyze(); await tempDir.delete(recursive: true); return result;