@@ -9,13 +9,7 @@ import 'dart:convert';
9
9
10
10
import 'package:analyzer/dart/ast/ast.dart' ;
11
11
import 'package:analyzer/dart/element/element.dart'
12
- show
13
- LibraryElement,
14
- Element,
15
- ConstructorElement,
16
- ClassElement,
17
- ParameterElement,
18
- PropertyAccessorElement;
12
+ show LibraryElement, Element, ConstructorElement, ClassElement, ParameterElement, PropertyAccessorElement;
19
13
import 'package:html/parser.dart' show parse;
20
14
import 'package:markdown/markdown.dart' as md;
21
15
@@ -28,32 +22,31 @@ const List<String> _oneLinerSkipTags = const ["code", "pre"];
28
22
29
23
final List <md.InlineSyntax > _markdown_syntaxes = [new _InlineCodeSyntax ()];
30
24
25
+ class MatchingLinkResult {
26
+ final ModelElement element;
27
+ final String label;
28
+ MatchingLinkResult (this .element, this .label);
29
+ }
30
+
31
31
// TODO: this is in the wrong place
32
32
NodeList <CommentReference > _getCommentRefs (ModelElement modelElement) {
33
33
if (modelElement == null ) return null ;
34
34
if (modelElement.documentation == null && modelElement.canOverride ()) {
35
35
var melement = modelElement.overriddenElement;
36
- if (melement != null &&
37
- melement.element.computeNode () != null &&
38
- melement.element.computeNode () is AnnotatedNode ) {
39
- var docComment = (melement.element.computeNode () as AnnotatedNode )
40
- .documentationComment;
36
+ if (melement != null && melement.element.computeNode () != null && melement.element.computeNode () is AnnotatedNode ) {
37
+ var docComment = (melement.element.computeNode () as AnnotatedNode ).documentationComment;
41
38
if (docComment != null ) return docComment.references;
42
39
return null ;
43
40
}
44
41
}
45
42
if (modelElement.element.computeNode () is AnnotatedNode ) {
46
- if ((modelElement.element.computeNode () as AnnotatedNode )
47
- .documentationComment !=
48
- null ) {
49
- return (modelElement.element.computeNode () as AnnotatedNode )
50
- .documentationComment
51
- .references;
43
+ final AnnotatedNode annotatedNode = modelElement.element.computeNode ();
44
+ if (annotatedNode.documentationComment != null ) {
45
+ return annotatedNode.documentationComment.references;
52
46
}
53
47
} else if (modelElement.element is LibraryElement ) {
54
48
// handle anonymous libraries
55
- if (modelElement.element.computeNode () == null ||
56
- modelElement.element.computeNode ().parent == null ) {
49
+ if (modelElement.element.computeNode () == null || modelElement.element.computeNode ().parent == null ) {
57
50
return null ;
58
51
}
59
52
var node = modelElement.element.computeNode ().parent.parent;
@@ -67,84 +60,113 @@ NodeList<CommentReference> _getCommentRefs(ModelElement modelElement) {
67
60
}
68
61
69
62
/// Returns null if element is a parameter.
70
- ModelElement _getMatchingLinkElement (
71
- String codeRef, ModelElement element, List <CommentReference > commentRefs,
63
+ MatchingLinkResult _getMatchingLinkElement (String codeRef, ModelElement element, List <CommentReference > commentRefs,
72
64
{bool isConstructor: false }) {
73
- if (commentRefs == null ) return null ;
65
+ if (commentRefs == null ) return new MatchingLinkResult ( null , null ) ;
74
66
75
67
Element refElement;
76
68
bool isEnum = false ;
77
69
78
70
for (CommentReference ref in commentRefs) {
79
71
if (ref.identifier.name == codeRef) {
80
72
bool isConstrElement = ref.identifier.staticElement is ConstructorElement ;
81
- if (isConstructor && isConstrElement ||
82
- ! isConstructor && ! isConstrElement) {
73
+ if (isConstructor && isConstrElement || ! isConstructor && ! isConstrElement) {
83
74
refElement = ref.identifier.staticElement;
84
75
break ;
85
76
}
86
77
}
87
78
}
88
79
89
80
// Did not find an element in scope
90
- if (refElement == null ) return null ;
81
+ if (refElement == null ) {
82
+ return _findRefElementInLibrary (codeRef, element, commentRefs);
83
+ }
91
84
92
85
if (refElement is PropertyAccessorElement ) {
93
86
// yay we found an accessor that wraps a const, but we really
94
87
// want the top-level field itself
95
88
refElement = (refElement as PropertyAccessorElement ).variable;
96
- if (refElement.enclosingElement is ClassElement &&
97
- (refElement.enclosingElement as ClassElement ).isEnum) {
89
+ if (refElement.enclosingElement is ClassElement && (refElement.enclosingElement as ClassElement ).isEnum) {
98
90
isEnum = true ;
99
91
}
100
92
}
101
93
102
- if (refElement is ParameterElement ) return null ;
94
+ if (refElement is ParameterElement ) return new MatchingLinkResult ( null , null ) ;
103
95
104
96
// bug! this can fail to find the right library name if the element's name
105
97
// we're looking for is the same as a name that comes in from an imported
106
98
// library.
107
99
//
108
100
// Don't search through all libraries in the package, actually search
109
101
// in the current scope.
110
- Library refLibrary =
111
- element.package.findLibraryFor (refElement, scopedTo: element);
102
+ Library refLibrary = element.package.findLibraryFor (refElement, scopedTo: element);
112
103
113
104
if (refLibrary != null ) {
114
105
// Is there a way to pull this from a registry of known elements?
115
106
// Seems like we're creating too many objects this way.
116
107
if (isEnum) {
117
- return new EnumField (refElement, refLibrary);
108
+ return new MatchingLinkResult ( new EnumField (refElement, refLibrary), null );
118
109
}
119
- return new ModelElement .from (refElement, refLibrary);
110
+ return new MatchingLinkResult (new ModelElement .from (refElement, refLibrary), null );
111
+ }
112
+ return new MatchingLinkResult (null , null );
113
+ }
114
+
115
+ MatchingLinkResult _findRefElementInLibrary (String codeRef, ModelElement element, List <CommentReference > commentRefs) {
116
+ final Library library = element.library;
117
+ final Package package = library.package;
118
+ final Map <String , ModelElement > result = {};
119
+
120
+ for (final modelElement in package.allModelElements) {
121
+ if (codeRef == modelElement.fullyQualifiedName) {
122
+ result[modelElement.fullyQualifiedName] = modelElement;
123
+ }
124
+ }
125
+
126
+ for (final modelElement in library.allModelElements) {
127
+ if (codeRef == modelElement.fullyQualifiedNameWithoutLibrary) {
128
+ result[modelElement.fullyQualifiedName] = modelElement;
129
+ }
130
+ }
131
+
132
+ if (result.isEmpty) {
133
+ return new MatchingLinkResult (null , null );
134
+ } else if (result.length == 1 ) {
135
+ return new MatchingLinkResult (result.values.first, result.values.first.name);
136
+ } else {
137
+ // TODO: add --fatal-warning, which would make the app crash in case of ambiguous references
138
+ print (
139
+ "Ambiguous reference to [${codeRef }] in '${element .fullyQualifiedName }' (${element .sourceFileName }:${element .lineNumber }). " +
140
+ "We found matches to the following elements: ${result .keys .map ((k ) => "'${k }'" ).join (", " )}" );
141
+ return new MatchingLinkResult (null , null );
120
142
}
121
- return null ;
122
143
}
123
144
124
- String _linkDocReference (String reference, ModelElement element,
125
- NodeList <CommentReference > commentRefs) {
126
- ModelElement linkedElement;
145
+ String _linkDocReference (String reference, ModelElement element, NodeList <CommentReference > commentRefs) {
127
146
// support for [new Constructor] and [new Class.namedCtr]
128
147
var refs = reference.split (' ' );
148
+ MatchingLinkResult result;
129
149
if (refs.length == 2 && refs.first == 'new' ) {
130
- linkedElement = _getMatchingLinkElement (refs[1 ], element, commentRefs,
131
- isConstructor: true );
150
+ result = _getMatchingLinkElement (refs[1 ], element, commentRefs, isConstructor: true );
132
151
} else {
133
- linkedElement = _getMatchingLinkElement (reference, element, commentRefs);
152
+ result = _getMatchingLinkElement (reference, element, commentRefs);
134
153
}
154
+ final ModelElement linkedElement = result.element;
155
+ final String label = result.label ?? reference;
135
156
if (linkedElement != null ) {
136
157
var classContent = '' ;
137
158
if (linkedElement.isDeprecated) {
138
159
classContent = 'class="deprecated" ' ;
139
160
}
140
161
// this would be linkedElement.linkedName, but link bodies are slightly
141
162
// different for doc references. sigh.
142
- return '<a ${classContent }href="${linkedElement .href }">$reference </a>' ;
163
+ return '<a ${classContent }href="${linkedElement .href }">$label </a>' ;
143
164
} else {
144
165
if (_emitWarning) {
166
+ // TODO: add --fatal-warning, which would make the app crash in case of ambiguous references
145
167
print (" warning: unresolved doc reference '$reference ' (in $element )" );
146
168
}
147
- return '<code>${HTML_ESCAPE .convert (reference )}</code>' ;
169
+ return '<code>${HTML_ESCAPE .convert (label )}</code>' ;
148
170
}
149
171
}
150
172
@@ -154,8 +176,7 @@ String _renderMarkdownToHtml(String text, [ModelElement element]) {
154
176
return new md.Text (_linkDocReference (name, element, commentRefs));
155
177
}
156
178
157
- return md.markdownToHtml (text,
158
- inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver);
179
+ return md.markdownToHtml (text, inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver);
159
180
}
160
181
161
182
class Documentation {
@@ -181,16 +202,13 @@ class Documentation {
181
202
s.remove ();
182
203
}
183
204
for (var pre in asHtmlDocument.querySelectorAll ('pre' )) {
184
- if (pre.children.isNotEmpty &&
185
- pre.children.length != 1 &&
186
- pre.children.first.localName != 'code' ) {
205
+ if (pre.children.isNotEmpty && pre.children.length != 1 && pre.children.first.localName != 'code' ) {
187
206
continue ;
188
207
}
189
208
190
209
if (pre.children.isNotEmpty && pre.children.first.localName == 'code' ) {
191
210
var code = pre.children.first;
192
- pre.classes
193
- .addAll (code.classes.where ((name) => name.startsWith ('language-' )));
211
+ pre.classes.addAll (code.classes.where ((name) => name.startsWith ('language-' )));
194
212
}
195
213
196
214
bool specifiesLanguage = pre.classes.isNotEmpty;
@@ -201,9 +219,7 @@ class Documentation {
201
219
202
220
// `trim` fixes issue with line ending differences between mac and windows.
203
221
var asHtml = asHtmlDocument.body.innerHtml? .trim ();
204
- var asOneLiner = asHtmlDocument.body.children.isEmpty
205
- ? ''
206
- : asHtmlDocument.body.children.first.innerHtml;
222
+ var asOneLiner = asHtmlDocument.body.children.isEmpty ? '' : asHtmlDocument.body.children.first.innerHtml;
207
223
if (! asOneLiner.startsWith ('<p>' )) {
208
224
asOneLiner = '<p>$asOneLiner </p>' ;
209
225
}
0 commit comments