| // 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; |
| } |
| } |