blob: fa657372f0cd7d0d92df5aad9dca30e647343e75 [file] [log] [blame]
// Copyright (c) 2015, 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.
// ignore_for_file: non_constant_identifier_names
library dartdoc.model_test;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:async/async.dart';
import 'package:collection/src/iterable_extensions.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/matching_link_result.dart';
import 'package:dartdoc/src/model/attribute.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/package_config_provider.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/render/category_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/warnings.dart';
import 'package:test/test.dart';
import '../src/utils.dart'
show bootBasicPackage, referenceLookup, kTestPackagePublicLibraries;
final _testPackageGraphMemo = AsyncMemoizer<PackageGraph>();
Future<PackageGraph> get testPackageGraph async =>
_testPackageGraphMemo.runOnce(() => bootBasicPackage('testing/test_package',
pubPackageMetaProvider, PhysicalPackageConfigProvider(),
excludeLibraries: ['css', 'code_in_comments'],
additionalArguments: ['--no-link-to-remote']));
/// For testing sort behavior.
class TestLibraryContainer extends LibraryContainer with Nameable {
@override
final List<String> containerOrder;
@override
String enclosingName;
@override
final String name;
@override
bool get isSdk => false;
@override
PackageGraph get packageGraph => throw UnimplementedError();
@override
Package get package => throw UnimplementedError();
TestLibraryContainer(
this.name, this.containerOrder, LibraryContainer? enclosingContainer)
: enclosingName = enclosingContainer?.name ?? '';
@override
DartdocOptionContext get config => throw UnimplementedError();
@override
String? get documentation => throw UnimplementedError();
@override
String get documentationAsHtml => throw UnimplementedError();
@override
bool get hasDocumentation => throw UnimplementedError();
@override
String? get href => throw UnimplementedError();
@override
bool get isDocumented => throw UnimplementedError();
@override
Kind get kind => throw UnimplementedError();
@override
String get oneLineDoc => throw UnimplementedError();
}
class TestLibraryContainerSdk extends TestLibraryContainer {
TestLibraryContainerSdk(super.name, super.containerOrder,
LibraryContainer super.enclosingContainer);
@override
bool get isSdk => true;
}
void main() {
late final PackageGraph packageGraph;
late final Library exLibrary;
late final Library fakeLibrary;
late final Library twoExportsLib;
late final Library baseClassLib;
late final Library dartAsync;
setUpAll(() async {
// Use model_special_cases_test.dart for tests that require
// a different package graph.
packageGraph = await testPackageGraph;
exLibrary = packageGraph.libraries.firstWhere((lib) => lib.name == 'ex');
fakeLibrary =
packageGraph.libraries.firstWhere((lib) => lib.name == 'fake');
dartAsync =
packageGraph.libraries.firstWhere((lib) => lib.name == 'dart:async');
twoExportsLib =
packageGraph.libraries.firstWhere((lib) => lib.name == 'two_exports');
baseClassLib =
packageGraph.libraries.firstWhere((lib) => lib.name == 'base_class');
});
group('PackageMeta and PackageGraph integration', () {
test('PackageMeta error messages generate correctly', () {
var message = packageGraph.packageMetaProvider
.getMessageForMissingPackageMeta(
fakeLibrary.element, packageGraph.config);
expect(message, contains('fake.dart'));
expect(message, contains('pub global activate dartdoc'));
});
});
group('triple-shift', () {
Library tripleShift;
late final Class C, E, F;
Extension ShiftIt;
late final Operator classShift, extensionShift;
late final Field constantTripleShifted;
setUpAll(() async {
tripleShift = (await testPackageGraph)
.libraries
.firstWhere((l) => l.name == 'triple_shift');
C = tripleShift.classes.firstWhere((c) => c.name == 'C');
E = tripleShift.classes.firstWhere((c) => c.name == 'E');
F = tripleShift.classes.firstWhere((c) => c.name == 'F');
ShiftIt = tripleShift.extensions.firstWhere((e) => e.name == 'ShiftIt');
classShift =
C.instanceOperators.firstWhere((o) => o.name.contains('>>>'));
extensionShift =
ShiftIt.instanceOperators.firstWhere((o) => o.name.contains('>>>'));
constantTripleShifted =
C.constantFields.firstWhere((f) => f.name == 'constantTripleShifted');
});
test('constants with triple shift render correctly', () {
expect(constantTripleShifted.constantValue, equals('3 &gt;&gt;&gt; 5'));
});
test('operators exist and are named correctly', () {
expect(classShift.name, equals('operator >>>'));
expect(extensionShift.name, equals('operator >>>'));
});
test('inheritance and overriding of triple shift operators works correctly',
() {
var tripleShiftE =
E.instanceOperators.firstWhere((o) => o.name.contains('>>>'));
var tripleShiftF =
F.instanceOperators.firstWhere((o) => o.name.contains('>>>'));
expect(tripleShiftE.isInherited, isTrue);
expect(tripleShiftE.canonicalModelElement, equals(classShift));
expect(tripleShiftE.modelType.returnType.name, equals('C'));
expect(tripleShiftF.isInherited, isFalse);
expect(tripleShiftF.modelType.returnType.name, equals('F'));
});
});
group('generic metadata', () {
late final Library genericMetadata;
late final TopLevelVariable f;
late final Typedef F;
late final Class C;
late final Method mp, mn;
setUpAll(() async {
genericMetadata = (await testPackageGraph)
.libraries
.firstWhere((l) => l.name == 'generic_metadata');
F = genericMetadata.typedefs.firstWhere((t) => t.name == 'F');
f = genericMetadata.properties.firstWhere((p) => p.name == 'f');
C = genericMetadata.classes.firstWhere((c) => c.name == 'C');
mp = C.instanceMethods.firstWhere((m) => m.name == 'mp');
mn = C.instanceMethods.firstWhere((m) => m.name == 'mn');
});
test(
'Verify annotations and their type arguments render on type parameters '
'for typedefs',
skip: 'dart-lang/sdk#46064', () {
expect((F.aliasedType as FunctionType).typeFormals.first.metadata,
isNotEmpty);
expect((F.aliasedType as FunctionType).parameters.first.metadata,
isNotEmpty);
// TODO(jcollins-g): add rendering verification once we have data from
// analyzer.
});
test('Verify type arguments on annotations renders, including parameters',
() {
var ab0 =
'@<a href="%%__HTMLBASE_dartdoc_internal__%%generic_metadata/A-class.html">A</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="%%__HTMLBASE_dartdoc_internal__%%generic_metadata/B.html">B</a></span>&gt;</span>(0)';
expect(genericMetadata.annotations.first.linkedNameWithParameters,
equals(ab0));
expect(f.annotations.first.linkedNameWithParameters, equals(ab0));
expect(C.annotations.first.linkedNameWithParameters, equals(ab0));
expect(C.typeParameters.first.annotations.first.linkedNameWithParameters,
equals(ab0));
expect(
mp.parameters
.map((p) => p.annotations.first.linkedNameWithParameters),
everyElement(equals(ab0)));
expect(
mn.parameters
.map((p) => p.annotations.first.linkedNameWithParameters),
everyElement(equals(ab0)));
});
});
group('generalized typedefs', () {
late final Library generalizedTypedefs;
late final Typedef T0, T1, T2, T3, T4, T5, T6, T7;
late final Class C, C2;
setUpAll(() {
generalizedTypedefs = packageGraph.libraries
.firstWhere((l) => l.name == 'generalized_typedefs');
T0 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T0');
T1 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T1');
T2 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T2');
T3 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T3');
T4 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T4');
T5 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T5');
T6 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T6');
T7 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T7');
C = generalizedTypedefs.classes.firstWhere((c) => c.name == 'C');
C2 = generalizedTypedefs.classes.firstWhere((c) => c.name == 'C2');
});
void expectTypedefs(Typedef t, String modelTypeToString,
Iterable<String> genericParameters) {
expect(t.modelType.toString(), equals(modelTypeToString));
expect(t.element.typeParameters.map((p) => p.toString()),
orderedEquals(genericParameters));
}
void expectAliasedTypeName(Aliased n, Matcher expected) {
expect(n.aliasElement.name, expected);
}
test('typedef references display aliases', () {
var g = C.instanceMethods.firstWhere((m) => m.name == 'g');
var c = C2.allFields.firstWhere((f) => f.name == 'c');
var d = C2.instanceMethods.firstWhere((f) => f.name == 'd');
expectAliasedTypeName(c.modelType as Aliased, equals('T1'));
expectAliasedTypeName(d.modelType.returnType as Aliased, equals('T2'));
expectAliasedTypeName(
d.parameters.first.modelType as Aliased, equals('T3'));
expectAliasedTypeName(
d.parameters.last.modelType as Aliased, equals('T4'));
expectAliasedTypeName(g.modelType.returnType as Aliased, equals('T1'));
expectAliasedTypeName(
g.modelType.parameters.first.modelType as Aliased, equals('T2'));
expectAliasedTypeName(
g.modelType.parameters.last.modelType as Aliased, equals('T3'));
});
test('typedef references to special types work',
skip: 'dart-lang/sdk#45291', () {
var a = generalizedTypedefs.properties.firstWhere((p) => p.name == 'a');
var b = C2.allFields.firstWhere((f) => f.name == 'b');
var f = C.allFields.firstWhere((f) => f.name == 'f');
expectAliasedTypeName(a.modelType as Aliased, equals('T0'));
expectAliasedTypeName(b.modelType as Aliased, equals('T0'));
expectAliasedTypeName(f.modelType as Aliased, equals('T0'));
});
test('basic non-function typedefs work', () {
expectTypedefs(T0, 'void', []);
expectTypedefs(T1, 'Function', []);
expectTypedefs(T2, 'List<X>', ['out X']);
expectTypedefs(T3, 'Map<X, Y>', ['out X', 'out Y']);
expectTypedefs(T4, 'void Function()', []);
expectTypedefs(T5, 'X Function(X, {X name})', ['inout X']);
expectTypedefs(T6, 'X Function(Y, [Map<Y, Y>])', ['out X', 'in Y']);
expectTypedefs(T7, 'X Function(Y, [Map<Y, Y>])',
['out X extends String', 'in Y extends List<X>']);
});
});
group('NNBD cases', () {
late final Library lateFinalWithoutInitializer,
nullSafetyClassMemberDeclarations,
nullableElements;
late final Class b;
late final Class c;
late final ModelFunction oddAsyncFunction, anotherOddFunction;
late final ModelFunction neverReturns, almostNeverReturns;
setUpAll(() async {
lateFinalWithoutInitializer = packageGraph.libraries
.firstWhere((lib) => lib.name == 'late_final_without_initializer');
nullSafetyClassMemberDeclarations = packageGraph.libraries
.firstWhere((lib) => lib.name == 'nnbd_class_member_declarations');
nullableElements = packageGraph.libraries
.firstWhere((lib) => lib.name == 'nullable_elements');
b = nullSafetyClassMemberDeclarations.allClasses
.firstWhere((c) => c.name == 'B');
c = nullSafetyClassMemberDeclarations.allClasses
.firstWhere((c) => c.name == 'C');
oddAsyncFunction = nullableElements.publicFunctions
.firstWhere((f) => f.name == 'oddAsyncFunction') as ModelFunction;
anotherOddFunction = nullableElements.publicFunctions
.firstWhere((f) => f.name == 'oddAsyncFunction') as ModelFunction;
neverReturns = nullableElements.publicFunctions
.firstWhere((f) => f.name == 'neverReturns') as ModelFunction;
almostNeverReturns = nullableElements.publicFunctions
.firstWhere((f) => f.name == 'almostNeverReturns') as ModelFunction;
});
test('Never types are allowed to have nullability markers', () {
expect(neverReturns.modelType.returnType.name, equals('Never'));
expect(neverReturns.modelType.returnType.nullabilitySuffix, equals(''));
expect(almostNeverReturns.modelType.returnType.name, equals('Never'));
expect(almostNeverReturns.modelType.returnType.nullabilitySuffix,
equals('?'));
});
test('old implied Future types have correct nullability', () {
expect(oddAsyncFunction.modelType.returnType.name, equals('dynamic'));
expect(
oddAsyncFunction.modelType.returnType.nullabilitySuffix, equals(''));
expect(anotherOddFunction.modelType.returnType.name, equals('dynamic'));
expect(anotherOddFunction.modelType.returnType.nullabilitySuffix,
equals(''));
});
test('method parameters with required', () {
var m1 = b.instanceMethods.firstWhere((m) => m.name == 'm1');
var p1 = m1.parameters.firstWhere((p) => p.name == 'p1');
var p2 = m1.parameters.firstWhere((p) => p.name == 'p2');
expect(p1.isRequiredNamed, isTrue);
expect(p2.isRequiredNamed, isFalse);
expect(p2.isNamed, isTrue);
expect(
m1.linkedParamsLines,
equals(
'<ol class="parameter-list"><li><span class="parameter" id="m1-param-some"><span class="type-annotation">int</span> <span class="parameter-name">some</span>, </span></li>\n'
'<li><span class="parameter" id="m1-param-regular"><span class="type-annotation">dynamic</span> <span class="parameter-name">regular</span>, </span></li>\n'
'<li><span class="parameter" id="m1-param-parameters"><span>covariant</span> <span class="type-annotation">dynamic</span> <span class="parameter-name">parameters</span>, </span></li>\n'
'<li><span class="parameter" id="m1-param-p1">{<span>required</span> <span class="type-annotation">dynamic</span> <span class="parameter-name">p1</span>, </span></li>\n'
'<li><span class="parameter" id="m1-param-p2"><span class="type-annotation">int</span> <span class="parameter-name">p2</span> = <span class="default-value">3</span>, </span></li>\n'
'<li><span class="parameter" id="m1-param-p3"><span>required</span> <span>covariant</span> <span class="type-annotation">dynamic</span> <span class="parameter-name">p3</span>, </span></li>\n'
'<li><span class="parameter" id="m1-param-p4"><span>required</span> <span>covariant</span> <span class="type-annotation">int</span> <span class="parameter-name">p4</span>}</span></li>\n'
'</ol>'));
});
test('verify no regression on ordinary optionals', () {
var m2 = b.instanceMethods.firstWhere((m) => m.name == 'm2');
var sometimes = m2.parameters.firstWhere((p) => p.name == 'sometimes');
var optionals = m2.parameters.firstWhere((p) => p.name == 'optionals');
expect(sometimes.isRequiredNamed, isFalse);
expect(sometimes.isRequiredPositional, isTrue);
expect(sometimes.isOptionalPositional, isFalse);
expect(optionals.isRequiredNamed, isFalse);
expect(optionals.isRequiredPositional, isFalse);
expect(optionals.isOptionalPositional, isTrue);
expect(
m2.linkedParamsLines,
equals(
'<ol class="parameter-list"><li><span class="parameter" id="m2-param-sometimes"><span class="type-annotation">int</span> <span class="parameter-name">sometimes</span>, </span></li>\n'
'<li><span class="parameter" id="m2-param-we"><span class="type-annotation">dynamic</span> <span class="parameter-name">we</span>, </span></li>\n'
'<li><span class="parameter" id="m2-param-have">[<span class="type-annotation">String</span> <span class="parameter-name">have</span>, </span></li>\n'
'<li><span class="parameter" id="m2-param-optionals"><span class="type-annotation">double</span> <span class="parameter-name">optionals</span>]</span></li>\n'
'</ol>'));
});
test('anonymous callback parameters are correctly marked as nullable', () {
var m3 = c.instanceMethods.firstWhere((m) => m.name == 'm3');
var listen = m3.parameters.firstWhere((p) => p.name == 'listen');
var onDone = m3.parameters.firstWhere((p) => p.name == 'onDone');
expect(listen.isRequiredPositional, isTrue);
expect(onDone.isNamed, isTrue);
expect(m3.linkedParamsLines, contains('</ol>\n)?, '));
expect(m3.linkedParamsLines, contains('</ol>\n)?}</span>'));
});
test('Late final class member test', () {
var c = lateFinalWithoutInitializer.allClasses
.firstWhere((c) => c.name == 'C');
var a = c.instanceFields.firstWhere((f) => f.name == 'a');
var b = c.instanceFields.firstWhere((f) => f.name == 'b');
var cField = c.instanceFields.firstWhere((f) => f.name == 'cField');
var dField = c.instanceFields.firstWhere((f) => f.name == 'dField');
// If Null safety isn't enabled, fields named 'late' come back from the
// analyzer instead of setting up 'isLate'.
expect(c.instanceFields.any((f) => f.name == 'late'), isFalse);
expect(a.modelType.name, equals('dynamic'));
expect(a.isLate, isTrue);
expect(a.attributes, contains(Attribute.late_));
expect(a.attributes, isNot(contains(Attribute.getterSetterPair)));
expect(b.modelType.name, equals('int'));
expect(b.isLate, isTrue);
expect(b.attributes, contains(Attribute.late_));
expect(b.attributes, isNot(contains(Attribute.getterSetterPair)));
expect(cField.modelType.name, equals('dynamic'));
expect(cField.isLate, isTrue);
expect(cField.attributes, contains(Attribute.late_));
expect(cField.attributes, isNot(contains(Attribute.getterSetterPair)));
expect(dField.modelType.name, equals('double'));
expect(dField.isLate, isTrue);
expect(dField.attributes, contains(Attribute.late_));
expect(dField.attributes, isNot(contains(Attribute.getterSetterPair)));
});
test('Late final top level variables', () {
var initializeMe = lateFinalWithoutInitializer.publicProperties
.firstWhere((v) => v.name == 'initializeMe');
expect(initializeMe.modelType.name, equals('String'));
expect(initializeMe.isLate, isTrue);
expect(initializeMe.attributes, contains(Attribute.late_));
expect(
initializeMe.attributes, isNot(contains(Attribute.getterSetterPair)));
});
test('complex nullable elements are detected and rendered correctly', () {
var complexNullableMembers = nullableElements.allClasses
.firstWhere((c) => c.name == 'ComplexNullableMembers');
expect(
complexNullableMembers.nameWithGenerics,
equals(
'ComplexNullableMembers&lt;<wbr><span class="type-parameter">T extends String?</span>&gt;'));
});
test('simple nullable elements are detected and rendered correctly', () {
var nullableMembers = nullableElements.allClasses
.firstWhere((c) => c.name == 'NullableMembers');
var methodWithNullables = nullableMembers.publicInstanceMethods
.firstWhere((f) => f.name == 'methodWithNullables');
var operatorStar = nullableMembers.publicInstanceOperators
.firstWhere((f) => f.name == 'operator *');
expect(
methodWithNullables.linkedParams,
equals(
'<span class="parameter" id="methodWithNullables-param-foo"><span class="type-annotation">String?</span> <span class="parameter-name">foo</span></span>'));
expect(
operatorStar.linkedParams,
equals(
'<span class="parameter" id="*-param-nullableOther"><span class="type-annotation"><a href="%%__HTMLBASE_dartdoc_internal__%%nullable_elements/NullableMembers-class.html">NullableMembers</a>?</span> <span class="parameter-name">nullableOther</span></span>'));
});
});
group('Set Literals', () {
late final Library set_literals;
late final TopLevelVariable aComplexSet,
inferredTypeSet,
specifiedSet,
untypedMap,
untypedMapWithoutConst,
typedSet;
setUpAll(() async {
set_literals = packageGraph.libraries
.firstWhere((lib) => lib.name == 'set_literals');
aComplexSet =
set_literals.constants.firstWhere((v) => v.name == 'aComplexSet');
inferredTypeSet =
set_literals.constants.firstWhere((v) => v.name == 'inferredTypeSet');
specifiedSet =
set_literals.constants.firstWhere((v) => v.name == 'specifiedSet');
untypedMap =
set_literals.constants.firstWhere((v) => v.name == 'untypedMap');
untypedMapWithoutConst = set_literals.constants
.firstWhere((v) => v.name == 'untypedMapWithoutConst');
typedSet = set_literals.constants.firstWhere((v) => v.name == 'typedSet');
});
test('Set literals test', () {
expect(aComplexSet.modelType.name, equals('Set'));
expect(
(aComplexSet.modelType as ParameterizedElementType)
.typeArguments
.map((a) => a.name)
.toList(),
equals(['AClassContainingLiterals']));
expect(aComplexSet.constantValue,
equals('const {const AClassContainingLiterals(3, 5)}'));
expect(inferredTypeSet.modelType.name, equals('Set'));
expect(
(inferredTypeSet.modelType as ParameterizedElementType)
.typeArguments
.map((a) => a.name)
.toList(),
equals(['int']));
expect(inferredTypeSet.constantValue, equals('const {1, 3, 5}'));
expect(specifiedSet.modelType.name, equals('Set'));
expect(
(specifiedSet.modelType as ParameterizedElementType)
.typeArguments
.map((a) => a.name)
.toList(),
equals(['int']));
expect(specifiedSet.constantValue, equals('const {}'));
// The analyzer is allowed to return a string with or without leading
// `const` here.
expect(
untypedMapWithoutConst.constantValue, matches(RegExp('(const )?{}')));
expect(untypedMap.modelType.name, equals('Map'));
expect(
(untypedMap.modelType as ParameterizedElementType)
.typeArguments
.map((a) => a.name)
.toList(),
equals(['dynamic', 'dynamic']));
expect(untypedMap.constantValue, equals('const {}'));
expect(typedSet.modelType.name, equals('Set'));
expect(
(typedSet.modelType as ParameterizedElementType)
.typeArguments
.map((a) => a.name)
.toList(),
equals(['String']));
expect(typedSet.constantValue,
matches(RegExp(r'const &lt;String&gt;\s?{}')));
});
});
group('Tools', () {
late final Class toolUser;
late final Class NonCanonicalToolUser,
CanonicalToolUser,
PrivateLibraryToolUser;
late final Class ImplementingClassForTool,
CanonicalPrivateInheritedToolUser;
late final Method invokeTool;
late final Method invokeToolNoInput;
late final Method invokeToolMultipleSections;
late final Method invokeToolNonCanonical, invokeToolNonCanonicalSubclass;
late final Method invokeToolPrivateLibrary,
invokeToolPrivateLibraryOriginal;
late final Method invokeToolParentDoc, invokeToolParentDocOriginal;
// ignore: omit_local_variable_types
final RegExp packageInvocationIndexRegexp =
RegExp(r'PACKAGE_INVOCATION_INDEX: (\d+)');
setUpAll(() {
NonCanonicalToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == '_NonCanonicalToolUser');
CanonicalToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'CanonicalToolUser');
PrivateLibraryToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'PrivateLibraryToolUser');
ImplementingClassForTool = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'ImplementingClassForTool');
CanonicalPrivateInheritedToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'CanonicalPrivateInheritedToolUser');
toolUser = exLibrary.classes.firstWhere((c) => c.name == 'ToolUser');
invokeTool =
toolUser.instanceMethods.firstWhere((m) => m.name == 'invokeTool');
invokeToolNonCanonical = NonCanonicalToolUser.instanceMethods
.firstWhere((m) => m.name == 'invokeToolNonCanonical');
invokeToolNonCanonicalSubclass = CanonicalToolUser.instanceMethods
.firstWhere((m) => m.name == 'invokeToolNonCanonical');
invokeToolNoInput = toolUser.instanceMethods
.firstWhere((m) => m.name == 'invokeToolNoInput');
invokeToolMultipleSections = toolUser.instanceMethods
.firstWhere((m) => m.name == 'invokeToolMultipleSections');
invokeToolPrivateLibrary = PrivateLibraryToolUser.instanceMethods
.firstWhere((m) => m.name == 'invokeToolPrivateLibrary');
invokeToolPrivateLibraryOriginal =
(invokeToolPrivateLibrary.definingEnclosingContainer as Class)
.instanceMethods
.firstWhere((m) => m.name == 'invokeToolPrivateLibrary');
invokeToolParentDoc = CanonicalPrivateInheritedToolUser.instanceMethods
.firstWhere((m) => m.name == 'invokeToolParentDoc');
invokeToolParentDocOriginal = ImplementingClassForTool.instanceMethods
.firstWhere((m) => m.name == 'invokeToolParentDoc');
for (var modelElement in packageGraph.allLocalModelElements) {
modelElement.documentation;
}
});
test(
'invokes tool when inherited documentation is the only means for it to be seen',
() {
// Verify setup of the test is correct.
expect(invokeToolParentDoc.isCanonical, isTrue);
expect(invokeToolParentDoc.hasDocumentationComment, isFalse);
// Error message here might look strange due to toString() on Methods, but if this
// fails that means we don't have the correct invokeToolParentDoc instance.
expect(invokeToolParentDoc.documentationFrom,
contains(invokeToolParentDocOriginal));
// Tool should be substituted out here.
expect(invokeToolParentDoc.documentation, isNot(contains('{@tool')));
});
group('does _not_ invoke a tool multiple times unnecessarily', () {
test('non-canonical subclass case', () {
expect(invokeToolNonCanonical.isCanonical, isFalse);
expect(invokeToolNonCanonicalSubclass.isCanonical, isTrue);
expect(
packageInvocationIndexRegexp
.firstMatch(invokeToolNonCanonical.documentation)!
.group(1),
equals(packageInvocationIndexRegexp
.firstMatch(invokeToolNonCanonicalSubclass.documentation)!
.group(1)));
expect(
invokeToolPrivateLibrary.documentation, isNot(contains('{@tool')));
expect(
invokeToolPrivateLibraryOriginal.documentation, contains('{@tool'));
});
test('Documentation borrowed from implementer case', () {
expect(
packageInvocationIndexRegexp
.firstMatch(invokeToolParentDoc.documentation)!
.group(1),
equals(packageInvocationIndexRegexp
.firstMatch(invokeToolParentDocOriginal.documentation)!
.group(1)));
});
});
test('can invoke a tool and pass args and environment', () {
expect(invokeTool.documentation, contains('--file=<INPUT_FILE>'));
expect(invokeTool.documentation,
contains(RegExp(r'--source=lib[/\\]example\.dart_[0-9]+_[0-9]+, ')));
expect(invokeTool.documentation,
contains(RegExp(r'--package-path=<PACKAGE_PATH>, ')));
expect(
invokeTool.documentation, contains('--package-name=test_package, '));
expect(invokeTool.documentation, contains('--library-name=ex, '));
expect(invokeTool.documentation,
contains('--element-name=ToolUser.invokeTool, '));
expect(invokeTool.documentation,
contains(r'''--special= |\[]!@#\"'$%^&*()_+]'''));
expect(invokeTool.documentation, contains('INPUT: <INPUT_FILE>'));
expect(invokeTool.documentation,
contains(RegExp('SOURCE_COLUMN: [0-9]+, ')));
expect(invokeTool.documentation,
contains(RegExp(r'SOURCE_PATH: lib[/\\]example\.dart, ')));
expect(invokeTool.documentation,
contains(RegExp(r'PACKAGE_PATH: <PACKAGE_PATH>, ')));
expect(
invokeTool.documentation, contains('PACKAGE_NAME: test_package, '));
expect(invokeTool.documentation, contains('LIBRARY_NAME: ex, '));
expect(invokeTool.documentation,
contains('ELEMENT_NAME: ToolUser.invokeTool, '));
expect(invokeTool.documentation,
contains(RegExp('INVOCATION_INDEX: [0-9]+}')));
expect(invokeTool.documentation, contains('## `Yes it is a [Dog]!`'));
});
test('can invoke a tool and add a reference link', () {
expect(invokeTool.documentation,
contains('Yes it is a [Dog]! Is not a [ToolUser].'));
expect(
invokeTool.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}ex/ToolUser-class.html">ToolUser</a>'));
expect(
invokeTool.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}ex/Dog-class.html">Dog</a>'));
});
test(r'can invoke a tool with no $INPUT or args', () {
expect(invokeToolNoInput.documentation, contains('Args: []'));
expect(invokeToolNoInput.documentation,
isNot(contains('This text should not appear in the output')));
expect(invokeToolNoInput.documentation, isNot(contains('[Dog]')));
expect(
invokeToolNoInput.documentationAsHtml,
isNot(contains(
'<a href="${htmlBasePlaceholder}ex/Dog-class.html">Dog</a>')));
});
test('can invoke a tool multiple times in one comment block', () {
var envLine = RegExp(r'^Env: \{', multiLine: true);
expect(envLine.allMatches(invokeToolMultipleSections.documentation),
hasLength(2));
var argLine = RegExp(r'^Args: \[', multiLine: true);
expect(argLine.allMatches(invokeToolMultipleSections.documentation),
hasLength(2));
expect(invokeToolMultipleSections.documentation,
contains('Invokes more than one tool in the same comment block.'));
expect(invokeToolMultipleSections.documentation,
contains('This text should appear in the output.'));
expect(invokeToolMultipleSections.documentation,
contains('## `This text should appear in the output.`'));
expect(invokeToolMultipleSections.documentation,
contains('This text should also appear in the output.'));
expect(invokeToolMultipleSections.documentation,
contains('## `This text should also appear in the output.`'));
});
});
group('Missing and Remote', () {
test(
'Verify that SDK libraries are not canonical when documenting a package',
() {
expect(
dartAsync.package.documentedWhere, equals(DocumentLocation.missing));
expect(dartAsync.isCanonical, isFalse);
});
test('Verify that packageGraph has an SDK but will not document it locally',
() {
expect(packageGraph.packages.firstWhere((p) => p.isSdk).documentedWhere,
isNot(equals(DocumentLocation.local)));
});
});
group('Category', () {
test('Verify categories for test_package', () {
expect(packageGraph.localPackages, hasLength(1));
expect(packageGraph.localPackages.first.hasCategories, isTrue);
var packageCategories = packageGraph.localPackages.first.categories;
expect(packageCategories, hasLength(6));
expect(packageGraph.localPackages.first.categoriesWithPublicLibraries,
hasLength(3));
expect(
packageCategories.map((c) => c.name).toList(),
orderedEquals([
'Superb',
'Unreal',
'Real Libraries',
'Misc',
'More Excellence',
'NotSoExcellent'
]));
expect(
packageCategories.map((c) => c.libraries).toList(),
orderedEquals([
hasLength(0),
hasLength(2),
hasLength(3),
hasLength(1),
hasLength(0),
hasLength(0)
]));
});
test('Verify libraries with multiple categories show up in multiple places',
() {
var packageCategories = packageGraph.publicPackages.first.categories;
var realLibraries =
packageCategories.firstWhere((c) => c.name == 'Real Libraries');
var misc = packageCategories.firstWhere((c) => c.name == 'Misc');
expect(
realLibraries.libraries.map((l) => l.name), contains('two_exports'));
expect(misc.libraries.map((l) => l.name), contains('two_exports'));
});
test('Verify that libraries without categories get handled', () {
expect(
packageGraph
.localPackages.first.defaultCategory.publicLibraries.length,
// Only 5 libraries have categories, the rest belong in default.
equals(kTestPackagePublicLibraries - 5));
});
// TODO consider moving these to a separate suite
test('CategoryRendererHtml renders category label', () {
var category = packageGraph.publicPackages.first.categories.first;
var renderer = CategoryRendererHtml();
expect(
renderer.renderCategoryLabel(category),
'<span class="category superb cp-0 linked" title="This is part of the Superb topic.">'
'<a href="${htmlBasePlaceholder}topics/Superb-topic.html">Superb</a></span>');
});
test('CategoryRendererHtml renders linkedName', () {
var category = packageGraph.publicPackages.first.categories.first;
var renderer = CategoryRendererHtml();
expect(renderer.renderLinkedName(category),
'<a href="${htmlBasePlaceholder}topics/Superb-topic.html">Superb</a>');
});
test('CategoryRendererMd renders category label', () {
var category = packageGraph.publicPackages.first.categories.first;
var renderer = CategoryRendererMd();
expect(renderer.renderCategoryLabel(category),
'[Superb](${htmlBasePlaceholder}topics/Superb-topic.html)');
});
test('CategoryRendererMd renders linkedName', () {
var category = packageGraph.publicPackages.first.categories.first;
var renderer = CategoryRendererMd();
expect(renderer.renderLinkedName(category),
'[Superb](${htmlBasePlaceholder}topics/Superb-topic.html)');
});
});
group('LibraryContainer', () {
late final TestLibraryContainer topLevel;
var sortOrderBasic = ['theFirst', 'second', 'fruit'];
var containerNames = [
'moo',
'woot',
'theFirst',
'topLevel Things',
'toplevel',
'fruit'
];
setUpAll(() {
topLevel = TestLibraryContainer('topLevel', [], null);
});
test('multiple containers with specified sort order', () {
var containers = <LibraryContainer>[];
for (var i = 0; i < containerNames.length; i++) {
var name = containerNames[i];
containers.add(TestLibraryContainer(name, sortOrderBasic, topLevel));
}
containers.add(TestLibraryContainerSdk('SDK', sortOrderBasic, topLevel));
containers.sort();
expect(
containers.map((c) => c.name),
orderedEquals([
'theFirst',
'fruit',
'toplevel',
'SDK',
'topLevel Things',
'moo',
'woot'
]));
});
test('multiple containers, no specified sort order', () {
var containers = <LibraryContainer>[];
for (var name in containerNames) {
containers.add(TestLibraryContainer(name, [], topLevel));
}
containers.add(TestLibraryContainerSdk('SDK', [], topLevel));
containers.sort();
expect(
containers.map((c) => c.name),
orderedEquals([
'toplevel',
'SDK',
'topLevel Things',
'fruit',
'moo',
'theFirst',
'woot'
]));
});
});
group('Library', () {
late final Library anonLib,
isDeprecated,
someLib,
reexportOneLib,
reexportTwoLib,
reexportThreeLib;
late final Class SomeClass,
SomeOtherClass,
YetAnotherClass,
AUnicornClass,
ADuplicateClass;
setUpAll(() {
anonLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'anonymous_library');
someLib = packageGraph.allLibraries.values
.firstWhere((lib) => lib.name == 'reexport.somelib');
reexportOneLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'reexport_one');
reexportTwoLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'reexport_two');
reexportThreeLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'reexport_three');
SomeClass = someLib.getClassByName('SomeClass');
SomeOtherClass = someLib.getClassByName('SomeOtherClass');
YetAnotherClass = someLib.getClassByName('YetAnotherClass');
AUnicornClass = someLib.getClassByName('AUnicornClass');
ADuplicateClass = reexportThreeLib.getClassByName('ADuplicateClass');
isDeprecated = packageGraph.libraries
.firstWhere((lib) => lib.name == 'is_deprecated');
});
test('has a name', () {
expect(exLibrary.name, 'ex');
});
test('has a line number and column', () {
expect(exLibrary.characterLocation, isNotNull);
});
test('can be deprecated', () {
expect(isDeprecated.isDeprecated, isTrue);
expect(anonLib.isDeprecated, isFalse);
});
test('can be reexported even if the file suffix is not .dart', () {
expect(fakeLibrary.allClasses.map((c) => c.name),
contains('MyClassFromADartFile'));
});
test('that is deprecated has a deprecated css class in linkedName', () {
expect(isDeprecated.linkedName, contains('class="deprecated"'));
});
test('has properties', () {
expect(exLibrary.hasPublicProperties, isTrue);
});
test('has constants', () {
expect(exLibrary.hasPublicConstants, isTrue);
});
test('has exceptions', () {
expect(exLibrary.hasPublicExceptions, isTrue);
});
test('has mixins', () {
expect(fakeLibrary.hasPublicMixins, isTrue);
expect(reexportTwoLib.hasPublicMixins, isTrue);
});
test('has functions', () {
expect(exLibrary.hasPublicFunctions, isTrue);
});
test('has typedefs', () {
expect(exLibrary.hasPublicTypedefs, isTrue);
});
test('exported class', () {
expect(exLibrary.classes.any((c) => c.name == 'Helper'), isTrue);
});
test('exported function', () {
expect(
exLibrary.functions.any((f) => f.name == 'helperFunction'), isFalse);
});
test('anonymous lib', () {
expect(anonLib.isAnonymous, isTrue);
});
test('with ambiguous reexport warnings', () {
final warningMsg =
'(reexport_one, reexport_two) -> reexport_two (confidence 0.000)';
// Unicorn class has a warning because two @canonicalFors cancel each other out.
expect(
packageGraph.packageWarningCounter.hasWarning(
AUnicornClass, PackageWarning.ambiguousReexport, warningMsg),
isTrue);
// This class is ambiguous without a @canonicalFor
expect(
packageGraph.packageWarningCounter.hasWarning(
YetAnotherClass, PackageWarning.ambiguousReexport, warningMsg),
isTrue);
// These two classes have a @canonicalFor
expect(
packageGraph.packageWarningCounter.hasWarning(
SomeClass, PackageWarning.ambiguousReexport, warningMsg),
isFalse);
expect(
packageGraph.packageWarningCounter.hasWarning(
SomeOtherClass, PackageWarning.ambiguousReexport, warningMsg),
isFalse);
// This library has a canonicalFor with no corresponding item
expect(
packageGraph.packageWarningCounter.hasWarning(reexportTwoLib,
PackageWarning.ignoredCanonicalFor, 'something.ThatDoesntExist'),
isTrue);
});
test('can import other libraries with unusual URIs', () {
final fakeLibraryImportedExported = <Library>{
for (final l in <LibraryElement>{
...fakeLibrary.element.importedLibraries,
...fakeLibrary.element.exportedLibraries
})
packageGraph.modelBuilder.fromElement(l) as Library
};
expect(fakeLibraryImportedExported.any((l) => l.name == 'import_unusual'),
isTrue);
});
test('@canonicalFor directive works', () {
expect(SomeOtherClass.canonicalLibrary, reexportOneLib);
expect(SomeClass.canonicalLibrary, reexportTwoLib);
});
test('with correct show/hide behavior', () {
expect(ADuplicateClass.definingLibrary.name, equals('shadowing_lib'));
});
});
group('Macros', () {
late final Class dog, ClassTemplateOneLiner;
late final Enum MacrosFromAccessors;
late final Method withMacro,
withMacro2,
withPrivateMacro,
withUndefinedMacro;
late final EnumField macroReferencedHere;
setUpAll(() {
dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
withMacro = dog.instanceMethods.firstWhere((m) => m.name == 'withMacro');
withMacro2 =
dog.instanceMethods.firstWhere((m) => m.name == 'withMacro2');
withPrivateMacro =
dog.instanceMethods.firstWhere((m) => m.name == 'withPrivateMacro');
withUndefinedMacro =
dog.instanceMethods.firstWhere((m) => m.name == 'withUndefinedMacro');
MacrosFromAccessors =
fakeLibrary.enums.firstWhere((e) => e.name == 'MacrosFromAccessors');
macroReferencedHere = MacrosFromAccessors.publicEnumValues
.firstWhere((e) => e.name == 'macroReferencedHere') as EnumField;
ClassTemplateOneLiner = exLibrary.allClasses
.firstWhere((c) => c.name == 'ClassTemplateOneLiner');
});
test('via reexport does not leave behind template crumbs', () {
expect(ClassTemplateOneLiner.isCanonical, isFalse);
expect(
ClassTemplateOneLiner.oneLineDoc,
equals(
'I had better not have a template directive in my one liner.'));
});
test('renders a macro defined within a enum', () {
expect(macroReferencedHere.documentationAsHtml,
contains('This is a macro defined in an Enum accessor.'));
});
test("renders a macro within the same comment where it's defined", () {
expect(withMacro.documentation,
equals('Macro method\n\nFoo macro content\n\nMore docs'));
});
test("renders a macro in another method, not the same where it's defined",
() {
expect(withMacro2.documentation, equals('Foo macro content'));
});
test('renders a macro defined in a private symbol', () {
expect(withPrivateMacro.documentation, contains('Private macro content'));
});
test('a warning is generated for unknown macros', () {
// Retrieve documentation first to generate the warning.
withUndefinedMacro.documentation;
expect(
packageGraph.packageWarningCounter.hasWarning(withUndefinedMacro,
PackageWarning.unknownMacro, 'ThatDoesNotExist'),
isTrue);
});
});
group('YouTube', () {
Class dog;
late final Method withYouTubeWatchUrl;
late final Method withYouTubeInOneLineDoc;
late final Method withYouTubeInline;
setUpAll(() {
dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
withYouTubeWatchUrl = dog.instanceMethods
.firstWhere((m) => m.name == 'withYouTubeWatchUrl');
withYouTubeInOneLineDoc = dog.instanceMethods
.firstWhere((m) => m.name == 'withYouTubeInOneLineDoc');
withYouTubeInline =
dog.instanceMethods.firstWhere((m) => m.name == 'withYouTubeInline');
});
test(
'renders a YouTube video within the method documentation with correct max height/width',
() {
expect(
withYouTubeWatchUrl.documentation,
contains(
'<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"',
),
);
expect(
withYouTubeWatchUrl.documentation,
contains('max-width: 560px;'),
);
expect(
withYouTubeWatchUrl.documentation,
contains('max-height: 315px;'),
);
});
test("Doesn't place YouTube video in one line doc", () {
expect(
withYouTubeInOneLineDoc.oneLineDoc,
isNot(
contains(
'<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"',
),
),
);
expect(
withYouTubeInOneLineDoc.documentation,
contains(
'<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"',
),
);
});
test('Handles YouTube video inline properly', () {
// Make sure it doesn't have a double-space before the continued line,
// which would indicate to Markdown to indent the line.
expect(withYouTubeInline.documentation, isNot(contains(' works')));
});
});
group('Animation', () {
late final Method withAnimationInOneLineDoc;
late final Method withAnimationInline;
setUpAll(() {
var dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
withAnimationInOneLineDoc = dog.instanceMethods
.firstWhere((m) => m.name == 'withAnimationInOneLineDoc');
withAnimationInline = dog.instanceMethods
.firstWhere((m) => m.name == 'withAnimationInline');
});
test("Doesn't place animations in one line doc", () {
expect(withAnimationInOneLineDoc.oneLineDoc, isNot(contains('<video')));
expect(withAnimationInOneLineDoc.documentation, contains('<video'));
});
test('Handles animations inline properly', () {
// Make sure it doesn't have a double-space before the continued line,
// which would indicate to Markdown to indent the line.
expect(withAnimationInline.documentation, isNot(contains(' works')));
});
});
group('MultiplyInheritedExecutableElement handling', () {
late final Class BaseThingy, BaseThingy2, ImplementingThingy2;
late final Method aImplementingThingyMethod;
late final Field aImplementingThingyField;
late final Field aImplementingThingy;
late final Accessor aImplementingThingyAccessor;
setUpAll(() {
BaseThingy =
fakeLibrary.classes.firstWhere((c) => c.name == 'BaseThingy');
BaseThingy2 =
fakeLibrary.classes.firstWhere((c) => c.name == 'BaseThingy2');
ImplementingThingy2 = fakeLibrary.classes
.firstWhere((c) => c.name == 'ImplementingThingy2');
aImplementingThingy = ImplementingThingy2.instanceFields
.firstWhere((m) => m.name == 'aImplementingThingy');
aImplementingThingyMethod = ImplementingThingy2.instanceMethods
.firstWhere((m) => m.name == 'aImplementingThingyMethod');
aImplementingThingyField = ImplementingThingy2.instanceFields
.firstWhere((m) => m.name == 'aImplementingThingyField');
aImplementingThingyAccessor = aImplementingThingyField.getter!;
});
test('Verify behavior of imperfect resolver', () {
expect(aImplementingThingy.element.enclosingElement,
equals(BaseThingy2.element));
expect(aImplementingThingyMethod.element.enclosingElement,
equals(BaseThingy.element));
expect(aImplementingThingyField.element.enclosingElement,
equals(BaseThingy.element));
expect(aImplementingThingyAccessor.element.enclosingElement,
equals(BaseThingy.element));
});
});
group('Docs as HTML', () {
late final Class Apple, B, superAwesomeClass, foo2;
late final TopLevelVariable incorrectDocReferenceFromEx;
late final ModelFunction thisIsAsync;
late final ModelFunction topLevelFunction;
late final Class extendedClass;
late final TopLevelVariable testingCodeSyntaxInOneLiners;
late final Class specialList;
Class baseForDocComments;
late final Method doAwesomeStuff;
late final Class subForDocComments;
late final ModelFunction short;
setUpAll(() {
incorrectDocReferenceFromEx = exLibrary.constants
.firstWhere((c) => c.name == 'incorrectDocReferenceFromEx');
B = exLibrary.classes.firstWhere((c) => c.name == 'B');
Apple = exLibrary.classes.firstWhere((c) => c.name == 'Apple');
specialList =
fakeLibrary.classes.firstWhere((c) => c.name == 'SpecialList');
topLevelFunction =
fakeLibrary.functions.firstWhere((f) => f.name == 'topLevelFunction');
thisIsAsync =
fakeLibrary.functions.firstWhere((f) => f.name == 'thisIsAsync');
testingCodeSyntaxInOneLiners = fakeLibrary.constants
.firstWhere((c) => c.name == 'testingCodeSyntaxInOneLiners');
superAwesomeClass = fakeLibrary.classes
.firstWhere((cls) => cls.name == 'SuperAwesomeClass');
foo2 = fakeLibrary.classes.firstWhere((cls) => cls.name == 'Foo2');
extendedClass = twoExportsLib.allClasses
.firstWhere((clazz) => clazz.name == 'ExtendingClass');
subForDocComments =
fakeLibrary.classes.firstWhere((c) => c.name == 'SubForDocComments');
baseForDocComments =
fakeLibrary.classes.firstWhere((c) => c.name == 'BaseForDocComments');
doAwesomeStuff = baseForDocComments.instanceMethods
.firstWhere((m) => m.name == 'doAwesomeStuff');
short = fakeLibrary.functions.firstWhere((f) => f.name == 'short');
});
group('markdown extensions', () {
late final Class DocumentWithATable;
late final String docsAsHtml;
setUpAll(() {
DocumentWithATable = fakeLibrary.classes
.firstWhere((cls) => cls.name == 'DocumentWithATable');
docsAsHtml = DocumentWithATable.documentationAsHtml;
});
test('Verify table appearance', () {
expect(
docsAsHtml,
contains('<table>\n<thead>\n<tr>\n<th>Component</th>'),
);
});
test('Verify links inside of table headers', () {
expect(
docsAsHtml,
contains(
'<th><a href="${htmlBasePlaceholder}fake/Annotation-class.html">Annotation</a></th>'));
});
test('Verify links inside of table body', () {
expect(
docsAsHtml,
contains('<tbody>\n'
'<tr>\n'
'<td><a href="${htmlBasePlaceholder}fake/DocumentWithATable/foo-constant.html">foo</a></td>'),
);
});
test('Verify there is no emoji support', () {
var tpvar = fakeLibrary.constants
.firstWhere((t) => t.name == 'hasMarkdownInDoc');
var tpvarDocsAsHtml = tpvar.documentationAsHtml;
expect(tpvarDocsAsHtml, contains('3ffe:2a00:100:7031::1'));
});
});
group('Comment processing', () {
test('can virtually add nodoc via options file', () {
var NodocMeLibrary = packageGraph.defaultPackage.allLibraries
.firstWhere((l) => l.name == 'nodocme');
expect(NodocMeLibrary.hasNodoc, isTrue);
var NodocMeImplementation = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'NodocMeImplementation');
expect(NodocMeImplementation.hasNodoc, isTrue);
expect(NodocMeImplementation.isPublic, isFalse);
var MeNeitherEvenWithoutADocComment = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'MeNeitherEvenWithoutADocComment');
expect(MeNeitherEvenWithoutADocComment.hasNodoc, isTrue);
expect(MeNeitherEvenWithoutADocComment.isPublic, isFalse);
});
});
group('doc references', () {
late final String docsAsHtml;
setUpAll(() {
docsAsHtml = doAwesomeStuff.documentationAsHtml;
});
test('Verify links to inherited members inside class', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}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(doAwesomeStuffWarnings
.containsKey(PackageWarning.ambiguousDocReference)));
});
test('can handle renamed imports', () {
var aFunctionUsingRenamedLib = fakeLibrary.functions
.firstWhere((f) => f.name == 'aFunctionUsingRenamedLib');
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to library: <a href="${htmlBasePlaceholder}mylibpub/mylibpub-library.html">renamedLib</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to constructor (implied): <a href="${htmlBasePlaceholder}mylibpub/YetAnotherHelper/YetAnotherHelper.html">new renamedLib.YetAnotherHelper()</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to constructor (implied, no new): <a href="${htmlBasePlaceholder}mylibpub/YetAnotherHelper/YetAnotherHelper.html">renamedLib.YetAnotherHelper()</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to class: <a href="${htmlBasePlaceholder}mylibpub/YetAnotherHelper-class.html">renamedLib.YetAnotherHelper</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to constructor (direct): <a href="${htmlBasePlaceholder}mylibpub/YetAnotherHelper/YetAnotherHelper.html">renamedLib.YetAnotherHelper.YetAnotherHelper</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to class member: <a href="${htmlBasePlaceholder}mylibpub/YetAnotherHelper/getMoreContents.html">renamedLib.YetAnotherHelper.getMoreContents</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to function: <a href="${htmlBasePlaceholder}mylibpub/helperFunction.html">renamedLib.helperFunction</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to overlapping prefix: <a href="${htmlBasePlaceholder}csspub/theOnlyThingInTheLibrary.html">renamedLib2.theOnlyThingInTheLibrary</a>'));
});
test('operator [] reference within a class works', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/BaseForDocComments/operator_get.html">operator []</a> '));
});
test('operator [] reference outside of a class works',
skip: 'https://github.com/dart-lang/dartdoc/issues/1285', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/SpecialList/operator_get.html">'
'SpecialList.operator []</a> '),
);
});
test('adds <code> tag to a class from the SDK', () {
expect(docsAsHtml, contains('<code>String</code>'));
});
test('adds <code> tag to a reference to its parameter', () {
expect(docsAsHtml, contains('<code>value</code>'));
});
test('links to a reference to its class', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/BaseForDocComments-class.html">BaseForDocComments</a>'));
});
test(
'link to a name in another library in this package, but is not imported into this library, should still be linked',
() {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}anonymous_library/doesStuff.html">doesStuff</a>'));
});
test(
'link to unresolved name in the library in this package still should be linked',
() {
var helperClass =
exLibrary.classes.firstWhere((c) => c.name == 'Helper');
expect(
helperClass.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}ex/Apple-class.html">Apple</a>'));
expect(
helperClass.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}ex/B-class.html">ex.B</a>'));
});
test('link to override method in implementer from base class', () {
var helperClass =
baseClassLib.classes.firstWhere((c) => c.name == 'Constraints');
expect(
helperClass.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}override_class/BoxConstraints/debugAssertIsValid.html">BoxConstraints.debugAssertIsValid</a>'));
});
test(
'link to a name of a class from an imported library that exports the name',
() {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}two_exports/BaseClass-class.html">BaseClass</a>'));
});
test(
'links to a reference to a top-level const with multiple underscores',
() {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/NAME_WITH_TWO_UNDERSCORES-constant.html">NAME_WITH_TWO_UNDERSCORES</a>'));
});
test('links to a method in this class', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/BaseForDocComments/anotherMethod.html">anotherMethod</a>'));
});
test('links to a top-level function in this library', () {
expect(
docsAsHtml,
contains(
'<a class="deprecated" href="${htmlBasePlaceholder}fake/topLevelFunction.html">topLevelFunction</a>'));
});
test('links to top-level function from an imported library', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}ex/function1.html">function1</a>'));
});
test('links to a class from an imported lib', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}ex/Apple-class.html">Apple</a>'));
});
test(
'links to a top-level const from same lib (which also has the same name as a const from an imported lib)',
() {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/incorrectDocReference-constant.html">incorrectDocReference</a>'));
});
test('links to a top-level const from an imported lib', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}ex/incorrectDocReferenceFromEx-constant.html">incorrectDocReferenceFromEx</a>'));
});
test('links to a top-level variable with a prefix from an imported lib',
() {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}csspub/theOnlyThingInTheLibrary.html">css.theOnlyThingInTheLibrary</a>'));
});
test('links to a name with a single underscore', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/NAME_SINGLEUNDERSCORE-constant.html">NAME_SINGLEUNDERSCORE</a>'));
});
test('correctly escapes type parameters in links', () {
expect(
docsAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/ExtraSpecialList-class.html">ExtraSpecialList&lt;Object&gt;</a>'));
});
test('correctly escapes type parameters in broken links', () {
expect(docsAsHtml,
contains('<code>ThisIsNotHereNoWay&lt;MyType&gt;</code>'));
});
test('leaves relative href resulting in a working link', () {
// Ideally doc comments should not make assumptions about Dartdoc output
// files, but unfortunately some do...
expect(
docsAsHtml,
contains(
'<a href="../SubForDocComments/localMethod.html">link</a>'));
});
});
test('multi-underscore names in brackets do not become italicized', () {
expect(short.documentation, contains('[NAME_WITH_TWO_UNDERSCORES]'));
expect(
short.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/NAME_WITH_TWO_UNDERSCORES-constant.html">NAME_WITH_TWO_UNDERSCORES</a>'));
});
test('still has brackets inside code blocks', () {
expect(topLevelFunction.documentationAsHtml,
contains("['hello from dart']"));
});
test('oneLine doc references in inherited methods should not have brackets',
() {
var add = specialList.instanceMethods.firstWhere((m) => m.name == 'add');
expect(
add.oneLineDoc,
equals(
'Adds <code>value</code> to the end of this list,\nextending the length by one.'));
});
test(
'full documentation references from inherited methods should not have brackets',
() {
var add = specialList.instanceMethods.firstWhere((m) => m.name == 'add');
expect(
add.documentationAsHtml,
startsWith(
'<p>Adds <code>value</code> to the end of this list,\nextending the length by one.'));
});
test('incorrect doc references are still wrapped in code blocks', () {
expect(incorrectDocReferenceFromEx.documentationAsHtml,
'<p>This should <code>not work</code>.</p>');
});
test('no references', () {
expect(
Apple.documentationAsHtml,
'<p>Sample class <code>String</code></p>\n'
'<pre class="language-dart"> A\n'
' B\n'
'</pre>');
});
test('single ref to class', () {
expect(
B.documentationAsHtml,
contains(
'<p>Extends class <a href="${htmlBasePlaceholder}ex/Apple-class.html">Apple</a>, use <a href="${htmlBasePlaceholder}ex/Apple/Apple.html">Apple.new</a> or <a href="${htmlBasePlaceholder}ex/Apple/Apple.fromString.html">Apple.fromString</a></p>'));
});
test('doc ref to class in SDK does not render as link', () {
expect(
thisIsAsync.documentationAsHtml,
equals(
'<p>An async function. It should look like I return a <code>Future</code>.</p>'));
});
test('references are correct in exported libraries', () {
expect(twoExportsLib, isNotNull);
expect(extendedClass, isNotNull);
var resolved = extendedClass.documentationAsHtml;
expect(resolved, isNotNull);
expect(
resolved,
contains(
'<a href="${htmlBasePlaceholder}two_exports/BaseClass-class.html">BaseClass</a>'));
expect(
resolved,
contains(
'Linking over to <a href="${htmlBasePlaceholder}ex/Apple-class.html">Apple</a>'));
});
test('references to class and constructors', () {
var comment = B.documentationAsHtml;
expect(
comment,
contains(
'Extends class <a href="${htmlBasePlaceholder}ex/Apple-class.html">Apple</a>'));
expect(
comment,
contains(
'use <a href="${htmlBasePlaceholder}ex/Apple/Apple.html">Apple.new</a>'));
expect(
comment,
contains(
'<a href="${htmlBasePlaceholder}ex/Apple/Apple.fromString.html">Apple.fromString</a>'));
});
test('references to nullable type and null-checked variable', () {
var RefsWithQsAndBangs =
exLibrary.classes.firstWhere((c) => c.name == 'RefsWithQsAndBangs');
var comment = RefsWithQsAndBangs.documentationAsHtml;
expect(
comment,
contains(
'nullable type: <a href="${htmlBasePlaceholder}ex/Apple-class.html">Apple?</a>'));
expect(
comment,
contains(
'null-checked variable <a href="${htmlBasePlaceholder}ex/myNumber.html">myNumber!</a>'));
});
test('reference to constructor named the same as a field', () {
var FieldAndCtorWithSameName = exLibrary.classes
.firstWhere((c) => c.name == 'FieldAndCtorWithSameName');
var comment = FieldAndCtorWithSameName.documentationAsHtml;
expect(
comment,
contains('Reference to '
'<a href="${htmlBasePlaceholder}ex/FieldAndCtorWithSameName/FieldAndCtorWithSameName.named.html">'
'FieldAndCtorWithSameName.named()</a>'));
});
test('reference to class from another library', () {
var comment = superAwesomeClass.documentationAsHtml;
expect(
comment,
contains(
'<a href="${htmlBasePlaceholder}ex/Apple-class.html">Apple</a>'));
});
test('reference to method', () {
var comment = foo2.documentationAsHtml;
expect(
comment,
equals(
'<p>link to method from class <a href="${htmlBasePlaceholder}ex/Apple/m.html">Apple.m</a></p>'));
});
test(
'code references to privately defined elements in public classes work properly',
() {
var notAMethodFromPrivateClass = fakeLibrary.allClasses
.firstWhere((Class c) => c.name == 'ReferringClass')
.publicInstanceMethods
.firstWhere((Method m) => m.name == 'notAMethodFromPrivateClass');
expect(
notAMethodFromPrivateClass.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/InheritingClassOne/aMethod.html">fake.InheritingClassOne.aMethod</a>'));
expect(
notAMethodFromPrivateClass.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/InheritingClassTwo/aMethod.html">fake.InheritingClassTwo.aMethod</a>'));
});
test('legacy code blocks render correctly', () {
expect(
testingCodeSyntaxInOneLiners.oneLineDoc,
equals(
'These are code syntaxes: <code>true</code> and <code>false</code>'));
});
test('doc comments to parameters are marked as code', () {
var localMethod = subForDocComments.instanceMethods
.firstWhere((m) => m.name == 'localMethod');
expect(localMethod.documentationAsHtml, contains('<code>foo</code>'));
expect(localMethod.documentationAsHtml, contains('<code>bar</code>'));
});
test('doc comment inherited from getter', () {
var getterWithDocs = subForDocComments.instanceFields
.firstWhere((m) => m.name == 'getterWithDocs');
expect(getterWithDocs.documentationAsHtml,
contains('Some really great topics.'));
});
test(
'a property with no explicit getters and setters does not duplicate docs',
() {
var powers = superAwesomeClass.instanceFields
.firstWhere((p) => p.name == 'powers');
Iterable<Match> matches =
RegExp('In the super class').allMatches(powers.documentationAsHtml);
expect(matches, hasLength(1));
});
});
group('Class edge cases', () {
// This is distinct from inheritance in the language and the analyzer
// implementation.
test(
'Implementor chain is correctly rewritten through intermediate private classes',
() {
var implementorsLibrary = packageGraph.publicLibraries
.firstWhere((l) => l.name == 'implementors');
var ImplementerOfDeclaredPrivateClasses = implementorsLibrary.classes
.firstWhere((c) => c.name == 'ImplementerOfDeclaredPrivateClasses');
var ImplementerOfThings = implementorsLibrary.classes
.firstWhere((c) => c.name == 'ImplementerOfThings');
var ImplementBase = implementorsLibrary.classes
.firstWhere((c) => c.name == 'ImplementBase');
expect(ImplementerOfThings.publicInterfaces.first.modelElement,
equals(ImplementBase));
expect(
ImplementerOfDeclaredPrivateClasses
.publicInterfaces.first.modelElement,
equals(ImplementBase));
expect(ImplementBase.publicImplementors,
contains(ImplementerOfDeclaredPrivateClasses));
expect(ImplementBase.publicImplementors, contains(ImplementerOfThings));
});
test('Overrides from intermediate abstract classes are picked up correctly',
() {
var IntermediateAbstractSubclass = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'IntermediateAbstractSubclass');
var operatorEquals = IntermediateAbstractSubclass.inheritedOperators
.firstWhere((o) => o.name == 'operator ==');
expect(operatorEquals.definingEnclosingContainer.name,
equals('IntermediateAbstract'));
});
test(
'Overrides from intermediate abstract classes that have external implementations via the SDK are picked up correctly',
() {
var dartCore =
packageGraph.libraries.firstWhere((l) => l.name == 'dart:core');
var intClass = dartCore.allClasses.firstWhere((c) => c.name == 'int');
var operatorEqualsInt = intClass.inheritedOperators
.firstWhere((o) => o.name == 'operator ==');
expect(operatorEqualsInt.definingEnclosingContainer.name, equals('num'));
});
test('Factories from unrelated classes are linked correctly', () {
var A = packageGraph.localPublicLibraries
.firstWhere((l) => l.name == 'unrelated_factories')
.allClasses
.firstWhere((c) => c.name == 'A');
var fromMap = A.constructors.firstWhere((c) => c.name == 'A.fromMap');
expect(fromMap.documentationAsHtml,
contains(r'unrelated_factories/AB/AB.fromMap.html">AB.fromMap</a>'));
expect(fromMap.documentationAsHtml,
contains(r'A/A.fromMap.html">fromMap</a>'));
expect(fromMap.documentationAsHtml,
contains(r'unrelated_factories/AB-class.html">AB</a>'));
expect(fromMap.documentationAsHtml,
contains(r'unrelated_factories/A-class.html">A</a>'));
});
test('Inherit from private class across private library to public library',
() {
var GadgetExtender = packageGraph.localPublicLibraries
.firstWhere((l) => l.name == 'gadget_extender')
.allClasses
.firstWhere((c) => c.name == 'GadgetExtender');
var gadgetGetter = GadgetExtender.instanceFields
.firstWhere((f) => f.name == 'gadgetGetter');
expect(gadgetGetter.isCanonical, isTrue);
});
test(
'ExecutableElements from private classes and from public interfaces (#1561)',
() {
var MIEEMixinWithOverride = fakeLibrary.publicClasses
.firstWhere((c) => c.name == 'MIEEMixinWithOverride');
var problematicOperator = MIEEMixinWithOverride.inheritedOperators
.firstWhere((o) => o.name == 'operator []=');
expect(problematicOperator.element.enclosingElement.name,
equals('_MIEEPrivateOverride'));
expect(problematicOperator.canonicalModelElement!.enclosingElement!.name,
equals('MIEEMixinWithOverride'));
});
});
group('Mixin', () {
late final Mixin GenericMixin;
late final Class GenericClass, ModifierClass, TypeInferenceMixedIn;
late final Field overrideByEverything,
overrideByGenericMixin,
overrideByBoth,
overrideByModifierClass;
setUpAll(() {
var classes = fakeLibrary.publicClasses;
GenericClass = classes.firstWhere((c) => c.name == 'GenericClass');
ModifierClass = classes.firstWhere((c) => c.name == 'ModifierClass');
GenericMixin =
fakeLibrary.publicMixins.firstWhere((m) => m.name == 'GenericMixin');
TypeInferenceMixedIn =
classes.firstWhere((c) => c.name == 'TypeInferenceMixedIn');
overrideByEverything = TypeInferenceMixedIn.instanceFields
.firstWhere((f) => f.name == 'overrideByEverything');
overrideByGenericMixin = TypeInferenceMixedIn.instanceFields
.firstWhere((f) => f.name == 'overrideByGenericMixin');
overrideByBoth = TypeInferenceMixedIn.instanceFields
.firstWhere((f) => f.name == 'overrideByBoth');
overrideByModifierClass = TypeInferenceMixedIn.instanceFields
.firstWhere((f) => f.name == 'overrideByModifierClass');
});
test('computes interfaces and implementors correctly', () {
var ThingToImplementInMixin = fakeLibrary.publicClasses
.firstWhere((c) => c.name == 'ThingToImplementInMixin');
var MixedInImplementation = fakeLibrary.publicClasses
.firstWhere((c) => c.name == 'MixedInImplementation');
var MixInImplementation =
fakeLibrary.mixins.firstWhere((m) => m.name == 'MixInImplementation');
var mixinGetter = MixInImplementation.allFields
.firstWhere((f) => f.name == 'mixinGetter');
expect(ThingToImplementInMixin.hasModifiers, isTrue);
expect(MixInImplementation.hasModifiers, isTrue);
expect(MixedInImplementation.hasModifiers, isTrue);
expect(ThingToImplementInMixin.publicImplementors,
orderedEquals([MixInImplementation]));
expect(MixInImplementation.publicImplementors,
orderedEquals([MixedInImplementation]));
expect(
MixedInImplementation.allFields
.firstWhere((f) => f.name == 'mixinGetter')
.canonicalModelElement,
equals(mixinGetter));
});
test('does have a line number and column', () {
expect(GenericMixin.characterLocation, isNotNull);
});
test('Verify inheritance/mixin structure and type inference', () {
expect(
TypeInferenceMixedIn.mixedInTypes
.map<String>((DefinedElementType t) => t.modelElement.name),
orderedEquals(['GenericMixin']));
expect(
TypeInferenceMixedIn.mixedInTypes.first.typeArguments
.map<String>((ElementType t) => t.name),
orderedEquals(['int']));
expect(TypeInferenceMixedIn.superChain, hasLength(2));
final firstType =
TypeInferenceMixedIn.superChain.first as ParameterizedElementType;
final lastType =
TypeInferenceMixedIn.superChain.last as ParameterizedElementType;
expect(firstType.name, equals('ModifierClass'));
expect(firstType.typeArguments.map<String>((ElementType t) => t.name),
orderedEquals(['int']));
expect(lastType.name, equals('GenericClass'));
expect(lastType.typeArguments.map<String>((ElementType t) => t.name),
orderedEquals(['int']));
});
test('Verify non-overridden members have right canonical classes', () {
var member = TypeInferenceMixedIn.instanceFields
.firstWhere((f) => f.name == 'member');
var modifierMember = TypeInferenceMixedIn.instanceFields
.firstWhere((f) => f.name == 'modifierMember');
var mixinMember = TypeInferenceMixedIn.instanceFields
.firstWhere((f) => f.name == 'mixinMember');
expect(member.canonicalEnclosingContainer, equals(GenericClass));
expect(modifierMember.canonicalEnclosingContainer, equals(ModifierClass));
expect(mixinMember.canonicalEnclosingContainer, equals(GenericMixin));
});
test('Verify overrides & documentation inheritance work as intended', () {
expect(overrideByEverything.canonicalEnclosingContainer,
equals(TypeInferenceMixedIn));
expect(overrideByGenericMixin.canonicalEnclosingContainer,
equals(GenericMixin));
expect(overrideByBoth.canonicalEnclosingContainer, equals(GenericMixin));
expect(overrideByModifierClass.canonicalEnclosingContainer,
equals(ModifierClass));
expect(
overrideByEverything.documentationFrom.first,
equals(GenericClass.instanceFields
.firstWhere((f) => f.name == 'overrideByEverything')
.getter));
expect(
overrideByGenericMixin.documentationFrom.first,
equals(GenericClass.instanceFields
.firstWhere((f) => f.name == 'overrideByGenericMixin')
.getter));
expect(
overrideByBoth.documentationFrom.first,
equals(GenericClass.instanceFields
.firstWhere((f) => f.name == 'overrideByBoth')
.getter));
expect(
overrideByModifierClass.documentationFrom.first,
equals(GenericClass.instanceFields
.firstWhere((f) => f.name == 'overrideByModifierClass')
.getter));
});
test('Verify that documentation for mixin applications contains links', () {
expect(
overrideByModifierClass.oneLineDoc,
contains(
'<a href="${htmlBasePlaceholder}fake/ModifierClass-class.html">ModifierClass</a>'));
expect(
overrideByModifierClass.canonicalModelElement!.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/ModifierClass-class.html">ModifierClass</a>'));
expect(
overrideByGenericMixin.oneLineDoc,
contains(
'<a href="${htmlBasePlaceholder}fake/GenericMixin-mixin.html">GenericMixin</a>'));
expect(
overrideByGenericMixin.canonicalModelElement!.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/GenericMixin-mixin.html">GenericMixin</a>'));
expect(
overrideByBoth.oneLineDoc,
contains(
'<a href="${htmlBasePlaceholder}fake/ModifierClass-class.html">ModifierClass</a>'));
expect(
overrideByBoth.oneLineDoc,
contains(
'<a href="${htmlBasePlaceholder}fake/GenericMixin-mixin.html">GenericMixin</a>'));
expect(
overrideByBoth.canonicalModelElement!.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/ModifierClass-class.html">ModifierClass</a>'));
expect(
overrideByBoth.canonicalModelElement!.documentationAsHtml,
contains(
'<a href="${htmlBasePlaceholder}fake/GenericMixin-mixin.html">GenericMixin</a>'));
});
});
group('Class', () {
late final List<Class> classes;
late final Class Apple, B, Cat, Dog, F, Dep, SpecialList;
late final Class ExtendingClass, CatString;
setUpAll(() {
classes = exLibrary.publicClasses.toList();
Apple = classes.firstWhere((c) => c.name == 'Apple');
B = classes.firstWhere((c) => c.name == 'B');
Cat = classes.firstWhere((c) => c.name == 'Cat');
Dog = classes.firstWhere((c) => c.name == 'Dog');
F = classes.firstWhere((c) => c.name == 'F');
Dep = classes.firstWhere((c) => c.name == 'Deprecated');
SpecialList =
fakeLibrary.classes.firstWhere((c) => c.name == 'SpecialList');
ExtendingClass =
twoExportsLib.classes.firstWhere((c) => c.name == 'ExtendingClass');
CatString = exLibrary.classes.firstWhere((c) => c.name == 'CatString');
});
test('has a fully qualified name', () {
expect(Apple.fullyQualifiedName, 'ex.Apple');
});
test('does have a line number and column', () {
expect(Apple.characterLocation, isNotNull);
});
test('we got the classes we expect', () {
expect(Apple.name, equals('Apple'));
expect(B.name, equals('B'));
expect(Cat.name, equals('Cat'));
expect(Dog.name, equals('Dog'));
});
test('a class with only inherited properties has some properties', () {
expect(CatString.hasInstanceFields, isTrue);
expect(CatString.instanceFields, isNotEmpty);
});
test('has enclosing element', () {
expect(Apple.enclosingElement.name, equals(exLibrary.name));
});
test('class name with generics', () {
expect(
F.nameWithGenerics,
equals(
'F&lt;<wbr><span class="type-parameter">T extends String</span>&gt;'));
});
test('correctly finds all the classes', () {
expect(classes, hasLength(37));
});
test('abstract', () {
expect(Cat.isAbstract, isTrue);
});
test('supertype', () {
expect(B.hasPublicSuperChainReversed, isTrue);
});
test('mixins', () {
expect(Apple.mixedInTypes, hasLength(0));
});
test('mixins private', () {
expect(F.mixedInTypes, hasLength(1));
});
test('interfaces', () {
var interfaces = Dog.interfaces;
expect(interfaces, hasLength(2));
expect(interfaces[0].name, 'Cat');
expect(interfaces[1].name, 'E');
});
test('class title has no abstract keyword', () {
expect(Dog.fullkind, 'class');
});
test('get constructors', () {
expect(Apple.publicConstructorsSorted, hasLength(2));
});
test('get static fields', () {
expect(Apple.publicVariableStaticFieldsSorted, hasLength(1));
});
test('constructors have source', () {
var ctor = Dog.constructors.first;
expect(ctor.sourceCode, isNotEmpty);
});
test('get constants', () {
expect(Apple.publicConstantFields, hasLength(1));
expect(Apple.publicConstantFields.first.kind, equals(Kind.constant));
});
test('get instance fields', () {
expect(Apple.publicInstanceFields.where((f) => !f.isInherited),
hasLength(3));
expect(Apple.publicInstanceFields.first.kind, equals(Kind.property));
});
test('get inherited properties, including properties of Object', () {
expect(B.publicInheritedFields, hasLength(4));
});
test('get methods', () {
expect(Dog.publicInstanceMethods.where((m) => !m.isInherited),
hasLength(16));
});
test('get operators', () {
expect(Dog.publicInstanceOperators, hasLength(2));
expect(Dog.publicInstanceOperators.first.name, 'operator ==');
expect(Dog.publicInstanceOperators.last.name, 'operator +');
});
test('has non-inherited instance operators', () {
expect(Dog.publicInheritedInstanceOperators, isFalse);
});
test('has only inherited instance operators', () {
expect(Cat.publicInheritedInstanceOperators, isTrue);
});
test('inherited methods, including from Object ', () {
expect(B.publicInheritedMethods, hasLength(7));
expect(B.hasPublicInheritedMethods, isTrue);
});
test('all instance methods', () {
var methods = B.publicInstanceMethods.where((m) => !m.isInherited);
expect(methods, isNotEmpty);
expect(B.publicInstanceMethods,
hasLength(methods.length + B.publicInheritedMethods.length));
});
test('inherited methods exist', () {
expect(B.inheritedMethods.firstWhere((x) => x.name == 'printMsg'),
isNotNull);
expect(B.inheritedMethods.firstWhere((x) => x.name == 'isGreaterThan'),
isNotNull);
});
test('exported class should have hrefs from the current library', () {
expect(
Dep.href, equals('${htmlBasePlaceholder}ex/Deprecated-class.html'));
expect(Dep.instanceMethods.firstWhere((m) => m.name == 'toString').href,
equals('${htmlBasePlaceholder}ex/Deprecated/toString.html'));
});
test('F has a single instance method', () {
expect(
F.publicInstanceMethods.where((m) => !m.isInherited), hasLength(1));
expect(
F.publicInstanceMethods.first.name, equals('methodWithGenericParam'));
});
test('F has many inherited methods', () {
expect(
F.publicInheritedMethods.map((im) => im.name),
containsAll([
'abstractMethod',
'foo',
'getAnotherClassD',
'getClassA',
'noSuchMethod',
'test',
'testGeneric',
'testGenericMethod',
'testMethod',
'toString',
'withAnimationInline',
'withAnimationInOneLineDoc',
'withMacro',
'withMacro2',
'withPrivateMacro',
'withUndefinedMacro',
'withYouTubeInline',
'withYouTubeInOneLineDoc',
'withYouTubeWatchUrl',
]));
});
test('F has zero declared instance properties', () {
expect(F.publicInstanceFields.where((f) => !f.isInherited), hasLength(0));
});
test('F has a few inherited properties', () {
expect(F.publicInheritedFields, hasLength(10));
expect(
F.publicInheritedFields.map((ip) => ip.name),
containsAll([
'aFinalField',
'aGetterReturningRandomThings',
'aProtectedFinalField',
'deprecatedField',
'deprecatedGetter',
'deprecatedSetter',
'hashCode',
'isImplemented',
'name',
'runtimeType'
]));
});
test('SpecialList has zero instance methods', () {
expect(SpecialList.publicInstanceMethods.where((m) => !m.isInherited),
hasLength(0));
});
test('SpecialList has many inherited methods', () {
expect(SpecialList.publicInheritedMethods, hasLength(49));
var methods = SpecialList.publicInstanceMethodsSorted
.where((m) => m.isInherited)
.toList();
expect(methods.first.name, equals('add'));
expect(methods[1].name, equals('addAll'));
});
test('ExtendingClass is in the right library', () {
expect(ExtendingClass.library.name, equals('two_exports'));
});
// because both the sub and super classes, though from different libraries,
// are exported out through one library
test('ExtendingClass has a super class that is also in the same library',
() {
// The real implementation of BaseClass is private, but it is exported.
expect(ExtendingClass.superChain.first.name, equals('BaseClass'));
expect(ExtendingClass.superChain.first.modelElement.isCanonical,
equals(false));
expect(
ExtendingClass.superChain.first.modelElement.isPublic, equals(false));
// And it should still show up in the publicSuperChain, because it is
// exported.
expect(ExtendingClass.publicSuperChain.first.name, equals('BaseClass'));
expect(
ExtendingClass
.publicSuperChain.first.modelElement.canonicalLibrary!.name,
equals('two_exports'));
});
test(
"ExtendingClass's super class has a library that is not in two_exports",
() {
expect(
ExtendingClass.superChain.last.name, equals('WithGetterAndSetter'));
expect(ExtendingClass.superChain.last.modelElement.library.name,
equals('fake'));
});
});
// Put linkage tests here; rendering tests should go to the appropriate
// [Class], [Extension], etc groups.
group('Comment References link tests', () {
group('Linking for generalized typedef cases works', () {
late final Library generalizedTypedefs;
late final Typedef T0, T2, T5, T8;
late final Class C1, C2;
late final Field C1a;
setUpAll(() {
generalizedTypedefs = packageGraph.libraries
.firstWhere((l) => l.name == 'generalized_typedefs');
T0 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T0');
T2 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T2');
T5 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T5');
T8 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T8');
C1 = generalizedTypedefs.classes.firstWhere((c) => c.name == 'C1');
C2 = generalizedTypedefs.classes.firstWhere((c) => c.name == 'C2');
C1a = C1.allFields.firstWhere((f) => f.name == 'a');
});
test('Verify basic ability to link anything', () {
expect(referenceLookup(T0, 'C2'), equals(MatchingLinkResult(C2)));
expect(referenceLookup(T2, 'C2'), equals(MatchingLinkResult(C2)));
expect(referenceLookup(T5, 'C2'), equals(MatchingLinkResult(C2)));
expect(referenceLookup(T8, 'C2'), equals(MatchingLinkResult(C2)));
});
test('Verify ability to link to type parameters', () {
var T2X = T2.typeParameters.firstWhere((t) => t.name == 'X');
expect(referenceLookup(T2, 'X'), equals(MatchingLinkResult(T2X)));
var T5X = T5.typeParameters.firstWhere((t) => t.name == 'X');
expect(referenceLookup(T5, 'X'), equals(MatchingLinkResult(T5X)));
});
test('Verify ability to link to parameters', () {
var T5name = T5.parameters.firstWhere((t) => t.name == 'name');
expect(referenceLookup(T5, 'name'), equals(MatchingLinkResult(T5name)));
});
test('Verify ability to link to class members of aliased classes', () {
expect(referenceLookup(generalizedTypedefs, 'T8.a'),
equals(MatchingLinkResult(C1a)));
expect(referenceLookup(T8, 'a'), equals(MatchingLinkResult(C1a)));
});
});
group('Linking for complex inheritance and reexport cases', () {
late Library base, extending, local_scope, two_exports;
late Class BaseWithMembers, ExtendingAgain;
late Field aField, anotherField, aStaticField;
late TopLevelVariable aNotReexportedVariable,
anotherNotReexportedVariable,
aSymbolOnlyAvailableInExportContext,
someConflictingNameSymbolTwoExports,
someConflictingNameSymbol;
late Method aStaticMethod;
late Constructor aConstructor;
setUp(() {
base = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'two_exports.src.base');
extending = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'two_exports.src.extending');
local_scope = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'two_exports.src.local_scope');
two_exports = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'two_exports');
BaseWithMembers =
base.classes.firstWhere((c) => c.name == 'BaseWithMembers');
aStaticField = BaseWithMembers.staticFields
.firstWhere((f) => f.name == 'aStaticField');
aStaticMethod = BaseWithMembers.staticMethods
.firstWhere((m) => m.name == 'aStaticMethod');
aConstructor = BaseWithMembers.constructors
.firstWhere((c) => c.name == 'BaseWithMembers.aConstructor');
someConflictingNameSymbol = extending.properties
.firstWhere((p) => p.name == 'someConflictingNameSymbol');
// This group tests lookups from the perspective of the reexported
// elements, to verify that various fallbacks work correctly.
ExtendingAgain =
two_exports.classes.firstWhere((c) => c.name == 'ExtendingAgain');
aField = ExtendingAgain.allFields.firstWhere((f) => f.name == 'aField');
anotherField = ExtendingAgain.allFields
.firstWhere((f) => f.name == 'anotherField');
aNotReexportedVariable = local_scope.properties
.firstWhere((p) => p.name == 'aNotReexportedVariable');
anotherNotReexportedVariable = local_scope.properties
.firstWhere((p) => p.name == 'anotherNotReexportedVariable');
aSymbolOnlyAvailableInExportContext = two_exports.properties
.firstWhere((p) => p.name == 'aSymbolOnlyAvailableInExportContext');
someConflictingNameSymbolTwoExports = two_exports.properties
.firstWhere((p) => p.name == 'someConflictingNameSymbol');
});
test('Grandparent override in container members', () {
expect(referenceLookup(aField, 'aNotReexportedVariable'),
equals(MatchingLinkResult(aNotReexportedVariable)));
// Verify that documentationFrom cases work. Just having the doc
// in the base class is enough to trigger [documentationFrom] and this
// feature.
expect(referenceLookup(anotherField, 'aNotReexportedVariable'),
equals(MatchingLinkResult(aNotReexportedVariable)));
});
// TODO(jcollins-g): dart-lang/dartdoc#2698
test('Linking for static/constructor inheritance across libraries', () {
expect(referenceLookup(ExtendingAgain, 'aStaticField'),
equals(MatchingLinkResult(aStaticField)));
expect(referenceLookup(ExtendingAgain, 'aStaticMethod'),
equals(MatchingLinkResult(aStaticMethod)));
expect(referenceLookup(ExtendingAgain, 'aConstructor'),
equals(MatchingLinkResult(aConstructor)));
});
test('Linking for inherited field from reexport context', () {
expect(referenceLookup(aField, 'anotherNotReexportedVariable'),
equals(MatchingLinkResult(anotherNotReexportedVariable)));
});
// TODO(jcollins-g): dart-lang/dartdoc#2696
test('Allow non-explicit export namespace linking', () {
expect(
referenceLookup(
BaseWithMembers, 'aSymbolOnlyAvailableInExportContext'),
equals(MatchingLinkResult(aSymbolOnlyAvailableInExportContext)));
});
test('Link to definingLibrary for class rather than its export context',
() {
expect(referenceLookup(ExtendingAgain, 'someConflictingNameSymbol'),
equals(MatchingLinkResult(someConflictingNameSymbol)));
expect(referenceLookup(two_exports, 'someConflictingNameSymbol'),
equals(MatchingLinkResult(someConflictingNameSymbolTwoExports)));
});
});
group('Type parameter lookups work', () {
late final Class TypeParameterThings,
TypeParameterThingsExtended,
TypeParameterThingsExtendedQ;
late final Extension UnboundTypeTargetExtension;
late final Field aName, aThing, doesNotCrash;
late final TypeParameter ATypeParam,
BTypeParam,
CTypeParam,
DTypeParam,
QTypeParam;
late final Method aMethod, aMethodExtended, aMethodExtendedQ;
late final Parameter aParam, anotherParam, typedParam;
late final ModelFunction aTopLevelTypeParameterFunction;
setUpAll(() {
UnboundTypeTargetExtension = fakeLibrary.extensions
.firstWhere((f) => f.name == 'UnboundTypeTargetExtension');
doesNotCrash = UnboundTypeTargetExtension.instanceFields
.firstWhere((f) => f.name == 'doesNotCrash');
aTopLevelTypeParameterFunction = fakeLibrary.functions
.firstWhere((f) => f.name == 'aTopLevelTypeParameterFunction');
// TODO(jcollins-g): dart-lang/dartdoc#2704, HTML and type parameters
// on the extended type should not be present here.
DTypeParam = aTopLevelTypeParameterFunction.typeParameters.firstWhere(
(t) => t.name.startsWith('DTypeParam extends TypeParameterThings'));
typedParam = aTopLevelTypeParameterFunction.parameters
.firstWhere((t) => t.name == 'typedParam');
TypeParameterThings = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'TypeParameterThings');
aName = TypeParameterThings.instanceFields
.firstWhere((f) => f.name == 'aName');
aThing = TypeParameterThings.instanceFields
.firstWhere((f) => f.name == 'aThing');
aMethod = TypeParameterThings.instanceMethods
.firstWhere((m) => m.name == 'aMethod');
CTypeParam =
aMethod.typeParameters.firstWhere((t) => t.name == 'CTypeParam');
aParam = aMethod.parameters.firstWhere((p) => p.name == 'aParam');
anotherParam =
aMethod.parameters.firstWhere((p) => p.name == 'anotherParam');
ATypeParam = TypeParameterThings.typeParameters
.firstWhere((t) => t.name == 'ATypeParam');
BTypeParam = TypeParameterThings.typeParameters.firstWhere(
(t) => t.name == 'BTypeParam extends FactoryConstructorThings');
TypeParameterThingsExtended = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'TypeParameterThingsExtended');
aMethodExtended = TypeParameterThingsExtended.instanceMethods
.firstWhere((m) => m.name == 'aMethod');
TypeParameterThingsExtendedQ = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'TypeParameterThingsExtendedQ');
aMethodExtendedQ = TypeParameterThingsExtendedQ.instanceMethods
.firstWhere((m) => m.name == 'aMethod');
QTypeParam = aMethodExtendedQ.typeParameters
.firstWhere((p) => p.name == 'QTypeParam');
});
test('on extension targeting an unbound type', () {
expect(referenceLookup(UnboundTypeTargetExtension, 'doesNotCrash'),
equals(MatchingLinkResult(doesNotCrash)));
});
test('on inherited documentation', () {
expect(referenceLookup(aMethodExtended, 'ATypeParam'),
equals(MatchingLinkResult(ATypeParam)));
expect(referenceLookup(aMethodExtended, 'BTypeParam'),
equals(MatchingLinkResult(BTypeParam)));
expect(referenceLookup(aMethodExtended, 'CTypeParam'),
equals(MatchingLinkResult(CTypeParam)));
// Disallowed, because Q does not exist where the docs originated from.
// The old code forgave this most of the time.
expect(referenceLookup(aMethodExtended, 'QTypeParam'),
equals(MatchingLinkResult(null)));
// We get an inverse situation on the extendedQ class.
expect(referenceLookup(aMethodExtendedQ, 'ATypeParam'),
equals(MatchingLinkResult(null)));
expect(referenceLookup(aMethodExtendedQ, 'BTypeParam'),
equals(MatchingLinkResult(null)));
expect(referenceLookup(aMethodExtendedQ, 'CTypeParam'),
equals(MatchingLinkResult(null)));
expect(referenceLookup(aMethodExtendedQ, 'QTypeParam'),
equals(MatchingLinkResult(QTypeParam)));
});
test('on classes', () {
expect(referenceLookup(TypeParameterThings, 'ATypeParam'),
equals(MatchingLinkResult(ATypeParam)));
expect(referenceLookup(TypeParameterThings, 'BTypeParam'),
equals(MatchingLinkResult(BTypeParam)));
expect(referenceLookup(aName, 'ATypeParam'),
equals(MatchingLinkResult(ATypeParam)));
expect(referenceLookup(aThing, 'BTypeParam'),
equals(MatchingLinkResult(BTypeParam)));
expect(referenceLookup(aMethod, 'CTypeParam'),
equals(MatchingLinkResult(CTypeParam)));
expect(referenceLookup(aParam, 'ATypeParam'),
equals(MatchingLinkResult(ATypeParam)));
expect(referenceLookup(anotherParam, 'CTypeParam'),
equals(MatchingLinkResult(CTypeParam)));
});
test('on top level methods', () {
expect(referenceLookup(aTopLevelTypeParameterFunction, 'DTypeParam'),
equals(MatchingLinkResult(DTypeParam)));
expect(referenceLookup(typedParam, 'DTypeParam'),
equals(MatchingLinkResult(DTypeParam)));
});
});
group('Ordinary namespace cases', () {
late final Package DartPackage;
late final Library Dart, mylibpub;
late final ModelFunction doesStuff,
function1,
topLevelFunction,
aFunctionUsingRenamedLib;
late final TopLevelVariable incorrectDocReference,
incorrectDocReferenceFromEx,
nameWithTwoUnderscores,
nameWithSingleUnderscore,
theOnlyThingInTheLibrary;
late final Constructor aNonDefaultConstructor,
defaultConstructor,
aConstructorShadowed,
anotherName,
anotherConstructor,
factoryConstructorThingsDefault;
late final Class Apple,
BaseClass,
baseForDocComments,
ExtraSpecialList,
FactoryConstructorThings,
string,
metaUseResult;
late final Method doAwesomeStuff, anotherMethod, aMethod;
// ignore: unused_local_variable
late final Operator bracketOperator, bracketOperatorOtherClass;
late final Parameter doAwesomeStuffParam,
aName,
anotherNameParameter,
anotherDifferentName,
differentName,
redHerring,
yetAnotherName,
somethingShadowyParameter;
late final Field forInheriting,
action,
initializeMe,
somethingShadowy,
aConstructorShadowedField,
aNameField,
yetAnotherNameField,
initViaFieldFormal;
setUpAll(() async {
mylibpub = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'mylibpub');
aFunctionUsingRenamedLib = fakeLibrary.functions
.firstWhere((f) => f.name == 'aFunctionUsingRenamedLib');
Dart = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'Dart');
DartPackage = packageGraph.packages.firstWhere((p) => p.name == 'Dart');
nameWithTwoUnderscores = fakeLibrary.constants
.firstWhere((v) => v.name == 'NAME_WITH_TWO_UNDERSCORES');
nameWithSingleUnderscore = fakeLibrary.constants
.firstWhere((v) => v.name == 'NAME_SINGLEUNDERSCORE');
string = packageGraph.allLibraries.values
.firstWhere((e) => e.name == 'dart:core')
.allClasses
.firstWhere((c) => c.name == 'String');
metaUseResult = packageGraph.allLibraries.values
.firstWhere((e) => e.name == 'meta')
.allClasses
.firstWhere((c) => c.name == 'UseResult');
baseForDocComments = fakeLibrary.classes
.firstWhere((c) => c.name == 'BaseForDocComments');
aNonDefaultConstructor = baseForDocComments.constructors.firstWhere(
(c) => c.name == 'BaseForDocComments.aNonDefaultConstructor');
defaultConstructor = baseForDocComments.constructors
.firstWhere((c) => c.name == 'BaseForDocComments');
somethingShadowyParameter = defaultConstructor.parameters
.firstWhere((p) => p.name == 'somethingShadowy');
initializeMe = baseForDocComments.allFields
.firstWhere((f) => f.name == 'initializeMe');
somethingShadowy = baseForDocComments.allFields
.firstWhere((f) => f.name == 'somethingShadowy');
doAwesomeStuff = baseForDocComments.instanceMethods
.firstWhere((m) => m.name == 'doAwesomeStuff');
anotherMethod = baseForDocComments.instanceMethods
.firstWhere((m) => m.name == 'anotherMethod');
doAwesomeStuffParam = doAwesomeStuff.parameters.first;
topLevelFunction = fakeLibrary.functions
.firstWhere((f) => f.name == 'topLevelFunction');
function1 =
exLibrary.functions.firstWhere((f) => f.name == 'function1');
Apple = exLibrary.classes.firstWhere((c) => c.name == 'Apple');
incorrectDocReference = fakeLibrary.constants
.firstWhere((v) => v.name == 'incorrectDocReference');
incorrectDocReferenceFromEx = exLibrary.constants
.firstWhere((v) => v.name == 'incorrectDocReferenceFromEx');
theOnlyThingInTheLibrary = packageGraph.libraries
.firstWhere((l) => l.name == 'csspub')
.properties
.firstWhere((v) => v.name == 'theOnlyThingInTheLibrary');
doesStuff = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'anonymous_library')
.functions
.firstWhere((f) => f.name == 'doesStuff');
BaseClass = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'two_exports.src.base')
.classes
.firstWhere((c) => c.name == 'BaseClass');
bracketOperator = baseForDocComments.instanceOperators
.firstWhere((o) => o.name == 'operator []');
bracketOperatorOtherClass = fakeLibrary.classes
.firstWhere((c) => c.name == 'SpecialList')
.instanceOperators
.firstWhere((o) => o.name == 'operator []');
ExtraSpecialList =
fakeLibrary.classes.firstWhere((c) => c.name == 'ExtraSpecialList');
forInheriting = fakeLibrary.classes
.firstWhere((c) => c.name == 'ImplicitProperties')
.allFields
.firstWhere((n) => n.name == 'forInheriting');
action = packageGraph.allLibraries.values
.firstWhere((l) => l.name == 'reexport.somelib')
.classes
.firstWhere((c) => c.name == 'BaseReexported')
.allFields
.firstWhere((f) => f.name == 'action');
aConstructorShadowed = baseForDocComments.constructors.firstWhere(
(c) => c.name == 'BaseForDocComments.aConstructorShadowed');
aConstructorShadowedField = baseForDocComments.allFields
.firstWhere((f) => f.name == 'aConstructorShadowed');
FactoryConstructorThings = fakeLibrary.classes
.firstWhere((c) => c.name == 'FactoryConstructorThings');
anotherName = FactoryConstructorThings.constructors.firstWhere(
(c) => c.name == 'FactoryConstructorThings.anotherName');
anotherConstructor = FactoryConstructorThings.constructors.firstWhere(
(c) => c.name == 'FactoryConstructorThings.anotherConstructor');
factoryConstructorThingsDefault = FactoryConstructorThings.constructors
.firstWhere((c) => c.name == 'FactoryConstructorThings');
aName = anotherName.parameters.firstWhere((p) => p.name == 'aName');
anotherNameParameter =
anotherName.parameters.firstWhere((p) => p.name == 'anotherName');
anotherDifferentName = anotherName.parameters
.firstWhere((p) => p.name == 'anotherDifferentName');
differentName =
anotherName.parameters.firstWhere((p) => p.name == 'differentName');
redHerring = anotherConstructor.parameters
.firstWhere((p) => p.name == 'redHerring');
aNameField = FactoryConstructorThings.allFields
.firstWhere((f) => f.name == 'aName');
yetAnotherNameField = FactoryConstructorThings.allFields
.firstWhere((f) => f.name == 'yetAnotherName');
initViaFieldFormal = FactoryConstructorThings.allFields
.firstWhere((f) => f.name == 'initViaFieldFormal');
aMethod = FactoryConstructorThings.instanceMethods
.firstWhere((m) => m.name == 'aMethod');
yetAnotherName =
aMethod.parameters.firstWhere((p) => p.name == 'yetAnotherName');
});
group('Parameter references work properly', () {
test('via a setter with a function parameter', () {
var aSetterWithFunctionParameter = fakeLibrary.properties
.firstWhere((p) => p.name == 'aSetterWithFunctionParameter');
var fParam = aSetterWithFunctionParameter.parameters
.firstWhere((p) => p.name == 'fParam');
var fParamA = (fParam.modelType as Callable)
.parameters
.firstWhere((p) => p.name == 'fParamA');
var fParamB = (fParam.modelType as Callable)
.parameters
.firstWhere((p) => p.name == 'fParamB');
var fParamC = (fParam.modelType as Callable)
.parameters
.firstWhere((p) => p.name == 'fParamC');
expect(
referenceLookup(aSetterWithFunctionParameter, 'fParam.fParamA'),
equals(MatchingLinkResult(fParamA)));
expect(
referenceLookup(aSetterWithFunctionParameter, 'fParam.fParamB'),
equals(MatchingLinkResult(fParamB)));
expect(
referenceLookup(aSetterWithFunctionParameter, 'fParam.fParamC'),
equals(MatchingLinkResult(fParamC)));
});
test('in class scope overridden by fields', () {
expect(referenceLookup(FactoryConstructorThings, 'aName'),
equals(MatchingLinkResult(aNameField)));
var anotherNameField = FactoryConstructorThings.allFields
.firstWhere((f) => f.name == 'anotherName');
expect(referenceLookup(FactoryConstructorThings, 'anotherName'),
equals(MatchingLinkResult(anotherNameField)));
expect(referenceLookup(FactoryConstructorThings, 'yetAnotherName'),
equals(MatchingLinkResult(yetAnotherNameField)));
expect(
referenceLookup(FactoryConstructorThings, 'initViaFieldFormal'),
equals(MatchingLinkResult(initViaFieldFormal)));
expect(referenceLookup(FactoryConstructorThings, 'redHerring'),
equals(MatchingLinkResult(redHerring)));
});
test('in class scope overridden by constructors when specified', () {
expect(
referenceLookup(FactoryConstructorThings,
'new FactoryConstructorThings.anotherName'),
equals(MatchingLinkResult(anotherName)));
});
test(
'in default constructor scope referring to a field formal parameter',
() {
expect(
referenceLookup(
factoryConstructorThingsDefault, 'initViaFieldFormal'),
equals(MatchingLinkResult(initViaFieldFormal)));
});
test('in factory constructor scope referring to parameters', () {
expect(referenceLookup(anotherName, 'aName'),
equals(MatchingLinkResult(aName)));
expect(referenceLookup(anotherName, 'anotherName'),
equals(MatchingLinkResult(anotherNameParameter)));
expect(referenceLookup(anotherName, 'anotherDifferentName'),
equals(MatchingLinkResult(anotherDifferentName)));
expect(referenceLookup(anotherName, 'differentName'),
equals(MatchingLinkResult(differentName)));
expect(referenceLookup(anotherName, 'redHerring'),
equals(MatchingLinkResult(redHerring)));
});
test('in factory constructor scope referring to constructors', () {
// A bare constructor reference is OK because there is no conflict.
expect(referenceLookup(anotherName, 'anotherConstructor'),
equals(MatchingLinkResult(anotherConstructor)));
// A conflicting constructor has to be explicit.
expect(
referenceLookup(
anotherName, 'new FactoryConstructorThings.anotherName'),
equals(MatchingLinkResult(anotherName)));
});
test('in method scope referring to parameters and variables', () {
expect(referenceLookup(aMethod, 'yetAnotherName'),
equals(MatchingLinkResult(yetAnotherName)));
expect(
referenceLookup(
aMethod, 'FactoryConstructorThings.yetAnotherName'),
equals(MatchingLinkResult(yetAnotherNameField)));
expect(
referenceLookup(
aMethod, 'FactoryConstructorThings.anotherName.anotherName'),
equals(MatchingLinkResult(anotherNameParameter)));
expect(referenceLookup(aMethod, 'aName'),
equals(MatchingLinkResult(aNameField)));
});
});
test('Referring to a renamed library directly works', () {
expect(
(referenceLookup(aFunctionUsingRenamedLib, 'renamedLib')
.commentReferable as ModelElement)
.canonicalModelElement,
equals(mylibpub));
});
test('Referring to libraries and packages with the same name is fine',
() {
expect(
referenceLookup(Apple, 'Dart'), equals(MatchingLinkResult(Dart)));
expect(referenceLookup(Apple, 'package:Dart'),
equals(MatchingLinkResult(DartPackage)));
});
test('Verify basic linking inside a constructor', () {
expect(referenceLookup(aNonDefaultConstructor, 'initializeMe'),
equals(MatchingLinkResult(initializeMe)));
expect(
referenceLookup(aNonDefaultConstructor, 'aNonDefaultConstructor'),
equals(MatchingLinkResult(aNonDefaultConstructor)));
expect(
referenceLookup(aNonDefaultConstructor,
'BaseForDocComments.aNonDefaultConstructor'),
equals(MatchingLinkResult(aNonDefaultConstructor)));
});
test(
'Verify that constructors do not override member fields unless explicitly specified',
() {
expect(referenceLookup(baseForDocComments, 'aConstructorShadowed'),
equals(MatchingLinkResult(aConstructorShadowedField)));
expect(
referenceLookup(
baseForDocComments, 'BaseForDocComments.aConstructorShadowed'),
equals(MatchingLinkResult(aConstructorShadowedField)));
expect(
referenceLookup(baseForDocComments,
'new BaseForDocComments.aConstructorShadowed'),
equals(MatchingLinkResult(aConstructorShadowed)));
});
test('Deprecated lookup styles st