blob: ffb4ac762a96f7cc23844c83d9764ce4409b5116 [file] [log] [blame]
// Copyright (c) 2019, 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.
import 'package:analyzer/dart/element/element.dart';
import 'package:meta/meta.dart';
/// Indirection between a name and the corresponding [Element].
///
/// References are organized in a prefix tree.
/// Each reference knows its parent, children, and the [Element].
///
/// Library:
/// URI of library
///
/// Class:
/// Reference of the enclosing library
/// "@class"
/// Name of the class
///
/// Method:
/// Reference of the enclosing class
/// "@method"
/// Name of the method
///
/// There is only one reference object per [Element].
class Reference {
/// The parent of this reference, or `null` if the root.
final Reference? parent;
/// The simple name of the reference in its [parent].
final String name;
/// The corresponding [Element], or `null` if a named container.
Element? element;
/// Temporary index used during serialization and linking.
int? index;
// null, Reference or Map<String, Reference>.
Object? _childrenUnion;
Reference.root() : this._(null, '');
Reference._(this.parent, this.name);
Iterable<Reference> get children {
final childrenUnion = _childrenUnion;
if (childrenUnion == null) return const [];
if (childrenUnion is Reference) return [childrenUnion];
return (childrenUnion as Map<String, Reference>).values;
}
@visibleForTesting
Object? get childrenUnionForTesting => _childrenUnion;
bool get isLibrary => parent?.isRoot == true;
bool get isPrefix => parent?.name == '@prefix';
bool get isRoot => parent == null;
bool get isSetter => parent?.name == '@setter';
/// Return the child with the given name, or `null` if does not exist.
Reference? operator [](String name) {
name = _rewriteDartUi(name);
final childrenUnion = _childrenUnion;
if (childrenUnion == null) return null;
if (childrenUnion is Reference) {
if (childrenUnion.name == name) return childrenUnion;
return null;
}
return (childrenUnion as Map<String, Reference>)[name];
}
/// Return the child with the given name, create if does not exist yet.
Reference getChild(String name) {
name = _rewriteDartUi(name);
final childrenUnion = _childrenUnion;
if (childrenUnion == null) {
// 0 -> 1 children.
return _childrenUnion = Reference._(this, name);
}
if (childrenUnion is Reference) {
if (childrenUnion.name == name) return childrenUnion;
// 1 -> 2 children.
final childrenUnionAsMap = _childrenUnion = <String, Reference>{};
childrenUnionAsMap[childrenUnion.name] = childrenUnion;
return childrenUnionAsMap[name] = Reference._(this, name);
}
return (childrenUnion as Map<String, Reference>)[name] ??=
Reference._(this, name);
}
Reference? removeChild(String name) {
name = _rewriteDartUi(name);
final childrenUnion = _childrenUnion;
if (childrenUnion == null) return null;
if (childrenUnion is Reference) {
if (childrenUnion.name == name) {
// 1 -> 0 children.
_childrenUnion = null;
return childrenUnion;
}
return null;
}
final childrenUnionAsMap = childrenUnion as Map<String, Reference>;
final result = childrenUnionAsMap.remove(name);
if (childrenUnionAsMap.length == 1) {
// 2 -> 1 children.
_childrenUnion = childrenUnionAsMap.values.single;
}
return result;
}
@override
String toString() => parent == null ? 'root' : '$parent::$name';
/// TODO(scheglov) Remove it, once when the actual issue is fixed.
/// https://buganizer.corp.google.com/issues/203423390
static String _rewriteDartUi(String name) {
const srcPrefix = 'dart:ui/src/ui/';
if (name.startsWith(srcPrefix)) {
return 'dart:ui/${name.substring(srcPrefix.length)}';
}
return name;
}
}