Skip to content

Commit

Permalink
Extract Canonicalization from the ModelElement hierarchy (#3753)
Browse files Browse the repository at this point in the history
  • Loading branch information
srawlins authored Apr 22, 2024
1 parent 1ae5b4c commit d9e31ff
Show file tree
Hide file tree
Showing 13 changed files with 57 additions and 201 deletions.
5 changes: 2 additions & 3 deletions lib/src/generator/templates.aot_renderers_for_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import 'dart:convert';

import 'package:dartdoc/src/generator/template_data.dart';
import 'package:dartdoc/src/model/accessor.dart';
import 'package:dartdoc/src/model/canonicalization.dart';
import 'package:dartdoc/src/model/category.dart';
import 'package:dartdoc/src/model/class.dart';
import 'package:dartdoc/src/model/constructor.dart';
Expand All @@ -37,6 +36,7 @@ import 'package:dartdoc/src/model/operator.dart';
import 'package:dartdoc/src/model/package.dart';
import 'package:dartdoc/src/model/top_level_variable.dart';
import 'package:dartdoc/src/model/typedef.dart';
import 'package:dartdoc/src/warnings.dart';

String renderCategory(CategoryTemplateData context0) {
final buffer = StringBuffer();
Expand Down Expand Up @@ -3524,8 +3524,7 @@ String _deduplicated_lib_templates__head_html(TemplateDataBase context0) {
return buffer.toString();
}

String _deduplicated_lib_templates__documentation_html(
Canonicalization context0) {
String _deduplicated_lib_templates__documentation_html(Warnable context0) {
final buffer = StringBuffer();
if (context0.hasDocumentation) {
buffer.writeln();
Expand Down
147 changes: 9 additions & 138 deletions lib/src/generator/templates.runtime_renderers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,28 +310,6 @@ class _Renderer_Accessor extends RendererBase<Accessor> {
parent: r);
},
),
'namePart': Property(
getValue: (CT_ c) => c.namePart,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(
self.getValue(c) as String,
nextProperty,
[...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.namePart, ast, r.template, sink,
parent: r);
},
),
'originalMember': Property(
getValue: (CT_ c) => c.originalMember,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -761,49 +739,6 @@ class _Renderer_CanonicalFor extends RendererBase<CanonicalFor> {
}
}

class _Renderer_Canonicalization extends RendererBase<Canonicalization> {
static final Map<Type, Object> _propertyMapCache = {};
static Map<String, Property<CT_>> propertyMap<
CT_ extends Canonicalization>() =>
_propertyMapCache.putIfAbsent(
CT_,
() => {
..._Renderer_Object.propertyMap<CT_>(),
'isCanonical': Property(
getValue: (CT_ c) => c.isCanonical,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'bool'),
getBool: (CT_ c) => c.isCanonical,
),
'locationPieces': Property(
getValue: (CT_ c) => c.locationPieces,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'Set<String>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.locationPieces.map((e) =>
_render_String(e, ast, r.template, sink, parent: r));
},
),
}) as Map<String, Property<CT_>>;

_Renderer_Canonicalization(Canonicalization context,
RendererBase<Object>? parent, Template template, StringSink sink)
: super(context, parent, template, sink);

@override
Property<Canonicalization>? getProperty(String key) {
if (propertyMap<Canonicalization>().containsKey(key)) {
return propertyMap<Canonicalization>()[key];
} else {
return null;
}
}
}

