blob: 85fb8c7a713c5f04ffecb1e5dc7a571ebdbccbc9 [file] [edit]
// 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.
/// Domain models and convenience extensions for contractual annotations
/// extracted from `package:meta`.
library;
import 'package:analyzer/dart/element/element.dart';
import 'package:collection/collection.dart';
import 'api_facet.dart';
/// Contractual annotations from `package:meta`.
enum MetaContract implements Comparable<MetaContract> {
experimental('experimental'),
immutable('immutable'),
internal('internal'),
mustBeOverridden('must be overridden'),
mustCallSuper('must call super'),
nonVirtual('non-virtual'),
protected('protected'),
useResult('use result'),
visibleForOverriding('visible for overriding'),
visibleForTesting('visible for testing');
/// The human-readable label used for text representation in summaries.
final String textLabel;
const MetaContract(this.textLabel);
@override
int compareTo(MetaContract other) => name.compareTo(other.name);
}
/// An [ApiFacet] capturing `package:meta` contracts.
final class MetaContractFacet implements ApiFacet {
@override
String get namespace => 'meta';
/// The set of active metadata contracts associated with this facet.
final Set<MetaContract> contracts;
MetaContractFacet(Iterable<MetaContract> contracts)
: contracts = contracts.isEmpty
? const {}
: Set.unmodifiable(contracts.sorted());
/// Extracts active `package:meta` contracts from [element], or returns `null`
/// if none are present.
static MetaContractFacet? fromElement(Element element) {
final contracts = <MetaContract>{};
void check(Metadata metadata) {
if (metadata.hasExperimental) contracts.add(MetaContract.experimental);
if (metadata.hasImmutable) contracts.add(MetaContract.immutable);
if (metadata.hasInternal) contracts.add(MetaContract.internal);
if (metadata.hasMustBeOverridden) {
contracts.add(MetaContract.mustBeOverridden);
}
if (metadata.hasMustCallSuper) contracts.add(MetaContract.mustCallSuper);
if (metadata.hasNonVirtual) contracts.add(MetaContract.nonVirtual);
if (metadata.hasProtected) contracts.add(MetaContract.protected);
if (metadata.hasUseResult) contracts.add(MetaContract.useResult);
if (metadata.hasVisibleForOverriding) {
contracts.add(MetaContract.visibleForOverriding);
}
if (metadata.hasVisibleForTesting) {
contracts.add(MetaContract.visibleForTesting);
}
}
check(element.nonSynthetic.metadata);
if (element is PropertyAccessorElement) {
check(element.variable.nonSynthetic.metadata);
}
return contracts.isNotEmpty ? MetaContractFacet(contracts) : null;
}
factory MetaContractFacet.fromJson(Map<String, dynamic> json) {
final list = json['contracts'] as List<dynamic>? ?? const [];
final set = <MetaContract>{};
for (final item in list) {
if (item is String) {
final contract = MetaContract.values
.where((e) => e.name == item)
.firstOrNull;
if (contract != null) {
set.add(contract);
}
}
}
return MetaContractFacet(set);
}
@override
Map<String, dynamic> toJson() => {
'namespace': namespace,
'contracts': contracts.map((e) => e.name).toList(),
};
@override
List<String> get parentheticalSegments =>
contracts.map((e) => e.textLabel).toList();
@override
int compareTo(ApiFacet other) {
final diff = namespace.compareTo(other.namespace);
if (diff != 0) return diff;
if (other is MetaContractFacet) {
final thisStr = contracts.map((e) => e.name).join(',');
final otherStr = other.contracts.map((e) => e.name).join(',');
return thisStr.compareTo(otherStr);
}
return 0;
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MetaContractFacet &&
const SetEquality<MetaContract>().equals(contracts, other.contracts);
@override
int get hashCode =>
Object.hash(namespace, const SetEquality<MetaContract>().hash(contracts));
}