@@ -9,13 +9,7 @@ import 'dart:convert';
99
1010import 'package:analyzer/dart/ast/ast.dart' ;
1111import '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;
1913import 'package:html/parser.dart' show parse;
2014import 'package:markdown/markdown.dart' as md;
2115
@@ -28,32 +22,31 @@ const List<String> _oneLinerSkipTags = const ["code", "pre"];
2822
2923final List <md.InlineSyntax > _markdown_syntaxes = [new _InlineCodeSyntax ()];
3024
25+ class MatchingLinkResult {
26+ final ModelElement element;
27+ final String label;
28+ MatchingLinkResult (this .element, this .label);
29+ }
30+
3131// TODO: this is in the wrong place
3232NodeList <CommentReference > _getCommentRefs (ModelElement modelElement) {
3333 if (modelElement == null ) return null ;
3434 if (modelElement.documentation == null && modelElement.canOverride ()) {
3535 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;
4138 if (docComment != null ) return docComment.references;
4239 return null ;
4340 }
4441 }
4542 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;
5246 }
5347 } else if (modelElement.element is LibraryElement ) {
5448 // 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 ) {
5750 return null ;
5851 }
5952 var node = modelElement.element.computeNode ().parent.parent;
@@ -67,84 +60,113 @@ NodeList<CommentReference> _getCommentRefs(ModelElement modelElement) {
6760}
6861
6962/// 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,
7264 {bool isConstructor: false }) {
73- if (commentRefs == null ) return null ;
65+ if (commentRefs == null ) return new MatchingLinkResult ( null , null ) ;
7466
7567 Element refElement;
7668 bool isEnum = false ;
7769
7870 for (CommentReference ref in commentRefs) {
7971 if (ref.identifier.name == codeRef) {
8072 bool isConstrElement = ref.identifier.staticElement is ConstructorElement ;
81- if (isConstructor && isConstrElement ||
82- ! isConstructor && ! isConstrElement) {
73+ if (isConstructor && isConstrElement || ! isConstructor && ! isConstrElement) {
8374 refElement = ref.identifier.staticElement;
8475 break ;
8576 }
8677 }
8778 }
8879
8980 // Did not find an element in scope
90- if (refElement == null ) return null ;
81+ if (refElement == null ) {
82+ return _findRefElementInLibrary (codeRef, element, commentRefs);
83+ }
9184
9285 if (refElement is PropertyAccessorElement ) {
9386 // yay we found an accessor that wraps a const, but we really
9487 // want the top-level field itself
9588 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) {
9890 isEnum = true ;
9991 }
10092 }
10193
102- if (refElement is ParameterElement ) return null ;
94+ if (refElement is ParameterElement ) return new MatchingLinkResult ( null , null ) ;
10395
10496 // bug! this can fail to find the right library name if the element's name
10597 // we're looking for is the same as a name that comes in from an imported
10698 // library.
10799 //
108100 // Don't search through all libraries in the package, actually search
109101 // in the current scope.
110- Library refLibrary =
111- element.package.findLibraryFor (refElement, scopedTo: element);
102+ Library refLibrary = element.package.findLibraryFor (refElement, scopedTo: element);
112103
113104 if (refLibrary != null ) {
114105 // Is there a way to pull this from a registry of known elements?
115106 // Seems like we're creating too many objects this way.
116107 if (isEnum) {
117- return new EnumField (refElement, refLibrary);
108+ return new MatchingLinkResult ( new EnumField (refElement, refLibrary), null );
118109 }
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 );
120142 }
121- return null ;
122143}
123144
124- String _linkDocReference (String reference, ModelElement element,
125- NodeList <CommentReference > commentRefs) {
126- ModelElement linkedElement;
145+ String _linkDocReference (String reference, ModelElement element, NodeList <CommentReference > commentRefs) {
127146 // support for [new Constructor] and [new Class.namedCtr]
128147 var refs = reference.split (' ' );
148+ MatchingLinkResult result;
129149 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 );
132151 } else {
133- linkedElement = _getMatchingLinkElement (reference, element, commentRefs);
152+ result = _getMatchingLinkElement (reference, element, commentRefs);
134153 }
154+ final ModelElement linkedElement = result.element;
155+ final String label = result.label ?? reference;
135156 if (linkedElement != null ) {
136157 var classContent = '' ;
137158 if (linkedElement.isDeprecated) {
138159 classContent = 'class="deprecated" ' ;
139160 }
140161 // this would be linkedElement.linkedName, but link bodies are slightly
141162 // different for doc references. sigh.
142- return '<a ${classContent }href="${linkedElement .href }">$reference </a>' ;
163+ return '<a ${classContent }href="${linkedElement .href }">$label </a>' ;
143164 } else {
144165 if (_emitWarning) {
166+ // TODO: add --fatal-warning, which would make the app crash in case of ambiguous references
145167 print (" warning: unresolved doc reference '$reference ' (in $element )" );
146168 }
147- return '<code>${HTML_ESCAPE .convert (reference )}</code>' ;
169+ return '<code>${HTML_ESCAPE .convert (label )}</code>' ;
148170 }
149171}
150172
@@ -154,8 +176,7 @@ String _renderMarkdownToHtml(String text, [ModelElement element]) {
154176 return new md.Text (_linkDocReference (name, element, commentRefs));
155177 }
156178
157- return md.markdownToHtml (text,
158- inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver);
179+ return md.markdownToHtml (text, inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver);
159180}
160181
161182class Documentation {
@@ -181,16 +202,13 @@ class Documentation {
181202 s.remove ();
182203 }
183204 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' ) {
187206 continue ;
188207 }
189208
190209 if (pre.children.isNotEmpty && pre.children.first.localName == 'code' ) {
191210 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-' )));
194212 }
195213
196214 bool specifiesLanguage = pre.classes.isNotEmpty;
@@ -201,9 +219,7 @@ class Documentation {
201219
202220 // `trim` fixes issue with line ending differences between mac and windows.
203221 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;
207223 if (! asOneLiner.startsWith ('<p>' )) {
208224 asOneLiner = '<p>$asOneLiner </p>' ;
209225 }
0 commit comments