blob: 4bc5a76b830fafffce2d7877c2fec138cf8e242d [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 'dart:convert';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/source/line_info.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember;
import 'package:collection/collection.dart' show IterableExtension;
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/comment_referable.dart';
import 'package:dartdoc/src/model/kind.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/utils.dart';
import 'package:dartdoc/src/warnings.dart';
/// Getters and setters.
class Accessor extends ModelElement {
@override
final PropertyAccessorElement2 element;
/// The combo ([Field] or [TopLevelVariable]) containing this accessor.
///
/// Initialized in [Field]'s constructor and in [TopLevelVariable]'s
/// constructor.
// TODO(srawlins): This might be super fragile. This field should somehow be
// initialized by code inside this library.
late final GetterSetterCombo enclosingCombo;
Accessor(this.element, super.library, super.packageGraph,
{ExecutableMember? super.originalMember});
@override
CharacterLocation? get characterLocation => element.isSynthetic
? enclosingCombo.characterLocation
: super.characterLocation;
@override
ExecutableMember? get originalMember =>
super.originalMember as ExecutableMember?;
late final Callable modelType =
getTypeFor((originalMember ?? element).type, library) as Callable;
bool get isSynthetic => element.isSynthetic;
/// The [enclosingCombo] where this element was defined.
late final GetterSetterCombo definingCombo =
getModelForElement(element.variable3!) as GetterSetterCombo;
String get _sourceCode {
if (!isSynthetic) {
return super.sourceCode;
}
var modelNode = packageGraph.getModelNodeFor(definingCombo.element);
return modelNode == null
? ''
: const HtmlEscape().convert(modelNode.sourceCode);
}
@override
String get sourceCode => _sourceCode;
@override
late final String documentationComment = () {
if (isSynthetic) {
/// Build a documentation comment for this accessor.
return _hasSyntheticDocumentationComment
? definingCombo.documentationComment
: '';
}
// TODO(srawlins): This doesn't seem right... the super value has delimiters
// (like `///`), but this one doesn't?
return stripCommentDelimiters(super.documentationComment);
}();
/// If this is a getter, assume we want synthetic documentation.
///
/// If the [definingCombo] has a `nodoc` tag, we want synthetic documentation
/// for a synthetic accessor just in case it is inherited somewhere down the
/// line due to split inheritance.
bool get _hasSyntheticDocumentationComment =>
(isGetter || definingCombo.hasNodoc || _comboDocsAreIndependent) &&
definingCombo.hasDocumentationComment;
// If we're a setter, and a getter exists, do not add synthetic documentation
// if the combo's documentation is actually derived from that getter.
bool get _comboDocsAreIndependent {
if (isSetter && definingCombo.hasGetter) {
if (definingCombo.getter!.isSynthetic ||
!definingCombo.documentationFrom.contains(this)) {
return true;
}
}
return false;
}
@override
bool get hasDocumentationComment => isSynthetic
? _hasSyntheticDocumentationComment
: element.documentationComment != null;
@override
void warn(
PackageWarning kind, {
String? message,
Iterable<Locatable> referredFrom = const [],
Iterable<String> extendedDebug = const [],
}) {
enclosingCombo.warn(kind,
message: message,
referredFrom: referredFrom,
extendedDebug: extendedDebug);
}
@override
ModelElement get enclosingElement => switch (element.enclosingElement2) {
LibraryFragment enclosingCompilationUnit =>
getModelForElement(enclosingCompilationUnit.element),
_ => getModelFor(element.enclosingElement2, library)
};
@override
String get filePath => enclosingCombo.filePath;
@override
String get aboveSidebarPath {
final enclosingElement = this.enclosingElement;
return switch (enclosingElement) {
Container() => enclosingElement.sidebarPath,
Library() => enclosingElement.sidebarPath,
_ => throw StateError(
'Enclosing element of $this should be Container or Library, but was '
'${enclosingElement.runtimeType}')
};
}
@override
String? get belowSidebarPath => null;
@override
bool get isCanonical => enclosingCombo.isCanonical;
@override
String? get href => enclosingCombo.href;
bool get isGetter => element is GetterElement;
bool get isSetter => element is SetterElement;
@override
Kind get kind => Kind.accessor;
/// Accessors should never be participating directly in comment reference
/// lookups.
@override
Map<String, CommentReferable> get referenceChildren =>
enclosingCombo.referenceChildren;
/// Accessors should never be participating directly in comment reference
/// lookups.
@override
Iterable<CommentReferable> get referenceParents =>
enclosingCombo.referenceParents;
}
/// A getter or setter that is a member of a [Container].
class ContainerAccessor extends Accessor with ContainerMember, Inheritable {
late final Container _enclosingElement;
@override
final bool isInherited;
ContainerAccessor(super.element, super.library, super.packageGraph,
[Container? enclosingElement])
: isInherited = false {
_enclosingElement = enclosingElement ?? super.enclosingElement as Container;
}
ContainerAccessor.inherited(
super.element, super.library, super.packageGraph, this._enclosingElement,
{super.originalMember})
: isInherited = true;
/// The index and values fields are never declared, and must be special cased.
bool get _isEnumSynthetic =>
enclosingCombo is EnumField && (name == 'index' || name == 'values');
@override
CharacterLocation? get characterLocation {
if (_isEnumSynthetic) return enclosingElement.characterLocation;
// TODO(jcollins-g): Remove the enclosingCombo case below once
// https://github.com/dart-lang/sdk/issues/46154 is fixed.
if (enclosingCombo is EnumField) return enclosingCombo.characterLocation;
return super.characterLocation;
}
@override
bool get isCovariant => isSetter && parameters.first.isCovariant;
@override
Container get enclosingElement => _enclosingElement;
@override
ContainerAccessor? get overriddenElement {
assert(packageGraph.allLibrariesAdded);
final parent = element.enclosingElement2;
if (parent is! InterfaceElement2) {
return null;
}
for (final supertype in parent.allSupertypes) {
var accessor = isGetter
? supertype.getters
.firstWhereOrNull((e) => e.lookupName == element.lookupName)
?.baseElement
: supertype.setters
.firstWhereOrNull((e) => e.lookupName == element.lookupName)
?.baseElement;
if (accessor == null) {
continue;
}
final parentContainer =
getModelForElement(supertype.element3) as InheritingContainer;
final possibleFields =
parentContainer.declaredFields.where((f) => !f.isStatic);
final fieldName = accessor.lookupName?.replaceFirst('=', '');
final foundField =
possibleFields.firstWhereOrNull((f) => f.element.name3 == fieldName);
if (foundField == null) {
continue;
}
final overridden = isGetter ? foundField.getter! : foundField.setter!;
assert(!overridden.isInherited);
return overridden;
}
return null;
}
}