| // 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/src/dart/element/element.dart'; |
| import 'package:meta/meta.dart'; |
| |
| /// Indirection between a name and the corresponding [ElementImpl]. |
| /// |
| /// References are organized in a prefix tree. |
| /// Each reference knows its parent, children, and the [ElementImpl]. |
| /// |
| /// 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 [ElementImpl]. |
| class Reference { |
| /// The name of the container used for duplicate declarations. |
| static const _defName = '@def'; |
| |
| /// The parent of this reference, or `null` if the root. |
| Reference? parent; |
| |
| /// The simple name of the reference in its [parent]. |
| String name; |
| |
| /// The corresponding [ElementImpl], or `null` if a named container. |
| ElementImpl? 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 { |
| var 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; |
| |
| /// The name of the element that this reference represents. |
| /// |
| /// Normally, this is [name]. But in case of duplicate declarations, such |
| /// as augmentations (which is allowed by the specification), or invalid |
| /// code, the actual name is the name of the parent of the duplicates |
| /// container `@def`. |
| String get elementName { |
| if (parent?.name == _defName) { |
| return parent!.parent!.name; |
| } |
| return name; |
| } |
| |
| bool get isLibrary => parent?.isRoot == true; |
| |
| bool get isPrefix => parent?.name == '@prefix'; |
| |
| bool get isRoot => parent == null; |
| |
| bool get isSetter => parent?.name == '@setter'; |
| |
| /// The parent that is not a container like `@method`. |
| /// |
| /// Usually this is the parent of the parent. |
| /// @class::A::@method::foo -> @class::A |
| /// |
| /// But if this is a duplicates, we go two more levels up. |
| /// @class::A::@method::foo::@def::0 -> @class::A |
| Reference get parentNotContainer { |
| // Should be `@method`, `@constructor`, etc. |
| var containerInParent = parent!; |
| |
| // Skip the duplicates container. |
| if (containerInParent.name == _defName) { |
| containerInParent = containerInParent.parent!.parent!; |
| } |
| |
| return containerInParent.parent!; |
| } |
| |
| /// Return the child with the given name, or `null` if does not exist. |
| Reference? operator [](String name) { |
| var 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]; |
| } |
| |
| /// Adds a new child with the given [name]. |
| /// |
| /// This method should be used when a new declaration of an element with |
| /// this name is processed. If there is no existing child with this name, |
| /// this method works exactly as [getChild]. If there is a duplicate, which |
| /// should happen rarely, an intermediate `@def` container is added, the |
| /// existing child is transferred to it and renamed to `0`, then a new child |
| /// is added with name `1`. Additional duplicate children get names `2`, etc. |
| Reference addChild(String name) { |
| var child = Reference._(null, name); |
| addChildReference(name, child); |
| return child; |
| } |
| |
| /// Transfers [child] to this parent. |
| void addChildReference(String name, Reference child) { |
| child.parent = this; |
| |
| var existing = this[name]; |
| |
| // If not a duplicate. |
| if (existing == null) { |
| _addChild(name, child); |
| return; |
| } |
| |
| var def = existing[_defName]; |
| |
| // If no duplicates container yet. |
| if (def == null) { |
| removeChild(name); // existing |
| def = getChild(name).getChild(_defName); |
| existing.parent = def; |
| existing.name = '0'; |
| def._addChild(existing.name, existing); |
| } |
| |
| // Add a new child to the duplicates container. |
| child.parent = def; |
| child.name = '${def.children.length}'; |
| def._addChild(child.name, child); |
| } |
| |
| /// Return the child with the given name, create if does not exist yet. |
| Reference getChild(String name) { |
| var 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. |
| var childrenUnionAsMap = _childrenUnion = <String, Reference>{}; |
| childrenUnionAsMap[childrenUnion.name] = childrenUnion; |
| return childrenUnionAsMap[name] = Reference._(this, name); |
| } |
| return (childrenUnion as Map<String, Reference>)[name] ??= Reference._( |
| this, |
| name, |
| ); |
| } |
| |
| /// Returns children with the given name. |
| /// Usually returns zero or one child. |
| /// But in case of duplicates will return more than one. |
| List<Reference> getChildrenByName(String name) { |
| var result = this[name]; |
| |
| // No such child yet. |
| if (result == null) { |
| return const []; |
| } |
| |
| // Maybe has the container with duplicates. |
| if (result[_defName] case var defContainer?) { |
| return defContainer.children.toList(); |
| } |
| |
| // Should be the only child with such name. |
| return [result]; |
| } |
| |
| Reference? removeChild(String name) { |
| var childrenUnion = _childrenUnion; |
| if (childrenUnion == null) return null; |
| if (childrenUnion is Reference) { |
| if (childrenUnion.name == name) { |
| // 1 -> 0 children. |
| _childrenUnion = null; |
| return childrenUnion; |
| } |
| return null; |
| } |
| var childrenUnionAsMap = childrenUnion as Map<String, Reference>; |
| var 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'; |
| |
| void _addChild(String name, Reference child) { |
| var childrenUnion = _childrenUnion; |
| if (childrenUnion == null) { |
| // 0 -> 1 children. |
| _childrenUnion = child; |
| return; |
| } |
| if (childrenUnion is Reference) { |
| // 1 -> 2 children. |
| var childrenUnionAsMap = _childrenUnion = <String, Reference>{}; |
| childrenUnionAsMap[childrenUnion.name] = childrenUnion; |
| childrenUnionAsMap[name] = child; |
| return; |
| } |
| (childrenUnion as Map<String, Reference>)[name] ??= child; |
| } |
| } |