- Add VariableSpec class with magnitude field validation - Add Formula class supporting multiple input/output variables - Support d4rt_code as string or object with code field - Add comprehensive tests for parsing and serialization - Fix broken test import in pruebas_d4rt_test.dart Follows README.md format requirements exactly
693 lines
25 KiB
Dart
693 lines
25 KiB
Dart
// Copyright (c) 2025, 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.
|
|
|
|
/// Generates the file `api.txt`, which describes the analyzer public API.
|
|
library;
|
|
|
|
import 'dart:collection';
|
|
|
|
import 'package:analyzer/dart/analysis/analysis_context.dart';
|
|
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
|
|
import 'package:analyzer/dart/analysis/results.dart';
|
|
import 'package:analyzer/dart/constant/value.dart';
|
|
import 'package:analyzer/dart/element/element2.dart';
|
|
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
|
import 'package:analyzer/dart/element/type.dart';
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
import 'package:analyzer/src/utilities/extensions/element.dart';
|
|
import 'package:analyzer_utilities/package_root.dart' as pkg_root;
|
|
import 'package:analyzer_utilities/package_root.dart';
|
|
import 'package:analyzer_utilities/tools.dart';
|
|
import 'package:collection/collection.dart';
|
|
import 'package:path/path.dart';
|
|
|
|
Future<void> main() async {
|
|
await GeneratedContent.generateAll(analyzerPkgPath, allTargets);
|
|
}
|
|
|
|
/// A list of all targets generated by this code generator.
|
|
final List<GeneratedContent> allTargets = [
|
|
GeneratedFile('api.txt', computeApiTxtContents)
|
|
];
|
|
|
|
/// The path to the `analyzer` package.
|
|
final String analyzerPkgPath =
|
|
normalize(join(pkg_root.packageRoot, 'analyzer'));
|
|
|
|
/// Computes what should be written to the `api.txt` file.
|
|
Future<String> computeApiTxtContents(String pkgPath) async {
|
|
var provider = PhysicalResourceProvider.INSTANCE;
|
|
var pathContext = provider.pathContext;
|
|
var pkgRootPath = provider.pathContext.normalize(packageRoot);
|
|
var packagePath = pathContext.join(pkgRootPath, 'analyzer');
|
|
var collection = AnalysisContextCollection(
|
|
includedPaths: [packagePath], resourceProvider: provider);
|
|
var context = collection.contexts.single;
|
|
var publicApi = ApiDescription();
|
|
var stringBuffer = StringBuffer();
|
|
printNodes(stringBuffer, await publicApi.build(context));
|
|
return stringBuffer.toString();
|
|
}
|
|
|
|
/// Outputs the contents of [nodes] to [sink], prepending [prefix] to every
|
|
/// line.
|
|
void printNodes<SortKey extends Comparable<SortKey>>(
|
|
StringSink sink, List<(SortKey, Node)> nodes,
|
|
{String prefix = ''}) {
|
|
for (var entry in nodes.sortedBy((n) => n.$1)) {
|
|
var node = entry.$2;
|
|
sink.writeln('$prefix${node.text.join()}');
|
|
node.printChildren(sink, prefix: '$prefix ');
|
|
}
|
|
}
|
|
|
|
/// Data structure keeping track of the analyzer API while walking it to produce
|
|
/// `api.txt`.
|
|
class ApiDescription {
|
|
/// Top level elements that have already had their child elements dumped.
|
|
///
|
|
/// If an element is seen again in a different library, it will be followed
|
|
/// with `(see above)` (rather than having its child elements dumped twice).
|
|
final _dumpedTopLevelElements = <Element2>{};
|
|
|
|
/// Top level elements that have been referenced so far and haven't yet been
|
|
/// processed by [build].
|
|
///
|
|
/// This is used to ensure that all elements referred to by the public API
|
|
/// (e.g., by being mentioned in the type of an API element) also show up in
|
|
/// the output.
|
|
final _potentiallyDanglingReferences = Queue<Element2>();
|
|
|
|
final _uniqueNamer = UniqueNamer();
|
|
|
|
/// Cache of values returned by [_getOrComputeImmediateSubinterfaceMap], to
|
|
/// avoid unnecessary recomputation.
|
|
final _immediateSubinterfaceCache =
|
|
<LibraryElement2, Map<ClassElement2, Set<InterfaceElement2>>>{};
|
|
|
|
/// Builds a list of [Node] objects representing all the libraries that are
|
|
/// relevant to the analyzer public API.
|
|
///
|
|
/// This includes libraries that are in the analyzer public API as well as
|
|
/// libraries that are referenced by the analyzer public API (either by being
|
|
/// re-exported as part of the analyzer public API, or by being used as part
|
|
/// of the type of something in the public API).
|
|
///
|
|
/// Each library node is pared with a [UriSortKey] indicating the order in
|
|
/// which the nodes should be output.
|
|
Future<List<(UriSortKey, Node)>> build(AnalysisContext context) async {
|
|
var nodes = <Uri, Node<MemberSortKey>>{};
|
|
for (var file in context.contextRoot.analyzedFiles().sorted()) {
|
|
if (!file.endsWith('.dart')) continue;
|
|
var fileResult = context.currentSession.getFile(file) as FileResult;
|
|
var uri = fileResult.uri;
|
|
if (fileResult.isLibrary && uri.isInAnalyzerPublicLib) {
|
|
var resolvedLibraryResult = (await context.currentSession
|
|
.getResolvedLibrary(file)) as ResolvedLibraryResult;
|
|
var node = nodes[uri] = Node<MemberSortKey>();
|
|
_dumpLibrary(resolvedLibraryResult.element2, node);
|
|
}
|
|
}
|
|
// Then dump anything referenced by public libraries.
|
|
while (_potentiallyDanglingReferences.isNotEmpty) {
|
|
var element = _potentiallyDanglingReferences.removeFirst();
|
|
if (!_dumpedTopLevelElements.add(element)) continue;
|
|
var containingLibraryUri = element.library2!.uri;
|
|
var childNode = Node<MemberSortKey>()
|
|
..text.add(_uniqueNamer.name(element));
|
|
_dumpElement(element, childNode);
|
|
(nodes[containingLibraryUri] ??= Node<MemberSortKey>()
|
|
..text.add('$containingLibraryUri:'))
|
|
.childNodes
|
|
.add((MemberSortKey(element), childNode));
|
|
}
|
|
return [
|
|
for (var entry in nodes.entries) (UriSortKey(entry.key), entry.value)
|
|
];
|
|
}
|
|
|
|
/// Creates a list of objects which, when their string representations are
|
|
/// concatenated, describes [type].
|
|
///
|
|
/// The reason we use this method rather than [DartType.toString] is to make
|
|
/// sure that (a) every element mentioned by the type is added to
|
|
/// [_potentiallyDanglingReferences], and (b) if an ambiguous name is used,
|
|
/// the ambiguity will be taken care of by [_uniqueNamer].
|
|
List<Object?> _describeType(DartType type) {
|
|
var suffix = switch (type.nullabilitySuffix) {
|
|
NullabilitySuffix.none => '',
|
|
NullabilitySuffix.star => '*',
|
|
NullabilitySuffix.question => '?'
|
|
};
|
|
switch (type) {
|
|
case DynamicType():
|
|
return ['dynamic'];
|
|
case FunctionType(
|
|
:var returnType,
|
|
:var typeParameters,
|
|
:var formalParameters
|
|
):
|
|
var params = <List<Object?>>[];
|
|
var optionalParams = <List<Object?>>[];
|
|
var namedParams = <String, List<Object?>>{};
|
|
for (var formalParameter in formalParameters) {
|
|
if (formalParameter.isNamed) {
|
|
namedParams[formalParameter.name3!] = [
|
|
if (formalParameter.isRequired) 'required ',
|
|
..._describeType(formalParameter.type)
|
|
];
|
|
} else if (formalParameter.isOptional) {
|
|
optionalParams.add(_describeType(formalParameter.type));
|
|
} else {
|
|
params.add(_describeType(formalParameter.type));
|
|
}
|
|
}
|
|
if (optionalParams.isNotEmpty) {
|
|
params.add(optionalParams.separate(prefix: '[', suffix: ']'));
|
|
}
|
|
if (namedParams.isNotEmpty) {
|
|
params.add(namedParams.entries
|
|
.sortedBy((e) => e.key)
|
|
.map((e) => [...e.value, ' ${e.key}'])
|
|
.separate(prefix: '{', suffix: '}'));
|
|
}
|
|
return <Object?>[
|
|
..._describeType(returnType),
|
|
' Function',
|
|
if (typeParameters.isNotEmpty)
|
|
...typeParameters
|
|
.map(_describeTypeParameter)
|
|
.separate(prefix: '<', suffix: '>'),
|
|
'(',
|
|
...params.separate(),
|
|
')',
|
|
suffix
|
|
];
|
|
case InterfaceType(:var element3, :var typeArguments):
|
|
_potentiallyDanglingReferences.addLast(element3);
|
|
return [
|
|
_uniqueNamer.name(element3),
|
|
if (typeArguments.isNotEmpty)
|
|
...typeArguments
|
|
.map(_describeType)
|
|
.separate(prefix: '<', suffix: '>'),
|
|
suffix
|
|
];
|
|
case RecordType(:var positionalFields, :var namedFields):
|
|
if (positionalFields.length == 1 && namedFields.isEmpty) {
|
|
return [
|
|
'(',
|
|
..._describeType(positionalFields[0].type),
|
|
',)',
|
|
suffix
|
|
];
|
|
}
|
|
return [
|
|
...[
|
|
for (var positionalField in positionalFields)
|
|
_describeType(positionalField.type),
|
|
if (namedFields.isNotEmpty)
|
|
namedFields
|
|
.sortedBy((f) => f.name)
|
|
.map((f) => [..._describeType(f.type), ' ', f.name])
|
|
.separate(prefix: '{', suffix: '}')
|
|
].separate(prefix: '(', suffix: ')'),
|
|
suffix
|
|
];
|
|
case TypeParameterType(:var element3):
|
|
return [element3.name3!, suffix];
|
|
case VoidType():
|
|
return ['void'];
|
|
case dynamic(:var runtimeType):
|
|
throw UnimplementedError('Unexpected type: $runtimeType');
|
|
}
|
|
}
|
|
|
|
/// Creates a list of objects which, when their string representations are
|
|
/// concatenated, describes [typeParameter].
|
|
List<Object?> _describeTypeParameter(TypeParameterElement2 typeParameter) {
|
|
return [
|
|
typeParameter.name3!,
|
|
if (typeParameter.bound case var bound?) ...[
|
|
' extends ',
|
|
..._describeType(bound)
|
|
]
|
|
];
|
|
}
|
|
|
|
/// Appends information to [node] describing [element].
|
|
void _dumpElement(Element2 element, Node<MemberSortKey> node) {
|
|
var enclosingElement = element.enclosingElement2;
|
|
if (enclosingElement is LibraryElement2 && !element.isInAnalyzerPublicApi) {
|
|
if (!enclosingElement.uri.isInAnalyzer) {
|
|
node.text.add(' (referenced)');
|
|
} else {
|
|
node.text.add(' (non-public)');
|
|
}
|
|
return;
|
|
}
|
|
var parentheticals = <List<Object?>>[];
|
|
switch (element) {
|
|
case InstanceElement2():
|
|
switch (element) {
|
|
case InterfaceElement2(
|
|
:var typeParameters2,
|
|
:var supertype,
|
|
:var interfaces
|
|
):
|
|
List<Object?> instanceDescription = [
|
|
switch (element) {
|
|
ClassElement2() => 'class',
|
|
EnumElement2() => 'enum',
|
|
MixinElement2() => 'mixin',
|
|
dynamic(:var runtimeType) => 'TODO: $runtimeType',
|
|
}
|
|
];
|
|
if (typeParameters2.isNotEmpty) {
|
|
instanceDescription.addAll(typeParameters2
|
|
.map(_describeTypeParameter)
|
|
.separate(prefix: '<', suffix: '>'));
|
|
}
|
|
if (element is! EnumElement2 && supertype != null) {
|
|
instanceDescription
|
|
.addAll([' extends ', ..._describeType(supertype)]);
|
|
}
|
|
if (element is MixinElement2 &&
|
|
element.superclassConstraints.isNotEmpty) {
|
|
instanceDescription.addAll(element.superclassConstraints
|
|
.map(_describeType)
|
|
.separate(prefix: ' on '));
|
|
}
|
|
if (interfaces.isNotEmpty) {
|
|
instanceDescription.addAll(interfaces
|
|
.map(_describeType)
|
|
.separate(prefix: ' implements '));
|
|
}
|
|
parentheticals.add(instanceDescription);
|
|
if (element is ClassElement2 && element.isSealed) {
|
|
var parenthetical = <Object>['sealed'];
|
|
parentheticals.add(parenthetical);
|
|
if (_getOrComputeImmediateSubinterfaceMap(
|
|
element.library2)[element]
|
|
case var subinterfaces?) {
|
|
parenthetical.add(' (immediate subtypes: ');
|
|
// Note: it's tempting to just do
|
|
// `subinterfaces.map(_uniqueNamer.name).join(', ')`, but that
|
|
// won't work, because the names returned by
|
|
// `UniqueName.toString()` aren't finalized until we've visited
|
|
// the entire API and seen if there are class names that need to
|
|
// be disambiguated. So we accumulate the `UniqueName` objects
|
|
// into the `parenthetical` list and rely on `printNodes`
|
|
// converting everything to a string when the final API
|
|
// description is being output.
|
|
var commaNeeded = false;
|
|
for (var subinterface in subinterfaces) {
|
|
if (commaNeeded) {
|
|
parenthetical.add(', ');
|
|
} else {
|
|
commaNeeded = true;
|
|
}
|
|
parenthetical.add(_uniqueNamer.name(subinterface));
|
|
}
|
|
parenthetical.add(')');
|
|
}
|
|
}
|
|
case ExtensionElement2(:var extendedType):
|
|
parentheticals
|
|
.add(['extension on ', ..._describeType(extendedType)]);
|
|
case dynamic(:var runtimeType):
|
|
throw UnimplementedError('Unexpected element: $runtimeType');
|
|
}
|
|
for (var member in element.children2.sortedBy((m) => m.name3 ?? '')) {
|
|
if (member.name3 case var name? when name.startsWith('_')) {
|
|
// Ignore private members
|
|
continue;
|
|
}
|
|
if (member is FieldElement2) {
|
|
// Ignore fields; we care about the getters and setters they induce.
|
|
continue;
|
|
}
|
|
if (member is ConstructorElement2 &&
|
|
element is ClassElement2 &&
|
|
element.isAbstract &&
|
|
(element.isFinal || element.isInterface || element.isSealed)) {
|
|
// The class can't be constructed from outside of the library that
|
|
// declares it, so its constructors aren't part of the public API.
|
|
continue;
|
|
}
|
|
if (member is ConstructorElement2 && element is EnumElement2) {
|
|
// Enum constructors can't be called from outside the enum itself,
|
|
// so they aren't part of the public API.
|
|
continue;
|
|
}
|
|
var childNode = Node<MemberSortKey>();
|
|
childNode.text.add(member.apiName);
|
|
_dumpElement(member, childNode);
|
|
node.childNodes.add((MemberSortKey(member), childNode));
|
|
}
|
|
case TopLevelFunctionElement(:var type):
|
|
parentheticals.add(['function: ', ..._describeType(type)]);
|
|
case ExecutableElement2(:var isStatic):
|
|
String maybeStatic = isStatic ? 'static ' : '';
|
|
switch (element) {
|
|
case GetterElement(:var type):
|
|
parentheticals.add(
|
|
['${maybeStatic}getter: ', ..._describeType(type.returnType)]);
|
|
case SetterElement(:var type):
|
|
parentheticals.add([
|
|
'${maybeStatic}setter: ',
|
|
..._describeType(type.formalParameters.single.type)
|
|
]);
|
|
case MethodElement2(:var type):
|
|
parentheticals
|
|
.add(['${maybeStatic}method: ', ..._describeType(type)]);
|
|
case ConstructorElement2(:var type):
|
|
parentheticals.add(['constructor: ', ..._describeType(type)]);
|
|
case dynamic(:var runtimeType):
|
|
throw UnimplementedError('Unexpected element: $runtimeType');
|
|
}
|
|
case dynamic(:var runtimeType):
|
|
throw UnimplementedError('Unexpected element: $runtimeType');
|
|
}
|
|
|
|
if (element.metadata.any(_isDeprecated)) {
|
|
parentheticals.add(['deprecated']);
|
|
}
|
|
if (element.metadata.any(_isExperimental)) {
|
|
parentheticals.add(['experimental']);
|
|
}
|
|
|
|
if (parentheticals.isNotEmpty) {
|
|
node.text.addAll(parentheticals.separate(prefix: ' (', suffix: ')'));
|
|
}
|
|
if (node.childNodes.isNotEmpty) {
|
|
node.text.add(':');
|
|
}
|
|
}
|
|
|
|
/// Appends information to [node] describing [element].
|
|
void _dumpLibrary(LibraryElement2 library, Node<MemberSortKey> node) {
|
|
var uri = library.uri;
|
|
node.text.addAll([uri, ':']);
|
|
var definedNames = library.exportNamespace.definedNames2;
|
|
for (var key in definedNames.keys.sorted()) {
|
|
var element = definedNames[key]!;
|
|
var childNode = Node<MemberSortKey>()
|
|
..text.add(_uniqueNamer.name(element));
|
|
if (!_dumpedTopLevelElements.add(element)) {
|
|
childNode.text.add(' (see above)');
|
|
} else {
|
|
_dumpElement(element, childNode);
|
|
}
|
|
node.childNodes.add((MemberSortKey(element), childNode));
|
|
}
|
|
}
|
|
|
|
/// Returns a map from each sealed class in [library] to the set of its
|
|
/// immediate sub-interfaces.
|
|
///
|
|
/// If this method has been called before with the same [library], a cached
|
|
/// map is returned from [_immediateSubinterfaceCache]. Otherwise a fresh map
|
|
/// is computed.
|
|
Map<ClassElement2, Set<InterfaceElement2>>
|
|
_getOrComputeImmediateSubinterfaceMap(LibraryElement2 library) {
|
|
if (_immediateSubinterfaceCache[library] case var m?) return m;
|
|
var result = <ClassElement2, Set<InterfaceElement2>>{};
|
|
for (var interface in [
|
|
...library.classes,
|
|
...library.mixins,
|
|
...library.enums,
|
|
...library.extensionTypes
|
|
]..sortBy((e) => e.name3!)) {
|
|
for (var superinterface in [
|
|
interface.supertype,
|
|
...interface.interfaces,
|
|
...interface.mixins,
|
|
if (interface is MixinElement2) ...interface.superclassConstraints
|
|
]) {
|
|
if (superinterface == null) continue;
|
|
var superinterfaceElement = superinterface.element3;
|
|
if (superinterfaceElement is ClassElement2 &&
|
|
superinterfaceElement.isSealed) {
|
|
(result[superinterfaceElement] ??= {}).add(interface);
|
|
}
|
|
}
|
|
}
|
|
_immediateSubinterfaceCache[library] = result;
|
|
return result;
|
|
}
|
|
|
|
bool _isDeprecated(ElementAnnotation annotation) {
|
|
if (annotation.computeConstantValue()!.type
|
|
case InterfaceType(:var element3) when element3.name3 == 'Deprecated') {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool _isExperimental(ElementAnnotation annotation) {
|
|
if (annotation.computeConstantValue()!.type
|
|
case InterfaceType(:var element3)
|
|
when element3.name3 == '_Experimental') {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Element categorization used by [MemberSortKey].
|
|
enum MemberCategory {
|
|
constructor,
|
|
propertyAccessor,
|
|
topLevelFunctionOrMethod,
|
|
interface,
|
|
extension
|
|
}
|
|
|
|
/// Sort key used to sort elements in the output.
|
|
class MemberSortKey implements Comparable<MemberSortKey> {
|
|
final bool _isInstanceMember;
|
|
final MemberCategory _category;
|
|
final String _name;
|
|
|
|
MemberSortKey(Element2 element)
|
|
: _isInstanceMember = _computeIsInstanceMember(element),
|
|
_category = _computeCategory(element),
|
|
_name = element.name3!;
|
|
|
|
@override
|
|
int compareTo(MemberSortKey other) {
|
|
if ((_isInstanceMember ? 1 : 0).compareTo(other._isInstanceMember ? 1 : 0)
|
|
case var value when value != 0) {
|
|
return value;
|
|
}
|
|
if (_category.index.compareTo(other._category.index) case var value
|
|
when value != 0) {
|
|
return value;
|
|
}
|
|
return _name.compareTo(other._name);
|
|
}
|
|
|
|
static MemberCategory _computeCategory(Element2 element) => switch (element) {
|
|
ConstructorElement2() => MemberCategory.constructor,
|
|
PropertyAccessorElement2() => MemberCategory.propertyAccessor,
|
|
TopLevelFunctionElement() => MemberCategory.topLevelFunctionOrMethod,
|
|
MethodElement2() => MemberCategory.topLevelFunctionOrMethod,
|
|
InterfaceElement2() => MemberCategory.interface,
|
|
ExtensionElement2() => MemberCategory.extension,
|
|
dynamic(:var runtimeType) =>
|
|
throw UnimplementedError('Unexpected element: $runtimeType')
|
|
};
|
|
|
|
static bool _computeIsInstanceMember(Element2 element) =>
|
|
element.enclosingElement2 is InstanceElement2 &&
|
|
switch (element) {
|
|
ExecutableElement2(:var isStatic) => !isStatic,
|
|
dynamic(:var runtimeType) =>
|
|
throw UnimplementedError('Unexpected element: $runtimeType')
|
|
};
|
|
}
|
|
|
|
/// A node to be printed to the output.
|
|
class Node<ChildSortKey extends Comparable<ChildSortKey>> {
|
|
/// A list of objects which, when their string representations are
|
|
/// concatenated, is the text that should be displayed on the first line of
|
|
/// the node.
|
|
///
|
|
/// The reason this is a list rather than a single string is to allow elements
|
|
/// of the list to be [UniqueName] objects, which may acquire a disambiguation
|
|
/// suffix at a later time.
|
|
final text = <Object?>[];
|
|
|
|
/// A list of child nodes, paired with a sort key indicating the order in
|
|
/// which they should be output.
|
|
final childNodes = <(ChildSortKey, Node)>[];
|
|
|
|
/// Outputs [childNodes], prepending [prefix] to every line.
|
|
void printChildren(StringSink sink, {required String prefix}) {
|
|
printNodes(sink, childNodes, prefix: prefix);
|
|
}
|
|
}
|
|
|
|
/// Object that will have a unique string representation within the context of a
|
|
/// given [UniqueNamer] instance.
|
|
///
|
|
/// If two or more [UniqueName] objects are constructed with reference to the
|
|
/// same [UniqueNamer], and they have the same [_nameHint], then all such
|
|
/// objects' [toString] methods will append a unique suffix of the form
|
|
/// `@INTEGER`, so that the resulting strings are unique.
|
|
class UniqueName {
|
|
/// The name that will be returned by [toString] if no disambiguation is
|
|
/// needed.
|
|
final String _nameHint;
|
|
|
|
/// If not `Null`, the integer that [toString] will use to disambiguate this
|
|
/// [UniqueName] from other ones with the same [_nameHint].
|
|
int? _disambiguator;
|
|
|
|
UniqueName(UniqueNamer uniqueNamer, this._nameHint) {
|
|
// The uniqueness guarantee depends on `_nameHint` not containing an `@`.
|
|
assert(!_nameHint.contains('@'));
|
|
var conflicts = uniqueNamer._conflicts[_nameHint] ??= [];
|
|
if (conflicts.length == 1) {
|
|
conflicts[0]._disambiguator = 1;
|
|
}
|
|
conflicts.add(this);
|
|
if (conflicts.length > 1) {
|
|
_disambiguator = conflicts.length;
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() => [
|
|
_nameHint,
|
|
if (_disambiguator case var disambiguator?) '@$disambiguator'
|
|
].join();
|
|
}
|
|
|
|
/// Manager of unique names for elements.
|
|
class UniqueNamer {
|
|
final _names = <Element2, UniqueName>{};
|
|
final _conflicts = <String, List<UniqueName>>{};
|
|
|
|
/// Returns a [UniqueName] object whose [toString] method will produce a
|
|
/// unique name for [element].
|
|
UniqueName name(Element2 element) =>
|
|
_names[element] ??= UniqueName(this, element.apiName);
|
|
}
|
|
|
|
/// URI categorization used by [UriSortKey].
|
|
enum UriCategory { inAnalyzer, notInAnalyzer }
|
|
|
|
/// Sort key used to sort libraries in the output.
|
|
///
|
|
/// Libraries in `package:analyzer` will be output first (sorted by URI),
|
|
/// followed by libraries not in `package:analyzer`.
|
|
class UriSortKey implements Comparable<UriSortKey> {
|
|
final UriCategory _category;
|
|
final String _uriString;
|
|
|
|
UriSortKey(Uri uri)
|
|
: _category = uri.isInAnalyzer
|
|
? UriCategory.inAnalyzer
|
|
: UriCategory.notInAnalyzer,
|
|
_uriString = uri.toString();
|
|
|
|
@override
|
|
int compareTo(UriSortKey other) {
|
|
if (_category.index.compareTo(other._category.index) case var value
|
|
when value != 0) {
|
|
return value;
|
|
}
|
|
return _uriString.compareTo(other._uriString);
|
|
}
|
|
}
|
|
|
|
extension on Iterable<Iterable<Object?>> {
|
|
/// Forms a list containing [prefix], followed by the elements of `this`
|
|
/// (separated by [separator]), followed by [suffix].
|
|
List<Object?> separate(
|
|
{String separator = ', ', String prefix = '', String suffix = ''}) {
|
|
var result = <Object?>[prefix];
|
|
var first = true;
|
|
for (var item in this) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
result.add(separator);
|
|
}
|
|
result.addAll(item);
|
|
}
|
|
result.add(suffix);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
extension on Element2 {
|
|
/// Returns the appropriate name for describing the element in `api.txt`.
|
|
///
|
|
/// The name is the same as [name3], but with `=` appended for setters.
|
|
String get apiName {
|
|
var apiName = name3!;
|
|
if (this is SetterElement) {
|
|
apiName += '=';
|
|
}
|
|
return apiName;
|
|
}
|
|
|
|
bool get isInAnalyzerPublicApi {
|
|
if (this
|
|
case PropertyAccessorElement2(
|
|
isSynthetic: true,
|
|
:var variable3?,
|
|
) when variable3.isInAnalyzerPublicApi) {
|
|
return true;
|
|
}
|
|
if (this
|
|
case Annotatable(
|
|
metadata2: Metadata(:var annotations),
|
|
) when annotations.any(_isPublicApiAnnotation)) {
|
|
return true;
|
|
}
|
|
if (name3 case var name? when !name.isPublic) return false;
|
|
if (library2!.uri.isInAnalyzerPublicLib) return true;
|
|
return false;
|
|
}
|
|
|
|
bool _isPublicApiAnnotation(ElementAnnotation annotation) {
|
|
if (annotation.computeConstantValue()
|
|
case DartObject(
|
|
type: InterfaceType(
|
|
element3: InterfaceElement2(name3: 'AnalyzerPublicApi'),
|
|
),
|
|
)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
extension on String {
|
|
bool get isPublic => !startsWith('_');
|
|
}
|
|
|
|
extension on Uri {
|
|
bool get isInAnalyzer =>
|
|
scheme == 'package' &&
|
|
switch (pathSegments) {
|
|
['analyzer', ...] => true,
|
|
_ => false,
|
|
};
|
|
|
|
bool get isInAnalyzerPublicLib =>
|
|
scheme == 'package' &&
|
|
switch (pathSegments) {
|
|
['analyzer', 'src', ...] => false,
|
|
['analyzer', ...] => true,
|
|
_ => false,
|
|
};
|
|
}
|