| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // ignore_for_file: non_constant_identifier_names | |
| import 'dart:core'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:api_summary/api_summary.dart'; | |
| import 'package:api_summary/src/api_builder.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; | |
| import 'test_utils.dart'; | |
| void main() { | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(MetaAnnotationsTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class MetaAnnotationsTest extends ApiSummaryTest { | |
| @override | |
| bool get addMetaPackageDep => true; | |
| Future<String> _build( | |
| Map<String, String> files, { | |
| ApiSummaryCustomizer? customizer, | |
| }) async { | |
| files.forEach(newFile); | |
| for (final file in files.keys) { | |
| if (file.endsWith('.dart')) await assertNoDiagnosticsInFile(file); | |
| } | |
| final context = contextCollection.contextFor( | |
| convertPath(testPackageLibPath), | |
| ); | |
| final package = await buildApiPackage( | |
| 'test', | |
| context, | |
| customizer ?? const ApiSummaryCustomizer(), | |
| ); | |
| return package.toString(); | |
| } | |
| Future<ApiSummary> _buildModel(Map<String, String> files) async { | |
| files.forEach(newFile); | |
| for (final file in files.keys) { | |
| if (file.endsWith('.dart')) await assertNoDiagnosticsInFile(file); | |
| } | |
| final context = contextCollection.contextFor( | |
| convertPath(testPackageLibPath), | |
| ); | |
| return buildApiPackage('test', context, const ApiSummaryCustomizer()); | |
| } | |
| Future<void> test_class_immutable() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': ''' | |
| import 'package:meta/meta.dart'; | |
| @immutable | |
| class ImmutableClass { | |
| final int x; | |
| const ImmutableClass(this.x); | |
| } | |
| ''', | |
| }); | |
| expect(summary, ''' | |
| package:test/file.dart: | |
| ImmutableClass (class extends Object, immutable): | |
| new (const constructor: ImmutableClass Function(int)) | |
| x (getter: int) | |
| dart:core: | |
| Object (referenced) | |
| int (referenced) | |
| '''); | |
| } | |
| Future<void> test_internal_annotations() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': ''' | |
| import 'src/internal.dart'; | |
| InternalClass getInternal() => InternalClass(); | |
| ''', | |
| '$testPackageLibPath/src/internal.dart': ''' | |
| import 'package:meta/meta.dart'; | |
| @internal | |
| class InternalClass { | |
| @internal | |
| void internalMethod() {} | |
| } | |
| ''', | |
| }, customizer: _ShowInternalDetailsCustomizer()); | |
| expect(summary, ''' | |
| package:test/file.dart: | |
| getInternal (function: InternalClass Function()) | |
| package:test/src/internal.dart: | |
| InternalClass (class extends Object, non-public, internal): | |
| new (constructor: InternalClass Function(), internal) | |
| internalMethod (method: void Function(), internal) | |
| dart:core: | |
| Object (referenced) | |
| '''); | |
| } | |
| Future<void> test_member_contractual_annotations() async { | |
| final summary = await _build({ | |
| '$testPackageLibPath/file.dart': ''' | |
| import 'package:meta/meta.dart'; | |
| class Base { | |
| @protected | |
| void protectedMethod() {} | |
| @mustCallSuper | |
| void mustCallSuperMethod() {} | |
| @visibleForOverriding | |
| void visibleForOverridingMethod() {} | |
| @nonVirtual | |
| void nonVirtualMethod() {} | |
| @mustBeOverridden | |
| void mustBeOverriddenMethod() {} | |
| @useResult | |
| int useResultMethod() => 42; | |
| @protected | |
| int protectedField = 0; | |
| } | |
| @useResult | |
| int topLevelUseResultFunction() => 1; | |
| ''', | |
| }); | |
| expect(summary, ''' | |
| package:test/file.dart: | |
| topLevelUseResultFunction (function: int Function(), use result) | |
| Base (class extends Object): | |
| new (constructor: Base Function()) | |
| protectedField (getter: int, protected) | |
| protectedField= (setter: int, protected) | |
| mustBeOverriddenMethod (method: void Function(), must be overridden) | |
| mustCallSuperMethod (method: void Function(), must call super) | |
| nonVirtualMethod (method: void Function(), non-virtual) | |
| protectedMethod (method: void Function(), protected) | |
| useResultMethod (method: int Function(), use result) | |
| visibleForOverridingMethod (method: void Function(), visible for overriding) | |
| dart:core: | |
| Object (referenced) | |
| int (referenced) | |
| '''); | |
| } | |
| Future<void> test_json_roundtrip_with_meta_annotations() async { | |
| final model = await _buildModel({ | |
| '$testPackageLibPath/file.dart': ''' | |
| import 'package:meta/meta.dart'; | |
| @immutable | |
| class Target { | |
| @protected | |
| @mustCallSuper | |
| @nonVirtual | |
| @useResult | |
| int calculate() => 0; | |
| } | |
| ''', | |
| }); | |
| final jsonMap = model.toJson(); | |
| final rehydrated = ApiSummary.fromJson(jsonMap); | |
| final cls = rehydrated.libraries.single.classes.single; | |
| expect(cls.name, 'Target'); | |
| expect(cls.isImmutable, isTrue); | |
| expect(cls.isInternal, isFalse); | |
| final method = cls.methods.singleWhere((m) => m.name == 'calculate'); | |
| expect(method.isProtected, isTrue); | |
| expect(method.isMustCallSuper, isTrue); | |
| expect(method.isNonVirtual, isTrue); | |
| expect(method.isUseResult, isTrue); | |
| expect(method.isMustBeOverridden, isFalse); | |
| expect(method.isVisibleForOverriding, isFalse); | |
| // Verify false flags are omitted in JSON output | |
| final libJson = (jsonMap['libraries'] as List) | |
| .cast<Map<String, dynamic>>()[0]; | |
| final classJson = (libJson['classes'] as List) | |
| .cast<Map<String, dynamic>>()[0]; | |
| final methodJson = (classJson['methods'] as List) | |
| .cast<Map<String, dynamic>>()[0]; | |
| expect(methodJson, containsPair('isProtected', true)); | |
| expect(methodJson, containsPair('isMustCallSuper', true)); | |
| expect(methodJson, containsPair('isNonVirtual', true)); | |
| expect(methodJson, containsPair('isUseResult', true)); | |
| expect(methodJson, isNot(contains('isMustBeOverridden'))); | |
| expect(methodJson, isNot(contains('isVisibleForOverriding'))); | |
| } | |
| } | |
| final class _ShowInternalDetailsCustomizer extends ApiSummaryCustomizer { | |
| @override | |
| bool shouldShowDetails(Element element, ApiSummaryContext context) => | |
| element.name?.contains('Internal') ?? false; | |
| } |