Skip to content

Fix global table lookup of non-canonical elements that have canonical counterparts #2397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 23 additions & 21 deletions lib/src/markdown_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,10 @@ class _MarkdownCommentReference {
// conflicts are allowed, but an instance field is allowed to have the
// same name as a named constructor.
_reducePreferConstructorViaIndicators,
// Prefer Fields/TopLevelVariables to accessors.
// TODO(jcollins-g): Remove after fixing dart-lang/dartdoc#2396 or
// exclude Accessors from all lookup tables.
_reducePreferCombos,
// Prefer the Dart analyzer's resolution of comment references. We
// can't start from this because of the differences in Dartdoc
// canonicalization.
Expand Down Expand Up @@ -547,6 +551,14 @@ class _MarkdownCommentReference {
}
}

void _reducePreferCombos() {
var accessors = results.whereType<Accessor>().toList();
accessors.forEach((a) {
results.remove(a);
results.add(a.enclosingCombo);
});
}

void _findTypeParameters() {
if (element is TypeParameters) {
results.addAll((element as TypeParameters).typeParameters.where((p) =>
Expand Down Expand Up @@ -648,7 +660,7 @@ class _MarkdownCommentReference {
prefixToLibrary[codeRefChompedParts.first]?.forEach((l) => l
.modelElementsNameMap[lookup]
?.map(_convertConstructors)
?.forEach((m) => _addCanonicalResult(m, _getPreferredClass(m))));
?.forEach((m) => _addCanonicalResult(m)));
}
}
}
Expand All @@ -660,8 +672,7 @@ class _MarkdownCommentReference {
if (codeRefChomped == modelElement.fullyQualifiedNameWithoutLibrary ||
(modelElement is Library &&
codeRefChomped == modelElement.fullyQualifiedName)) {
_addCanonicalResult(
_convertConstructors(modelElement), preferredClass);
_addCanonicalResult(_convertConstructors(modelElement));
}
}
}
Expand All @@ -671,7 +682,7 @@ class _MarkdownCommentReference {
// Only look for partially qualified matches if we didn't find a fully qualified one.
if (library.modelElementsNameMap.containsKey(codeRefChomped)) {
for (final modelElement in library.modelElementsNameMap[codeRefChomped]) {
_addCanonicalResult(_convertConstructors(modelElement), preferredClass);
_addCanonicalResult(_convertConstructors(modelElement));
}
}
}
Expand All @@ -684,15 +695,7 @@ class _MarkdownCommentReference {
packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
for (var modelElement
in packageGraph.findRefElementCache[codeRefChomped]) {
// For fully qualified matches, the original preferredClass passed
// might make no sense. Instead, use the enclosing class from the
// element in [packageGraph.findRefElementCache], because that element's
// enclosing class will be preferred from [codeRefChomped]'s perspective.
_addCanonicalResult(
_convertConstructors(modelElement),
modelElement.enclosingElement is Class
? modelElement.enclosingElement
: null);
_addCanonicalResult(_convertConstructors(modelElement));
}
}
}
Expand Down Expand Up @@ -785,9 +788,8 @@ class _MarkdownCommentReference {
}

