Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve non-imported symbols in comments [#1153] #1298

Merged
merged 3 commits into from
Dec 27, 2016

Conversation

astashov
Copy link
Contributor

@astashov astashov commented Dec 17, 2016

@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 allow 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:

Testing: Unit tests, of course, also tried to generate Flutter docs with
that, and observed that the links for previously non-resolved symbols
work.

(fixes #1153)

@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
  (dart-lang#1153 (comment))
  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.
@googlebot googlebot added the cla: yes Google CLA check succeeded. label Dec 17, 2016
@devoncarew
Copy link
Member

Reviewing now -

What to do in case of ambiguity here? Show a warning, crash with an error message, ignore?

I believe @Hixie mentioned fast-fail. We should definitely at least warn. Dartdoc does generate docs unless there are significant parse errors in the code - it's lenient towards somewhat bad source, so you can always generate docs.

The approach the analyzer takes is to allow clients to opt-into failing on warnings (--fatal-warnings). This is probably what we should do here as well. It's outside the scope of this PR however, so I would say warn, and open an issue to add a --fatal-warnings flag and associated behavior.

Do we want to show short name in case a fully qualified name is provided? I saw in the comments in the ticket

Sounds like (from @Hixie) yes :)

@devoncarew
Copy link
Member

Re: material.Icon.icon as the fully qualified name - there are cases where this format is not unique (specifically, in the flutter package, there are two libraries with the name material). While that's a nice, clean fully specified format, we may need something more unique.

}
final linkedElement = result.element;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the type here (and below).

return new MatchingLinkResult(result.values.first, result.values.first.name);
} else {
if (_emitWarning) {
print("Abiguous reference '${codeRef}' in '${element.fullyQualifiedName}', " +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sp: ambiguous

Also, can you get the file and line number here in order to print them out?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like: Ambiguous reference to [foo], lib/my_lib.dart, line 100.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, I see that we're not doing this (locating by file and line number) in the original code, so while this would be great, it's not necessary for this PR.

}

MatchingLinkResult _findRefElementInLibrary(String codeRef, ModelElement element, List<CommentReference> commentRefs) {
final package = element.library.package;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the type here

MatchingLinkResult _findRefElementInLibrary(String codeRef, ModelElement element, List<CommentReference> commentRefs) {
final package = element.library.package;
final Map<String, ModelElement> result = {};
package.libraries.forEach((library) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lookup should probably live somewhere more general, like as a method on Package. That would probably also help with performance issues, as the set of all symbols (and their fully-qualified names) could be calculated once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good point!

@devoncarew
Copy link
Member

OK, looks like String get fullyQualifiedName already exists on model elements. Let's try the existing libName[.enclosingElement].symbolName and see how that works.

* Add types to declarations
* Add filename/line number to the warnings
* Move the code for gathering all the model elements of a package to the
  package's accessor and cache it.
@astashov
Copy link
Contributor Author

@devoncarew I addessed your feedback, thanks! PTAL

@@ -1425,6 +1425,26 @@ abstract class ModelElement implements Comparable, Nameable, Documentable {
return (_fullyQualifiedName ??= _buildFullyQualifiedName());
}

String get sourceFileName {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this can use the function shorthand (=>)

return (modelElement.element.computeNode() as AnnotatedNode)
.documentationComment
.references;
if ((modelElement.element.computeNode() as AnnotatedNode).documentationComment != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're doing this cast twice inside this if statement (modelElement.element.computeNode() as AnnotatedNode) - perhaps create a new local variable to host the AnnotatedNode?

result[modelElement.fullyQualifiedName] = modelElement;
}
});
package.allModelElements.forEach((modelElement) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last comment, I promise :)

If we use a Map here, in the event we have multiple matches, the entry in the map could be overridden. So that would hide the fact that the reference is not unique. Perhaps use a List instead?

And we'll be iterating over a lot of elements - I tend to avoid using a closure for that; a for loop will be more performant.

You might do something thuswise:

List<ModelElement> results = [];

for (ModelElement element in package.allModelElements) {
  if (codeRef.contains('.')) {
    // It's a fully qualified reference.
    if (element.fullyQualifiedName == codeRef) {
      results.add(element);
      break;
    }
  } else {
    if (element.name == codeRef) {
      results.add(element);
    }
  }
}

@devoncarew
Copy link
Member

@astashov, I think there were one or two issues w/ the lookup code - I added a proposal code snippet; everything else looks good.

@astashov
Copy link
Contributor Author

Hmm, it seems like there's one drawback with using fullyQualifiedName - it's the same for inherited methods/properties. E.g. tons of classes inherit from Widget in Flutter, so they all have key property, and all their fully qualified name are material.Widget.key. So, I see a lot of warnings in Flutter docs like this:

Ambiguous reference to [key] in 'material.State.didUpdateConfig' (/Users/anton/projects/flutter/packages/flutter/lib/src/widgets/framework.dart:785). We found matches to the following elements: 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.DataRow.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.MergeableMaterialItem.key', 'material.Widget.key', 'material.MergeableMaterialItem.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.MergeableMaterialItem.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.TableRow.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key', 'material.Widget.key'

@Hixie
Copy link
Contributor

Hixie commented Dec 21, 2016

Isn't that fine? They can all just point to Widget.key.

@Hixie
Copy link
Contributor

Hixie commented Dec 21, 2016

Having said that, I don't think we'd want [key] to match material.Widget.key. I think we just want things in the global scope of their respective libraries to be accessible without qualification.

@astashov
Copy link
Contributor Author

Isn't that fine? They can all just point to Widget.key.

Oh right, that's actually fine. Sorry :)

Having said that, I don't think we'd want [key] to match material.Widget.key. I think we just want things in the global scope of their respective libraries to be accessible without qualification.

Umm, now I'm lost :) Could you please elaborate? Why you don't want key to match material.Widget.key? Because it's a different library?

@Hixie
Copy link
Contributor

Hixie commented Dec 21, 2016

Because it's not in scope. Conceptually, the way I imagine this working is that you import all the libraries. But even if you did that, in code, key wouldn't resolve to Widget.key, right? You can only get to Widget.key if you qualify it via an instance of the class (or for statics via the class itself).

More practically, if we consider every member of every class to be in scope everywhere, we're going to have conflicts up the wazoo. :-)

@astashov
Copy link
Contributor Author

So, you don't want [key] to match material.Widget.key, you only want key to match material.key or something like that? I.e. the top level stuff only? And Widget.key to match material.Widget.key. Something like that?

@Hixie
Copy link
Contributor

Hixie commented Dec 22, 2016

That would be my recommendation, yeah.

@devoncarew
Copy link
Member

Cycling back to this, what @Hixie says makes sense. It sounds like material.Widget.key would be the fully qualified way to specify key, and Widget.key would be legal shorthand for it as long as there wasn't a Widget.key in another library you're generating docs for.

Similarly with material.Widget and Widget.

From now on, if the reference was, for example, `[key]`, we won't match
`material.Widget.key`, we will only match `material.key` (i.e. top-level
`key` symbol). If you still want to match `Widget.key`, you should use
either `[Widget.key]` or `[material.Widget.key]`.
@astashov
Copy link
Contributor Author

Okay, I've updated the PR. So, from now on, if the reference was, for example, [key], we won't match material.Widget.key, we will only match material.key (i.e. top-level key symbol). If you still want to match Widget.key, you should use either [Widget.key] or [material.Widget.key].

That's how you want it to work, right?

@Hixie
Copy link
Contributor

Hixie commented Dec 23, 2016

How do I know what value to use for the library name? Is it (exclusively) the identifiers used with library keywords?

@astashov
Copy link
Contributor Author

Yeah, I think so. E.g. Widget is in lib/src/widgets/framework.dart, which is exported in lib/widgets.dart, and that file has library widgets; on top, so it will be widgets.Widget. The confusing thing that e.g. lib/material.dart also exports lib/widgets.dart, so material.Widget is also a valid reference. I believe there's an issue for that though (#1158)

@Hixie
Copy link
Contributor

Hixie commented Dec 24, 2016

SGTM

@devoncarew
Copy link
Member

lgtm! Landing...

@devoncarew devoncarew merged commit 3c653b4 into dart-lang:master Dec 27, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Google CLA check succeeded.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

handle dartdoc references to non-imported symbols (was: dartdoc-specific imports)
4 participants