Skip to content

Commit 9c36806

Browse files
committed
Initial implementation of constructor tearoff support
1 parent d1cc44c commit 9c36806

File tree

7 files changed

+201
-33
lines changed

7 files changed

+201
-33
lines changed

lib/src/model/constructor.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ class Constructor extends ModelElement
6767
@override
6868
bool get isConst => element.isConst;
6969

70-
bool get isUnnamedConstructor => name == enclosingElement.name;
70+
bool get isUnnamedConstructor =>
71+
name == enclosingElement.name || name == '${enclosingElement.name}.new';
7172

7273
@Deprecated(
74+
// TODO(jcollins-g): This, in retrospect, seems like a bad idea.
7375
'Renamed to `isUnnamedConstructor`; this getter with the old name will '
7476
'be removed as early as Dartdoc 1.0.0')
7577
bool get isDefaultConstructor => isUnnamedConstructor;
@@ -144,5 +146,5 @@ class Constructor extends ModelElement
144146

145147
@override
146148
String get referenceName =>
147-
element.name == '' ? enclosingElement.name : element.name;
149+
isUnnamedConstructor ? enclosingElement.name : element.name;
148150
}

test/end2end/model_special_cases_test.dart

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'dart:io';
1212

1313
import 'package:analyzer/dart/element/type.dart';
1414
import 'package:async/async.dart';
15+
import 'package:dartdoc/src/matching_link_result.dart';
1516
import 'package:dartdoc/src/model/model.dart';
1617
import 'package:dartdoc/src/package_config_provider.dart';
1718
import 'package:dartdoc/src/package_meta.dart';
@@ -20,6 +21,7 @@ import 'package:pub_semver/pub_semver.dart';
2021
import 'package:test/test.dart';
2122

2223
import '../src/utils.dart' as utils;
24+
import '../src/utils.dart';
2325