// Add a result, but make it canonical.
void _addCanonicalResult(ModelElement modelElement, Container tryClass) {
results.add(packageGraph.findCanonicalModelElementFor(modelElement.element,
preferredClass: tryClass));
void _addCanonicalResult(ModelElement modelElement) {
results.add(modelElement.canonicalModelElement);
}

/// _getResultsForClass assumes codeRefChomped might be a member of tryClass (inherited or not)
Expand All @@ -804,7 +806,7 @@ class _MarkdownCommentReference {
} else {
// People like to use 'this' in docrefs too.
if (codeRef == 'this') {
_addCanonicalResult(tryClass, null);
_addCanonicalResult(tryClass);
} else {
// TODO(jcollins-g): get rid of reimplementation of identifier resolution
// or integrate into ModelElement in a simpler way.
Expand All @@ -816,7 +818,7 @@ class _MarkdownCommentReference {
// Fortunately superChains are short, but optimize this if it matters.
superChain.addAll(tryClass.superChain.map((t) => t.element));
for (final c in superChain) {
_getResultsForSuperChainElement(c, tryClass);
_getResultsForSuperChainElement(c);
if (results.isNotEmpty) break;
}
}
Expand All @@ -825,20 +827,20 @@ class _MarkdownCommentReference {

/// Get any possible results for this class in the superChain. Returns
/// true if we found something.
void _getResultsForSuperChainElement(Class c, Class tryClass) {
void _getResultsForSuperChainElement(Class c) {
var membersToCheck = (c.allModelElementsByNamePart[codeRefChomped] ?? [])
.map(_convertConstructors);
for (var modelElement in membersToCheck) {
// [thing], a member of this class
_addCanonicalResult(modelElement, tryClass);
_addCanonicalResult(modelElement);
}
if (codeRefChompedParts.length < 2 ||
codeRefChompedParts[codeRefChompedParts.length - 2] == c.name) {
membersToCheck =
(c.allModelElementsByNamePart[codeRefChompedParts.last] ??
<ModelElement>[])
.map(_convertConstructors);
membersToCheck.forEach((m) => _addCanonicalResult(m, tryClass));
membersToCheck.forEach((m) => _addCanonicalResult(m));
}
results.remove(null);
if (results.isNotEmpty) return;
Expand Down
3 changes: 1 addition & 2 deletions lib/src/model/class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,7 @@ class Class extends Container
var accessorMap = <String, List<PropertyAccessorElement>>{};
for (var accessorElement in inheritedAccessorElements) {
var name = accessorElement.name.replaceFirst('=', '');
accessorMap.putIfAbsent(name, () => []);
accessorMap[name].add(accessorElement);
accessorMap.putIfAbsent(name, () => []).add(accessorElement);
}

// For half-inherited fields, the analyzer only links the non-inherited
Expand Down
32 changes: 17 additions & 15 deletions lib/src/model/library.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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:collection';

import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
Expand Down Expand Up @@ -283,8 +285,8 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
for (var i in element.imports) {
// Ignore invalid imports.
if (i.prefix?.name != null && i.importedLibrary != null) {
_prefixToLibrary.putIfAbsent(i.prefix?.name, () => {});
_prefixToLibrary[i.prefix?.name]
_prefixToLibrary
.putIfAbsent(i.prefix?.name, () => {})
.add(ModelElement.from(i.importedLibrary, library, packageGraph));
}
}
Expand Down Expand Up @@ -585,29 +587,29 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
static String getLibraryName(LibraryElement element) =>
_getLibraryName(element);

/*late final*/ Map<String, Set<ModelElement>> _modelElementsNameMap;
/*late final*/ HashMap<String, Set<ModelElement>> _modelElementsNameMap;

/// Map of [fullyQualifiedNameWithoutLibrary] to all matching [ModelElement]s
/// in this library. Used for code reference lookups.
Map<String, Set<ModelElement>> get modelElementsNameMap {
HashMap<String, Set<ModelElement>> get modelElementsNameMap {
if (_modelElementsNameMap == null) {
_modelElementsNameMap = <String, Set<ModelElement>>{};
_modelElementsNameMap = HashMap<String, Set<ModelElement>>();
allModelElements.forEach((ModelElement modelElement) {
// [definingLibrary] may be null if [element] has been imported or
// exported with a non-normalized URI, like "src//a.dart".
if (modelElement.definingLibrary == null) return;
_modelElementsNameMap.putIfAbsent(
modelElement.fullyQualifiedNameWithoutLibrary, () => {});
_modelElementsNameMap[modelElement.fullyQualifiedNameWithoutLibrary]
_modelElementsNameMap
.putIfAbsent(
modelElement.fullyQualifiedNameWithoutLibrary, () => {})
.add(modelElement);
});
}
return _modelElementsNameMap;
}

/*late final*/ Map<Element, Set<ModelElement>> _modelElementsMap;
/*late final*/ HashMap<Element, Set<ModelElement>> _modelElementsMap;

Map<Element, Set<ModelElement>> get modelElementsMap {
HashMap<Element, Set<ModelElement>> get modelElementsMap {
if (_modelElementsMap == null) {
var results = quiver.concat(<Iterable<ModelElement>>[
library.constants,
Expand Down Expand Up @@ -639,13 +641,13 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
]);
}),
]);
_modelElementsMap = <Element, Set<ModelElement>>{};
_modelElementsMap = HashMap<Element, Set<ModelElement>>();
results.forEach((modelElement) {
_modelElementsMap.putIfAbsent(modelElement.element, () => {});
_modelElementsMap[modelElement.element].add(modelElement);
_modelElementsMap
.putIfAbsent(modelElement.element, () => {})
.add(modelElement);
});
_modelElementsMap.putIfAbsent(element, () => {});
_modelElementsMap[element].add(this);
_modelElementsMap.putIfAbsent(element, () => {}).add(this);
}
return _modelElementsMap;
}
Expand Down
8 changes: 4 additions & 4 deletions lib/src/model/model_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,9 @@ abstract class ModelElement extends Canonicalization
library.packageGraph.allConstructedModelElements[key] = newModelElement;
if (newModelElement is Inheritable) {
var iKey = Tuple2<Element, Library>(e, library);
library.packageGraph.allInheritableElements.putIfAbsent(iKey, () => {});
library.packageGraph.allInheritableElements[iKey].add(newModelElement);
library.packageGraph.allInheritableElements
.putIfAbsent(iKey, () => {})
.add(newModelElement);
}
}
}
Expand Down Expand Up @@ -557,10 +558,9 @@ abstract class ModelElement extends Canonicalization
preferredClass: preferredClass);
}

