blob: 4457f2e1a9a5591cc00371a00da611e4d57df365 [file] [log] [blame]
// Copyright (c) 2021, 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:analysis_server/src/computer/computer_hover.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
/// Cached data about the documentation associated with the elements declared in
/// a single analysis context.
class DocumentationCache {
/// A shared instance for elements that have no documentation.
static final DocumentationWithSummary _emptyDocs =
DocumentationWithSummary(full: '', summary: '');
/// The object used to compute the documentation associated with a single
/// element.
final DartdocDirectiveInfo dartdocDirectiveInfo;
/// The documentation associated with the elements that have been cached. The
/// cache is keyed by the path of the file containing the declaration of the
/// element and the qualified name of the element.
final Map<String, Map<String, DocumentationWithSummary>> documentationCache =
{};
/// Initialize a newly created cache.
DocumentationCache(this.dartdocDirectiveInfo);
/// Fill the cache with data from the [result].
void cacheFromResult(ResolvedUnitResult result) {
var compilationUnit = result.unit?.declaredElement;
if (compilationUnit != null) {
documentationCache.remove(_keyForUnit(compilationUnit));
_cacheFromElement(compilationUnit);
for (var library in result.libraryElement.importedLibraries) {
_cacheLibrary(library);
}
}
}
/// Return the data cached for the given [element], or `null` if there is no
/// cached data.
DocumentationWithSummary? dataFor(Element element) {
var parent = element.enclosingElement;
if (parent == null) {
return null;
}
var key = element.name;
if (key == null) {
return null;
}
if (parent is! CompilationUnitElement) {
var parentName = parent.name;
if (parentName == null) {
return null;
}
key = '$parentName.$key';
parent = parent.enclosingElement;
}
if (parent is CompilationUnitElement) {
var elementMap = documentationCache[_keyForUnit(parent)];
return elementMap?[key];
}
return null;
}
/// Fill the cache with data from the [compilationUnit].
void _cacheFromElement(CompilationUnitElement compilationUnit) {
var elementMap =
documentationCache.putIfAbsent(_keyForUnit(compilationUnit), () => {});
for (var element in compilationUnit.accessors) {
if (!element.isSynthetic) {
elementMap.cacheTopLevelElement(dartdocDirectiveInfo, element);
}
}
for (var element in compilationUnit.enums) {
var parentKey =
elementMap.cacheTopLevelElement(dartdocDirectiveInfo, element);
if (parentKey != null) {
for (var member in element.fields) {
elementMap.cacheMember(dartdocDirectiveInfo, parentKey, member);
}
}
}
for (var element in compilationUnit.extensions) {
var parentKey =
elementMap.cacheTopLevelElement(dartdocDirectiveInfo, element);
if (parentKey != null) {
for (var member in element.accessors) {
if (!member.isSynthetic) {
elementMap.cacheMember(dartdocDirectiveInfo, parentKey, member);
}
}
for (var member in element.fields) {
if (!member.isSynthetic) {
elementMap.cacheMember(dartdocDirectiveInfo, parentKey, member);
}
}
for (var member in element.methods) {
elementMap.cacheMember(dartdocDirectiveInfo, parentKey, member);
}
}
}
for (var element in compilationUnit.functions) {
elementMap.cacheTopLevelElement(dartdocDirectiveInfo, element);
}
for (var element in [
...compilationUnit.mixins,
...compilationUnit.classes
]) {
var parentKey =
elementMap.cacheTopLevelElement(dartdocDirectiveInfo, element);
if (parentKey != null) {
for (var member in element.accessors) {
if (!element.isSynthetic) {
elementMap.cacheMember(dartdocDirectiveInfo, parentKey, member);
}
}
for (var member in element.fields) {
if (!element.isSynthetic) {
elementMap.cacheMember(dartdocDirectiveInfo, parentKey, member);
}
}
for (var member in element.methods) {
elementMap.cacheMember(dartdocDirectiveInfo, parentKey, member);
}
}
}
for (var element in compilationUnit.topLevelVariables) {
if (!element.isSynthetic) {
elementMap.cacheTopLevelElement(dartdocDirectiveInfo, element);
}
}
for (var element in compilationUnit.typeAliases) {
elementMap.cacheTopLevelElement(dartdocDirectiveInfo, element);
}
}
/// Cache the data for the given [library] and every library exported from it
/// if it hasn't already been cached.
void _cacheLibrary(LibraryElement library) {
if (_hasDataFor(library.definingCompilationUnit)) {
return;
}
for (var unit in library.units) {
_cacheFromElement(unit);
}
for (var exported in library.exportedLibraries) {
_cacheLibrary(exported);
}
}
/// Return `true` if the cache contains data for the [compilationUnit].
bool _hasDataFor(CompilationUnitElement compilationUnit) {
return documentationCache.containsKey(_keyForUnit(compilationUnit));
}
/// Return the key used in the [documentationCache] for the [compilationUnit].
String _keyForUnit(CompilationUnitElement compilationUnit) =>
compilationUnit.source.fullName;
}
extension on Map<String, DocumentationWithSummary> {
/// Cache the data associated with the top-level [element], and return the
/// [key] used for the element. This does not cache any data associated with
/// any other elements, including children of the [element].
String? cacheTopLevelElement(
DartdocDirectiveInfo dartdocDirectiveInfo, Element element) {
var key = element.name;
if (key == null) {
return null;
}
cacheElement(dartdocDirectiveInfo, key, element);
return key;
}
/// Cache the data associated with the [member] element given that the key
/// associated with the member's parent is [parentKey].
void cacheMember(DartdocDirectiveInfo dartdocDirectiveInfo, String parentKey,
Element member) {
var name = member.name;
if (name == null) {
return null;
}
cacheElement(dartdocDirectiveInfo, '$parentKey.$name', member);
}
/// Cache the data associated with the [element], using the given [key].
DocumentationWithSummary? cacheElement(
DartdocDirectiveInfo dartdocDirectiveInfo, String key, Element element) {
var documentation = DartUnitHoverComputer.computeDocumentation(
dartdocDirectiveInfo, element,
includeSummary: true);
if (documentation is DocumentationWithSummary) {
return this[key] = documentation;
}
return this[key] = DocumentationCache._emptyDocs;
}
}