2426
final _testPackageGraphExperimentsMemo = AsyncMemoizer<PackageGraph>();
2527
Future<PackageGraph> get _testPackageGraphExperiments =>
@@ -75,10 +77,107 @@ void main() {
7577
VersionRange(min: Version.parse('2.14.0-0'), includeMin: true);
7678
final _tripleShiftAllowed =
7779
VersionRange(min: Version.parse('2.14.0-0'), includeMin: true);
80+
final _constructorTearoffsAllowed =
81+
VersionRange(min: Version.parse('2.15.0-0'), includeMin: true);
7882

7983
// Experimental features not yet enabled by default. Move tests out of this
8084
// block when the feature is enabled by default.
8185
group('Experiments', () {
86+
group('constructor-tearoffs', () {
87+
Library constructorTearoffs;
88+
Class A, B, C, D, E, F;
89+
Mixin M;
90+
Typedef At, Bt, Ct, Et, Ft, NotAClass;
91+
Constructor Anew, Bnew, Cnew, Dnew, Enew, Fnew;
92+
93+
setUpAll(() async {
94+
constructorTearoffs = (await _testPackageGraphExperiments)
95+
.libraries
96+
.firstWhere((l) => l.name == 'constructor_tearoffs');
97+
A = constructorTearoffs.classes.firstWhere((c) => c.name == 'A');
98+
B = constructorTearoffs.classes.firstWhere((c) => c.name == 'B');
99+
C = constructorTearoffs.classes.firstWhere((c) => c.name == 'C');
100+
D = constructorTearoffs.classes.firstWhere((c) => c.name == 'D');
101+
E = constructorTearoffs.classes.firstWhere((c) => c.name == 'E');
102+
F = constructorTearoffs.classes.firstWhere((c) => c.name == 'F');
103+
M = constructorTearoffs.mixins.firstWhere((m) => m.name == 'M');
104+
At = constructorTearoffs.typedefs.firstWhere((t) => t.name == 'At');
105+
Bt = constructorTearoffs.typedefs.firstWhere((t) => t.name == 'Bt');
106+
Ct = constructorTearoffs.typedefs.firstWhere((t) => t.name == 'Ct');
107+
Et = constructorTearoffs.typedefs.firstWhere((t) => t.name == 'Et');
108+
Ft = constructorTearoffs.typedefs.firstWhere((t) => t.name == 'Ft');
109+
NotAClass = constructorTearoffs.typedefs
110+
.firstWhere((t) => t.name == 'NotAClass');
111+
Anew = A.unnamedConstructor;
112+
Bnew = B.unnamedConstructor;
113+
Cnew = C.unnamedConstructor;
114+
Dnew = D.unnamedConstructor;
115+
Enew = E.unnamedConstructor;
116+
Fnew = F.unnamedConstructor;
117+
});
118+
119+
test('smoke test', () {
120+
expect(A, isNotNull);
121+
expect(B, isNotNull);
122+
expect(C, isNotNull);
123+
expect(D, isNotNull);
124+
expect(E, isNotNull);
125+
expect(F, isNotNull);
126+
expect(M, isNotNull);
127+
expect(At, isNotNull);
128+
expect(Bt, isNotNull);
129+
expect(Ct, isNotNull);
130+
expect(Et, isNotNull);
131+
expect(Ft, isNotNull);
132+
expect(NotAClass, isNotNull);
133+
expect(Anew, isNotNull);
134+
expect(Bnew, isNotNull);
135+
expect(Cnew, isNotNull);
136+
expect(Dnew, isNotNull);
137+
expect(Enew, isNotNull);
138+
expect(Fnew, isNotNull);
139+
});
140+
141+
test('reference regression', () {
142+
expect(newLookup(constructorTearoffs, 'A.A'),
143+
equals(MatchingLinkResult(Anew)));
144+
expect(newLookup(constructorTearoffs, 'new A()'),
145+
equals(MatchingLinkResult(Anew)));
146+
expect(newLookup(constructorTearoffs, 'A()'),
147+
equals(MatchingLinkResult(Anew)));
148+
expect(newLookup(constructorTearoffs, 'B.B'),
149+
equals(MatchingLinkResult(Bnew)));
150+
expect(newLookup(constructorTearoffs, 'new B()'),
151+
equals(MatchingLinkResult(Bnew)));
152+
expect(newLookup(constructorTearoffs, 'B()'),
153+
equals(MatchingLinkResult(Bnew)));
154+
expect(newLookup(constructorTearoffs, 'C.C'),
155+
equals(MatchingLinkResult(Cnew)));
156+
expect(newLookup(constructorTearoffs, 'new C()'),
157+
equals(MatchingLinkResult(Cnew)));
158+
expect(newLookup(constructorTearoffs, 'C()'),
159+
equals(MatchingLinkResult(Cnew)));
160+
expect(newLookup(constructorTearoffs, 'D.D'),
161+
equals(MatchingLinkResult(Dnew)));
162+
expect(newLookup(constructorTearoffs, 'new D()'),
163+
equals(MatchingLinkResult(Dnew)));
164+
expect(newLookup(constructorTearoffs, 'D()'),
165+
equals(MatchingLinkResult(Dnew)));
166+
expect(newLookup(constructorTearoffs, 'E.E'),
167+
equals(MatchingLinkResult(Enew)));
168+
expect(newLookup(constructorTearoffs, 'new E()'),
169+
equals(MatchingLinkResult(Enew)));
170+
expect(newLookup(constructorTearoffs, 'E()'),
171+
equals(MatchingLinkResult(Enew)));
172+
expect(newLookup(constructorTearoffs, 'F.F'),
173+
equals(MatchingLinkResult(Fnew)));
174+
expect(newLookup(constructorTearoffs, 'new F()'),
175+
equals(MatchingLinkResult(Fnew)));
176+
expect(newLookup(constructorTearoffs, 'F()'),
177+
equals(MatchingLinkResult(Fnew)));
178+
});
179+
}, skip: !_constructorTearoffsAllowed.allows(utils.platformVersion));
180+
82181
group('triple-shift', () {
83182
Library tripleShift;
84183
Class C, E, F;

test/end2end/model_test.dart

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'dart:io';
99
import 'package:analyzer/source/line_info.dart';
1010
import 'package:async/async.dart';
1111
import 'package:dartdoc/src/element_type.dart';
12-
import 'package:dartdoc/src/markdown_processor.dart';
1312
import 'package:dartdoc/src/matching_link_result.dart';
1413
import 'package:dartdoc/src/model/feature.dart';
1514
import 'package:dartdoc/src/model/model.dart';
@@ -25,6 +24,7 @@ import 'package:dartdoc/src/warnings.dart';
2524
import 'package:test/test.dart';
2625

2726
import '../src/utils.dart' as utils;
27+
import '../src/utils.dart';
2828

2929
final _testPackageGraphMemo = AsyncMemoizer<PackageGraph>();
3030
Future<PackageGraph> get testPackageGraph async =>
@@ -2124,35 +2124,6 @@ void main() {
21242124
// Put linkage tests here; rendering tests should go to the appropriate
21252125
// [Class], [Extension], etc groups.
21262126
group('Comment References link tests', () {
2127-
/// For comparison purposes, return an equivalent [MatchingLinkResult]
2128-
/// for the defining element returned. May return [originalResult].
2129-
/// We do this to eliminate canonicalization effects from comparison,
2130-
/// as the original lookup code returns canonicalized results and the
2131-
/// new lookup code is only guaranteed to return equivalent results.
2132-
MatchingLinkResult definingLinkResult(MatchingLinkResult originalResult) {
2133-
if (originalResult.commentReferable?.element != null) {
2134-
return MatchingLinkResult(
2135-
ModelElement.fromElement(originalResult.commentReferable.element,
2136-
originalResult.commentReferable.packageGraph),
2137-
warn: originalResult.warn);
2138-
}
2139-
return originalResult;
2140-
}
2141-
2142-
MatchingLinkResult originalLookup(Warnable element, String codeRef) =>
2143-
definingLinkResult(getMatchingLinkElement(element, codeRef,
2144-
enhancedReferenceLookup: false));
2145-
MatchingLinkResult newLookup(Warnable element, String codeRef) =>
2146-
definingLinkResult(getMatchingLinkElement(element, codeRef,
2147-
enhancedReferenceLookup: true));
2148-
2149-
MatchingLinkResult bothLookup(Warnable element, String codeRef) {
2150-
var originalLookupResult = originalLookup(element, codeRef);
2151-
var newLookupResult = newLookup(element, codeRef);
2152-
expect(newLookupResult.isEquivalentTo(originalLookupResult), isTrue);
2153-
return newLookupResult;
2154-
}
2155-
21562127
group('Linking for generalized typedef cases works', () {
21572128
Library generalizedTypedefs;
21582129
Typedef T0, T2, T5, T8;

test/src/utils.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ import 'package:analyzer/file_system/file_system.dart';
1010
import 'package:analyzer/file_system/memory_file_system.dart';
1111
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
1212
import 'package:dartdoc/src/dartdoc_options.dart';
13+
import 'package:dartdoc/src/markdown_processor.dart';
14+
import 'package:dartdoc/src/matching_link_result.dart';
15+
import 'package:dartdoc/src/model/model_element.dart';
1316
import 'package:dartdoc/src/model/package_builder.dart';
1417
import 'package:dartdoc/src/model/package_graph.dart';
1518
import 'package:dartdoc/src/package_config_provider.dart';
1619
import 'package:dartdoc/src/package_meta.dart';
20+
import 'package:dartdoc/src/warnings.dart';
1721
import 'package:pub_semver/pub_semver.dart';
22+
import 'package:test/expect.dart';
1823

1924
/// The number of public libraries in testing/test_package, minus 2 for
2025
/// the excluded libraries listed in the initializers for _testPackageGraphMemo
@@ -190,3 +195,32 @@ two:lib/
190195

191196
return projectFolder;
192197
}
198+
199+
/// For comparison purposes, return an equivalent [MatchingLinkResult]
200+
/// for the defining element returned. May return [originalResult].
201+
/// We do this to eliminate canonicalization effects from comparison,
202+
/// as the original lookup code returns canonicalized results and the
203+
/// new lookup code is only guaranteed to return equivalent results.
204+
MatchingLinkResult definingLinkResult(MatchingLinkResult originalResult) {
205+
if (originalResult.commentReferable?.element != null) {
206+
return MatchingLinkResult(
207+
ModelElement.fromElement(originalResult.commentReferable.element,
208+
originalResult.commentReferable.packageGraph),
209+
warn: originalResult.warn);
210+
}
211+
return originalResult;
212+
}
213+
214+
MatchingLinkResult originalLookup(Warnable element, String codeRef) =>
215+
definingLinkResult(getMatchingLinkElement(element, codeRef,
216+
enhancedReferenceLookup: false));
217+
MatchingLinkResult newLookup(Warnable element, String codeRef) =>
218+
definingLinkResult(getMatchingLinkElement(element, codeRef,
219+
enhancedReferenceLookup: true));
220+
221+
MatchingLinkResult bothLookup(Warnable element, String codeRef) {
222+
var originalLookupResult = originalLookup(element, codeRef);
223+
var newLookupResult = newLookup(element, codeRef);
224+
expect(newLookupResult.isEquivalentTo(originalLookupResult), isTrue);
225+
return newLookupResult;
226+
}

testing/test_package_experiments/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ analyzer:
55
- nonfunction-type-aliases
66
- triple-shift
77
- generic-metadata
8+
- constructor-tearoffs
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Exercise a number of constructor-tearoff related features inside dartdoc.
6+
///
7+
/// References to tearoffs should work at a top level too:
8+
/// [A.new], [B.new], [At.new], [Bt.new], [C], [C.new], [D.new],
9+
///
10+
library constructor_tearoffs;
11+
12+
abstract class A {
13+
final int number;
14+
/// Even though this is abstract, dartdoc should still allow referring to
15+
/// [A.new].
16+
A.new(this.number);
17+
}
18+
19+
class B extends A {
20+
B.new() : super(5);
21+
}
22+
23+
class C {}
24+
25+
typedef At = A;
26+
typedef Bt = B;
27+
typedef Ct = C;
28+
29+
/// [Dt.new] should be a thing.
30+
abstract class D<T extends String> {}
31+
32+
typedef Dt = D;
33+
34+
/// I refer to many things, including typedef constructor tearoffs [At.new],
35+
/// [Bt.new], [Ct.new], [Et.new].
36+
/// Don't forget about [E.new], [E.E], or [D<String>.new].
37+
class E extends D<String> {
38+
final String aValue;
39+
E.new(this.aValue) {}
40+
}
41+
42+
typedef Et = E;
43+
44+
/// Referring to [F<T>.new] and [F<Object>.new] should work fine.
45+
class F<T> {
46+
F() {
47+
print('I too am a valid constructor invocation with this feature.');
48+
}
49+
}
50+
51+
typedef Ft = F;
52+
53+
/// Referring to [Fstring.new] should be fine.
54+
typedef Fstring = F<String>;
55+
56+
/// Can't refer to `.new` here.
57+
typedef NotAClass = Function;
58+
59+
/// Mixins don't have constructors either, so disallow `M.new`.
60+
mixin M<T> on C {
61+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: test_package_experiments
22
version: 0.0.1
33
environment:
4-
sdk: '>=2.14.0-0 <3.0.0'
4+
sdk: '>=2.15.0-0 <3.0.0'
55
description: Experimental flags are tested here.

0 commit comments

Comments
 (0)