ModelElement _canonicalModelElement;
// Returns the canonical ModelElement for this ModelElement, or null
// if there isn't one.
ModelElement _canonicalModelElement;

ModelElement get canonicalModelElement =>
_canonicalModelElement ??= buildCanonicalModelElement();

Expand Down
23 changes: 12 additions & 11 deletions lib/src/model/package_graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,13 @@ class PackageGraph {
return _extensions;
}

Map<String, Set<ModelElement>> _findRefElementCache;
Map<String, Set<ModelElement>> get findRefElementCache {
HashMap<String, Set<ModelElement>> _findRefElementCache;
HashMap<String, Set<ModelElement>> get findRefElementCache {
if (_findRefElementCache == null) {
assert(packageGraph.allLibrariesAdded);
_findRefElementCache = {};
_findRefElementCache = HashMap<String, Set<ModelElement>>();
for (final modelElement
in utils.filterNonDocumented(packageGraph.allLocalModelElements)) {
in utils.filterHasCanonical(packageGraph.allModelElements)) {
_findRefElementCache.putIfAbsent(
modelElement.fullyQualifiedNameWithoutLibrary, () => {});
_findRefElementCache.putIfAbsent(
Expand All @@ -210,15 +210,15 @@ class PackageGraph {
PackageWarningCounter _packageWarningCounter;

/// All ModelElements constructed for this package; a superset of [allModelElements].
final Map<Tuple3<Element, Library, Container>, ModelElement>
allConstructedModelElements = {};
final allConstructedModelElements =
HashMap<Tuple3<Element, Library, Container>, ModelElement>();

/// Anything that might be inheritable, place here for later lookup.
final Map<Tuple2<Element, Library>, Set<ModelElement>>
allInheritableElements = {};
final allInheritableElements =
HashMap<Tuple2<Element, Library>, Set<ModelElement>>();

/// A mapping of the list of classes which implement each class.
final Map<Class, List<Class>> _implementors = LinkedHashMap(
final _implementors = LinkedHashMap<Class, List<Class>>(
equals: (Class a, Class b) => a.definingClass == b.definingClass,
hashCode: (Class class_) => class_.definingClass.hashCode);

Expand Down Expand Up @@ -544,8 +544,9 @@ class PackageGraph {
referredFrom: <Locatable>[topLevelLibrary]);
return;
}
_libraryElementReexportedBy.putIfAbsent(libraryElement, () => {});
_libraryElementReexportedBy[libraryElement].add(topLevelLibrary);
_libraryElementReexportedBy
.putIfAbsent(libraryElement, () => {})
.add(topLevelLibrary);
for (var exportedElement in libraryElement.exports) {
_tagReexportsFor(
topLevelLibrary, exportedElement.exportedLibrary, exportedElement);
Expand Down
5 changes: 5 additions & 0 deletions lib/src/model_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ AstNode getAstNode(
return null;
}

Iterable<T> filterHasCanonical<T extends ModelElement>(
Iterable<T> maybeHasCanonicalItems) {
return maybeHasCanonicalItems.where((me) => me.canonicalModelElement != null);
}

/// Remove elements that aren't documented.
Iterable<T> filterNonDocumented<T extends Documentable>(
Iterable<T> maybeDocumentedItems) {
Expand Down
5 changes: 3 additions & 2 deletions lib/src/package_config_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ class FakePackageConfigProvider implements PackageConfigProvider {
final _packageConfigData = <String, List<package_config.Package>>{};

void addPackageToConfigFor(String location, String name, Uri root) {
_packageConfigData.putIfAbsent(location, () => []);
_packageConfigData[location].add(package_config.Package(name, root));
_packageConfigData
.putIfAbsent(location, () => [])
.add(package_config.Package(name, root));
}

@override
Expand Down
3 changes: 1 addition & 2 deletions lib/src/warnings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,7 @@ class PackageWarningCounter {
errorCount += 1;
}
var warningData = Tuple2<PackageWarning, String>(kind, message);
countedWarnings.putIfAbsent(element?.element, () => {});
countedWarnings[element?.element].add(warningData);
countedWarnings.putIfAbsent(element?.element, () => {}).add(warningData);
_writeWarning(kind, warningMode, config.verboseWarnings,
element?.fullyQualifiedName, fullMessage);
}
Expand Down
19 changes: 19 additions & 0 deletions test/end2end/model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:dartdoc/src/render/model_element_renderer.dart';
import 'package:dartdoc/src/render/parameter_renderer.dart';
import 'package:dartdoc/src/render/typedef_renderer.dart';
import 'package:dartdoc/src/special_elements.dart';
import 'package:dartdoc/src/tuple.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -882,6 +883,24 @@ void main() {
docsAsHtml = doAwesomeStuff.documentationAsHtml;
});

test('Verify links to inherited members inside class', () {
expect(
docsAsHtml,
contains(
'<a href="${HTMLBASE_PLACEHOLDER}fake/ImplicitProperties/forInheriting.html">ClassWithUnusualProperties.forInheriting</a>'));
expect(
docsAsHtml,
contains(
'<a href="%%__HTMLBASE_dartdoc_internal__%%reexport_two/BaseReexported/action.html">ExtendedBaseReexported.action</a></p>'));
var doAwesomeStuffWarnings = packageGraph.packageWarningCounter
.countedWarnings[doAwesomeStuff.element] ??
[];
expect(
doAwesomeStuffWarnings,
isNot(anyElement((Tuple2<PackageWarning, String> e) =>
e.item1 == PackageWarning.ambiguousDocReference)));
});

test('can handle renamed imports', () {
var aFunctionUsingRenamedLib = fakeLibrary.functions
.firstWhere((f) => f.name == 'aFunctionUsingRenamedLib');
Expand Down
5 changes: 5 additions & 0 deletions testing/test_package/lib/fake.dart
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ class ExtraSpecialList<E> extends SpecialList {}
/// {@subCategory Things and Such}
/// {@image https://flutter.io/images/catalog-widget-placeholder.png}
/// {@samples https://flutter.io}
///
class BaseForDocComments {
/// Takes a [value] and returns a String.
///
Expand Down Expand Up @@ -899,6 +900,10 @@ class BaseForDocComments {
///
/// Reference to something that doesn't exist containing a type parameter [ThisIsNotHereNoWay<MyType>]
///
/// Reference to an inherited member: [ClassWithUnusualProperties.forInheriting]
///
/// Reference to an inherited member in another library via class name: [ExtendedBaseReexported.action]
///
/// Link to a nonexistent file (erroneously expects base href): [link](SubForDocComments/localMethod.html)
///
/// Link to an existing file: [link](../SubForDocComments/localMethod.html)
Expand Down
9 changes: 8 additions & 1 deletion testing/test_package/lib/src/somelib.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ class YetAnotherClass {}

class AUnicornClass {}

class BaseReexported {
String action;
}

class ExtendedBaseReexported extends BaseReexported {
}

/// A private extension.
extension _Unseen on Object {
void doYouSeeMe() { }
Expand All @@ -21,4 +28,4 @@ extension on List {
/// [_Unseen] is not seen, but [DocumentMe] is.
extension DocumentThisExtensionOnce on String {
String get reportOnString => '$this is wonderful';
}
}