blob: c7b31b08dbfd270e75d9d7c2961a37214e63bb81 [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 'package:dartdoc/src/model/model.dart';
/// Classes extending this class have canonicalization support in Dartdoc.
abstract class Canonicalization implements Locatable, Documentable {
bool get isCanonical;
Library get canonicalLibrary;
List<ModelCommentReference> get commentRefs => null;
/// Pieces of the location, split to remove 'package:' and slashes.
Set<String> get locationPieces;
List<ScoredCandidate> scoreCanonicalCandidates(Iterable<Library> libraries) {
return libraries.map(_scoreElementWithLibrary).toList()..sort();
}
ScoredCandidate _scoreElementWithLibrary(Library lib) {
var scoredCandidate = ScoredCandidate(this, lib);
Iterable<String> resplit(Set<String> items) sync* {
for (var item in items) {
for (var subItem in item.split('_')) {
yield subItem;
}
}
}
// Large boost for @canonicalFor, essentially overriding all other concerns.
if (lib.canonicalFor.contains(fullyQualifiedName)) {
scoredCandidate._alterScore(5.0, 'marked @canonicalFor');
}
// Penalty for deprecated libraries.
if (lib.isDeprecated) scoredCandidate._alterScore(-1.0, 'is deprecated');
// Give a big boost if the library has the package name embedded in it.
if (lib.package.namePieces.intersection(lib.namePieces).isEmpty) {
scoredCandidate._alterScore(1.0, 'embeds package name');
}
// 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 * lib.namePieces.length, 'name is long');
// If we don't know the location of this element, return our best guess.
// TODO(jcollins-g): is that even possible?
assert(locationPieces.isNotEmpty);
if (locationPieces.isEmpty) return scoredCandidate;
// The more pieces we have of the location in our library name, the more we should boost our score.
scoredCandidate._alterScore(
lib.namePieces.intersection(locationPieces).length.toDouble() /
locationPieces.length.toDouble(),
'element location shares parts with name');
// If pieces of location at least start with elements of our library name, boost the score a little bit.
var scoreBoost = 0.0;
for (var piece in resplit(locationPieces)) {
for (var namePiece in lib.namePieces) {
if (piece.startsWith(namePiece)) {
scoreBoost += 0.001;
}
}
}
scoredCandidate._alterScore(
scoreBoost, 'element location parts start with parts of name');
return scoredCandidate;
}
@Deprecated(
'Public method intended to be private; will be removed as early as '
'Dartdoc 1.0.0')
ScoredCandidate scoreElementWithLibrary(Library lib) =>
_scoreElementWithLibrary(lib);
}
/// This class represents the score for a particular element; how likely
/// it is that this is the canonical element.
class ScoredCandidate implements Comparable<ScoredCandidate> {
final List<String> _reasons = [];
@Deprecated(
'Public field intended to be private; will be removed as early as '
'Dartdoc 1.0.0')
List<String> get reasons => _reasons;
@Deprecated(
'Public field intended to be private; will be removed as early as '
'Dartdoc 1.0.0')
set reasons(List<String> value) => reasons = value;
/// The canonicalization element being scored.
final Canonicalization _element;
@Deprecated(
'Public getter intended to be private; will be removed as early as '
'Dartdoc 1.0.0')
Canonicalization get element => _element;
final Library library;
/// The score accumulated so far. Higher means it is more likely that this
/// is the intended canonical Library.
double score = 0.0;
ScoredCandidate(this._element, this.library);
void _alterScore(double scoreDelta, String reason) {
score += scoreDelta;
if (scoreDelta != 0) {
_reasons.add(
"${reason} (${scoreDelta >= 0 ? '+' : ''}${scoreDelta.toStringAsPrecision(4)})");
}
}
@Deprecated(
'Public method intended to be private; will be removed as early as '
'Dartdoc 1.0.0')
void alterScore(double scoreDelta, String reason) =>
_alterScore(scoreDelta, reason);
@override
int compareTo(ScoredCandidate other) {
//assert(element == other.element);
return score.compareTo(other.score);
}
@override
String toString() =>
"${library.name}: ${score.toStringAsPrecision(4)} - ${_reasons.join(', ')}";
}