From b386283391bec67a7ade6cc826fa35744e64a2e8 Mon Sep 17 00:00:00 2001 From: Anton Astashov Date: Sat, 17 Dec 2016 15:48:12 -0600 Subject: [PATCH] Resolve non-imported symbols in comments [#1153] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @Hixie noticed, that sometimes, a library, like the Flutter rendering library, wants to refer to another library, like the Flutter widgets library, in the documentation, but doesn't want to introduce a dependency in the code. Currently, there’s no mechanisms in dartdoc, which allows that. This commit adds that. You can use either a short name (e.g. [icon]) or a fully qualified name (like, [material.Icon.icon]), in the HTML docs, it always will be shown as a short name though (is that a desired behavior?). Dartdoc will go over all the libraries, classes, methods, properties, constants of the package that is being documented, and will try to resolve the symbol. If it’s successful, it will add the link to it, same way as when the symbol is in scope. Some outstanding questions: * What to do in case of ambiguity here? Show a warning, crash with an error message, ignore? * Do we want to show short name in case a fully qualified name is provided? I saw in the comments in the ticket (https://github.com/dart-lang/dartdoc/issues/1153#issuecomment-236012803) that @Hixie would want that. * Anything else? Testing: Unit tests, of course, also tried to generate Flutter docs with that, and observed that the links for previously non-resolved symbols work. --- lib/src/markdown_processor.dart | 74 ++++++++++++++++--- test/model_test.dart | 4 +- testing/test_package/lib/src/extending.dart | 2 +- .../test_package_docs/ex/ShapeType-class.html | 4 +- .../BaseForDocComments/doAwesomeStuff.html | 2 +- .../two_exports/ExtendingClass-class.html | 2 +- 6 files changed, 69 insertions(+), 19 deletions(-) diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart index 5a83a70f16..0276add8c3 100644 --- a/lib/src/markdown_processor.dart +++ b/lib/src/markdown_processor.dart @@ -28,6 +28,12 @@ const List _oneLinerSkipTags = const ["code", "pre"]; final List _markdown_syntaxes = [new _InlineCodeSyntax()]; +class MatchingLinkResult { + final ModelElement element; + final String label; + MatchingLinkResult(this.element, this.label); +} + // TODO: this is in the wrong place NodeList _getCommentRefs(ModelElement modelElement) { if (modelElement == null) return null; @@ -67,10 +73,10 @@ NodeList _getCommentRefs(ModelElement modelElement) { } /// Returns null if element is a parameter. -ModelElement _getMatchingLinkElement( +MatchingLinkResult _getMatchingLinkElement( String codeRef, ModelElement element, List commentRefs, {bool isConstructor: false}) { - if (commentRefs == null) return null; + if (commentRefs == null) return new MatchingLinkResult(null, null); Element refElement; bool isEnum = false; @@ -87,7 +93,9 @@ ModelElement _getMatchingLinkElement( } // Did not find an element in scope - if (refElement == null) return null; + if (refElement == null) { + return _findRefElementInLibrary(codeRef, element, commentRefs); + } if (refElement is PropertyAccessorElement) { // yay we found an accessor that wraps a const, but we really @@ -99,7 +107,7 @@ ModelElement _getMatchingLinkElement( } } - if (refElement is ParameterElement) return null; + if (refElement is ParameterElement) return new MatchingLinkResult(null, null); // bug! this can fail to find the right library name if the element's name // we're looking for is the same as a name that comes in from an imported @@ -114,24 +122,66 @@ ModelElement _getMatchingLinkElement( // Is there a way to pull this from a registry of known elements? // Seems like we're creating too many objects this way. if (isEnum) { - return new EnumField(refElement, refLibrary); + return new MatchingLinkResult(new EnumField(refElement, refLibrary), null); } - return new ModelElement.from(refElement, refLibrary); + return new MatchingLinkResult(new ModelElement.from(refElement, refLibrary), null); + } + return new MatchingLinkResult(null, null); +} + +MatchingLinkResult _findRefElementInLibrary(String codeRef, ModelElement element, List commentRefs) { + final package = element.library.package; + final Map result = {}; + package.libraries.forEach((library) { + final List modelElements = [] + ..addAll(library.allClasses) + ..addAll(library.constants) + ..addAll(library.enums) + ..addAll(library.functions) + ..addAll(library.properties) + ..addAll(library.typedefs); + + library.allClasses.forEach((c) { + modelElements.addAll(c.allInstanceMethods); + modelElements.addAll(c.allInstanceProperties); + modelElements.addAll(c.allOperators); + modelElements.addAll(c.staticMethods); + modelElements.addAll(c.staticProperties); + }); + + modelElements.forEach((modelElement) { + if (codeRef == modelElement.name || codeRef == modelElement.fullyQualifiedName) { + result[modelElement.fullyQualifiedName] = modelElement; + } + }); + }); + + if (result.isEmpty) { + return new MatchingLinkResult(null, null); + } else if (result.length == 1) { + return new MatchingLinkResult(result.values.first, result.values.first.name); + } else { + if (_emitWarning) { + print("Abiguous reference '${codeRef}' in '${element.fullyQualifiedName}', " + + "we found it in the following elements: ${result.keys.map((k) => "'${k}'").join(", ")}"); + } + return new MatchingLinkResult(null, null); } - return null; } String _linkDocReference(String reference, ModelElement element, NodeList commentRefs) { - ModelElement linkedElement; // support for [new Constructor] and [new Class.namedCtr] var refs = reference.split(' '); + MatchingLinkResult result; if (refs.length == 2 && refs.first == 'new') { - linkedElement = _getMatchingLinkElement(refs[1], element, commentRefs, + result = _getMatchingLinkElement(refs[1], element, commentRefs, isConstructor: true); } else { - linkedElement = _getMatchingLinkElement(reference, element, commentRefs); + result = _getMatchingLinkElement(reference, element, commentRefs); } + final linkedElement = result.element; + final label = result.label ?? reference; if (linkedElement != null) { var classContent = ''; if (linkedElement.isDeprecated) { @@ -139,12 +189,12 @@ String _linkDocReference(String reference, ModelElement element, } // this would be linkedElement.linkedName, but link bodies are slightly // different for doc references. sigh. - return '$reference'; + return '$label'; } else { if (_emitWarning) { print(" warning: unresolved doc reference '$reference' (in $element)"); } - return '${HTML_ESCAPE.convert(reference)}'; + return '${HTML_ESCAPE.convert(label)}'; } } diff --git a/test/model_test.dart b/test/model_test.dart index 627b25f82e..a4547418d2 100644 --- a/test/model_test.dart +++ b/test/model_test.dart @@ -276,7 +276,7 @@ void main() { test( 'link to a name in another library in this package, but is not imported into this library, is codeified', () { - expect(docsAsHtml, contains('doesStuff')); + expect(docsAsHtml, contains('doesStuff')); }); test( @@ -427,7 +427,7 @@ void main() { expect(resolved, isNotNull); expect(resolved, contains('BaseClass')); - expect(resolved, contains('linking over to Apple.')); + expect(resolved, contains('linking over to Apple.')); }); test('references to class and constructors', () { diff --git a/testing/test_package/lib/src/extending.dart b/testing/test_package/lib/src/extending.dart index c60eb1d95f..fae34934b3 100644 --- a/testing/test_package/lib/src/extending.dart +++ b/testing/test_package/lib/src/extending.dart @@ -8,5 +8,5 @@ int topLevelVariable = 1; /// /// Also check out [topLevelVariable]. /// -/// This should not work: linking over to [Apple]. +/// This also should work: linking over to [Apple]. class ExtendingClass extends BaseClass {} diff --git a/testing/test_package_docs/ex/ShapeType-class.html b/testing/test_package_docs/ex/ShapeType-class.html index 25e49eba0c..1714eac4a7 100644 --- a/testing/test_package_docs/ex/ShapeType-class.html +++ b/testing/test_package_docs/ex/ShapeType-class.html @@ -136,8 +136,8 @@
ex

Foo bar.

  1. All references should be hyperlinks. MyError and -ShapeType and MyError and MyException and -MyError and MyException and +ShapeType and MyError and MyException and +MyError and MyException and List<int> foo bar.
diff --git a/testing/test_package_docs/fake/BaseForDocComments/doAwesomeStuff.html b/testing/test_package_docs/fake/BaseForDocComments/doAwesomeStuff.html index d090335382..8e579efa69 100644 --- a/testing/test_package_docs/fake/BaseForDocComments/doAwesomeStuff.html +++ b/testing/test_package_docs/fake/BaseForDocComments/doAwesomeStuff.html @@ -117,7 +117,7 @@
BaseForDocComments

Reference to a top-level const in another library incorrectDocReferenceFromEx

Reference to prefixed-name from another lib css.theOnlyThingInTheLibrary xx

Reference to a name that exists in this package, but is not imported -in this library doesStuff xx

+in this library doesStuff xx

Reference to a name of a class from an import of a library that exported the name BaseClass xx

diff --git a/testing/test_package_docs/two_exports/ExtendingClass-class.html b/testing/test_package_docs/two_exports/ExtendingClass-class.html index 7ac640892a..f7f4c382fc 100644 --- a/testing/test_package_docs/two_exports/ExtendingClass-class.html +++ b/testing/test_package_docs/two_exports/ExtendingClass-class.html @@ -95,7 +95,7 @@
two_exports

Extending class extends BaseClass.

Also check out topLevelVariable.

-

This should not work: linking over to Apple.

+

This also should work: linking over to Apple.