class _Renderer_Categorization extends RendererBase<Categorization> {
static final Map<Type, Object> _propertyMapCache = {};
static Map<String, Property<CT_>> propertyMap<CT_ extends Categorization>() =>
Expand Down Expand Up @@ -903,7 +838,6 @@ class _Renderer_Category extends RendererBase<Category> {
..._Renderer_Warnable.propertyMap<CT_>(),
..._Renderer_CommentReferable.propertyMap<CT_>(),
..._Renderer_Locatable.propertyMap<CT_>(),
..._Renderer_Canonicalization.propertyMap<CT_>(),
..._Renderer_MarkdownFileDocumentation.propertyMap<CT_>(),
..._Renderer_LibraryContainer.propertyMap<CT_>(),
..._Renderer_TopLevelContainer.propertyMap<CT_>(),
Expand Down Expand Up @@ -8984,6 +8918,13 @@ class _Renderer_Locatable extends RendererBase<Locatable> {
_render_String(c.href!, ast, r.template, sink, parent: r);
},
),
'isCanonical': Property(
getValue: (CT_ c) => c.isCanonical,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'bool'),
getBool: (CT_ c) => c.isCanonical,
),
'location': Property(
getValue: (CT_ c) => c.location,
renderVariable:
Expand Down Expand Up @@ -9136,18 +9077,6 @@ class _Renderer_MarkdownFileDocumentation
parent: r);
},
),
'locationPieces': Property(
getValue: (CT_ c) => c.locationPieces,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'Set<String>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.locationPieces.map((e) =>
_render_String(e, ast, r.template, sink, parent: r));
},
),
'oneLineDoc': Property(
getValue: (CT_ c) => c.oneLineDoc,
renderVariable:
Expand Down Expand Up @@ -10102,7 +10031,7 @@ class _Renderer_ModelElement extends RendererBase<ModelElement> {
_propertyMapCache.putIfAbsent(
CT_,
() => {
..._Renderer_Canonicalization.propertyMap<CT_>(),
..._Renderer_Object.propertyMap<CT_>(),
..._Renderer_CommentReferable.propertyMap<CT_>(),
..._Renderer_Warnable.propertyMap<CT_>(),
..._Renderer_Locatable.propertyMap<CT_>(),
Expand Down Expand Up @@ -10748,18 +10677,6 @@ class _Renderer_ModelElement extends RendererBase<ModelElement> {
parent: r);
},
),
'locationPieces': Property(
getValue: (CT_ c) => c.locationPieces,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'Set<String>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.locationPieces.map((e) =>
_render_String(e, ast, r.template, sink, parent: r));
},
),
'modelNode': Property(
getValue: (CT_ c) => c.modelNode,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -11384,40 +11301,6 @@ class _Renderer_Nameable extends RendererBase<Nameable> {
_render_String(c.name, ast, r.template, sink, parent: r);
},
),
'namePart': Property(
getValue: (CT_ c) => c.namePart,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(
self.getValue(c) as String,
nextProperty,
[...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.namePart, ast, r.template, sink,
parent: r);
},
),
'namePieces': Property(
getValue: (CT_ c) => c.namePieces,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'Set<String>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.namePieces.map((e) =>
_render_String(e, ast, r.template, sink, parent: r));
},
),
'packageGraph': Property(
getValue: (CT_ c) => c.packageGraph,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -11667,7 +11550,6 @@ class _Renderer_Package extends RendererBase<Package> {
..._Renderer_LibraryContainer.propertyMap<CT_>(),
..._Renderer_Nameable.propertyMap<CT_>(),
..._Renderer_Locatable.propertyMap<CT_>(),
..._Renderer_Canonicalization.propertyMap<CT_>(),
..._Renderer_Warnable.propertyMap<CT_>(),
..._Renderer_CommentReferable.propertyMap<CT_>(),
'aboveSidebarPath': Property(
Expand Down Expand Up @@ -12128,18 +12010,6 @@ class _Renderer_Package extends RendererBase<Package> {
parent: r);
},
),
'locationPieces': Property(
getValue: (CT_ c) => c.locationPieces,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'Set<String>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.locationPieces.map((e) =>
_render_String(e, ast, r.template, sink, parent: r));
},
),
'name': Property(
getValue: (CT_ c) => c.name,
renderVariable:
Expand Down Expand Up @@ -16353,6 +16223,7 @@ const _invisibleGetters = {
'documentationIsLocal',
'fullyQualifiedName',
'href',
'isCanonical',
'location'
},
'Map': {
Expand Down
11 changes: 2 additions & 9 deletions lib/src/model/accessor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,22 +156,15 @@ class Accessor extends ModelElement {
@override
Kind get kind => Kind.accessor;

String get _namePart => super.namePart.split('=').first;

@override
String get namePart => _namePart;

@override

/// Accessors should never be participating directly in comment reference
/// lookups.
@override
Map<String, CommentReferable> get referenceChildren =>
enclosingCombo.referenceChildren;

@override

/// Accessors should never be participating directly in comment reference
/// lookups.
@override
Iterable<CommentReferable> get referenceParents =>
enclosingCombo.referenceParents;
}
Expand Down
36 changes: 22 additions & 14 deletions lib/src/model/canonicalization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ import 'package:dartdoc/src/warnings.dart';
/// This provides heuristic scoring to determine which library a human likely
/// considers this element to be primarily 'from', and therefore, canonical.
/// Still warn if the heuristic isn't very confident.
abstract mixin class Canonicalization
implements Locatable, Documentable, Warnable {
bool get isCanonical;
final class Canonicalization {
final ModelElement _element;

/// Pieces of the location, split to remove 'package:' and slashes.
Set<String> get locationPieces;
Canonicalization(this._element);

Library calculateCanonicalCandidate(Iterable<Library> libraries) {
var locationPieces = _element.element.location
.toString()
.split(_locationSplitter)
.where((s) => s.isNotEmpty)
.toSet();
var scoredCandidates = libraries
.map((library) => _scoreElementWithLibrary(
library, fullyQualifiedName, locationPieces))
library, _element.fullyQualifiedName, locationPieces))
.toList(growable: false)
..sort();

Expand All @@ -31,11 +34,11 @@ abstract mixin class Canonicalization
var confidence = highestScore - secondHighestScore;
final canonicalLibrary = librariesByScore.last;

if (confidence < config.ambiguousReexportScorerMinConfidence) {
if (confidence < _element.config.ambiguousReexportScorerMinConfidence) {
var libraryNames = librariesByScore.map((l) => l.name);
var message = '$libraryNames -> ${canonicalLibrary.name} '
'(confidence ${confidence.toStringAsPrecision(4)})';
warn(PackageWarning.ambiguousReexport,
_element.warn(PackageWarning.ambiguousReexport,
message: message, extendedDebug: scoredCandidates.map((s) => '$s'));
}

Expand All @@ -61,17 +64,19 @@ abstract mixin class Canonicalization
scoredCandidate._alterScore(-1.0, _Reason.deprecated);
}

var libraryNamePieces = {
...library.name.split('.').where((s) => s.isNotEmpty)
};

// Give a big boost if the library has the package name embedded in it.
if (library.package.namePieces
.intersection(library.namePieces)
.isNotEmpty) {
if (libraryNamePieces.contains(library.package.name)) {
scoredCandidate._alterScore(1.0, _Reason.packageName);
}

// Give a tiny boost for libraries with long names, assuming they're
// more specific (and therefore more likely to be the owner of this symbol).
scoredCandidate._alterScore(
.01 * library.namePieces.length, _Reason.longName);
.01 * libraryNamePieces.length, _Reason.longName);

// If we don't know the location of this element (which shouldn't be
// possible), return our best guess.
Expand All @@ -81,7 +86,7 @@ abstract mixin class Canonicalization
// The more pieces we have of the location in our library name, the more we
// should boost our score.
scoredCandidate._alterScore(
library.namePieces.intersection(elementLocationPieces).length.toDouble() /
libraryNamePieces.intersection(elementLocationPieces).length.toDouble() /
elementLocationPieces.length.toDouble(),
_Reason.sharedNamePart,
);
Expand All @@ -90,7 +95,7 @@ abstract mixin class Canonicalization
// boost the score a little bit.
var scoreBoost = 0.0;
for (var piece in elementLocationPieces.expand((item) => item.split('_'))) {
for (var namePiece in library.namePieces) {
for (var namePiece in libraryNamePieces) {
if (piece.startsWith(namePiece)) {
scoreBoost += 0.001;
}
Expand All @@ -101,6 +106,9 @@ abstract mixin class Canonicalization
}
}

/// A pattern that can split [Locatable.location] strings.
final _locationSplitter = RegExp(r'(package:|[\\/;.])');

/// This class represents the score for a particular element; how likely
/// it is that this is the canonical element.
class _ScoredCandidate implements Comparable<_ScoredCandidate> {
Expand Down
1 change: 0 additions & 1 deletion lib/src/model/category.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class Category
Warnable,
CommentReferable,
Locatable,
Canonicalization,
MarkdownFileDocumentation,
LibraryContainer,
TopLevelContainer,
Expand Down
Loading

0 comments on commit d9e31ff

Please sign in to comment.