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

add unused_top_members_in_executable_libraries #3513

Merged

Conversation

a14n
Copy link
Contributor

@a14n a14n commented Jul 12, 2022

Description

Report unused top-level members in executable libraries.

Fixes #2395

@coveralls
Copy link

coveralls commented Jul 12, 2022

Coverage Status

Coverage increased (+0.03%) to 95.506% when pulling 7bbd899 on a14n:unused_top_members_in_executable_libraries into f7d2da0 on dart-lang:main.

Copy link
Member

@bwilkerson bwilkerson left a comment

Choose a reason for hiding this comment

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

It isn't clear to me that this is a rule we want to have.

As noted below, it will have false positives unless a convention is followed, and there's no support for enforcing that convention. If we think that this is a convention that we should support, then we should probably have a lint that ensures that it isn't violate. It seems to me that without that enforcement we run a risk of having a high rate of false positives from this rule.

const _details = r'''

Top-level members in an executable library should be used directly inside this
library. Executable library are usually never used as dependency and it's better
Copy link
Member

Choose a reason for hiding this comment

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

nit: "library" --> "libraries"

More importantly, though, this needs to be reworded to make it clear that this lint assumes a convention in which libraries containing main are not imported by other libraries, and that there is no support for ensuring adherence to such a convention.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a comment regarding the assumption.

return false;
}

bool _isInsideExecutableLibrary(AstNode node) {
Copy link
Member

Choose a reason for hiding this comment

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

The node is always a CompilationUnit, so I don't think you need the first two lines (and the parameter type should be narrowed).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I changed the test to check if there is a declaration conforming the _isEntryPoint function (inlined).

if (root is! CompilationUnit) return false;
var library = root.declaredElement?.library;
return library != null &&
library.exportNamespace.definedNames.containsKey('main');
Copy link
Member

Choose a reason for hiding this comment

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

I think this can be replaced by library?.entryPoint != null. If so, consider inlining this method.

Copy link
Contributor Author

@a14n a14n left a comment

Choose a reason for hiding this comment

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

I ran this lint on flutter (see flutter/flutter#107464) and I didn't face false positives.

const _details = r'''

Top-level members in an executable library should be used directly inside this
library. Executable library are usually never used as dependency and it's better
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a comment regarding the assumption.

return false;
}

bool _isInsideExecutableLibrary(AstNode node) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I changed the test to check if there is a declaration conforming the _isEntryPoint function (inlined).

@a14n
Copy link
Contributor Author

a14n commented Jul 22, 2022

PTAL

@pq pq requested a review from srawlins July 27, 2022 16:15
@pq
Copy link
Member

pq commented Jul 27, 2022

@srawlins, since you provided motivation in #2395, could you take a look?

// BSD-style license that can be found in the LICENSE file.

// test w/ `dart test -N unused_top_members_in_executable_libraries`

Copy link
Member

Choose a reason for hiding this comment

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

How do you want to treat doc comments? I think they are probably not use:

class C {}

/// I love [C].
void main() {}

Copy link
Member

Choose a reason for hiding this comment

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

However we decide this, there should be a test asserting the expected behavior.

I would prefer to not count it as usage, but I don't have a strong opinion.

@srawlins
Copy link
Member

@pq is there a way to benchmark this before it is landed? I worry about performance on this one because of traverseNodesInDFS.

@pq
Copy link
Member

pq commented Jul 27, 2022

is there a way to benchmark this before it is landed?

Great question! The good news is each PR triggers the benchmark bot and that gives us a feel. If you expand the checks, you'll see it:

image

Click on details and you can look at the output.

In this case, you're right that as implemented, this is relatively costly:

image

@a14n a14n force-pushed the unused_top_members_in_executable_libraries branch from aa0aa82 to 9911da4 Compare July 29, 2022 09:09
element.isPublic &&
!element.hasVisibleForTesting;
});
unusedMembers.forEach(rule.reportLint);
Copy link
Member

Choose a reason for hiding this comment

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

Given that these are declarations, it would be better if we could report the lint on the name of the declaration, or the extension keyword in the case of an unnamed extension.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

copybara-service bot pushed a commit to dart-lang/sdk that referenced this pull request Aug 2, 2022
For dart-lang/linter#3513

Change-Id: Ia522fc8958c2c7c48e3366a874ceae28cb9ed6bf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/253302
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
lib/src/rules/unreachable_from_main.dart Show resolved Hide resolved
test_data/rules/unreachable_from_main.dart Show resolved Hide resolved
lib/src/rules/unreachable_from_main.dart Outdated Show resolved Hide resolved
lib/src/rules/unreachable_from_main.dart Outdated Show resolved Hide resolved
lib/src/rules/unreachable_from_main.dart Outdated Show resolved Hide resolved
lib/src/rules/unreachable_from_main.dart Outdated Show resolved Hide resolved
lib/src/rules/unreachable_from_main.dart Show resolved Hide resolved
@a14n
Copy link
Contributor Author

a14n commented Aug 3, 2022

PTAL

@a14n
Copy link
Contributor Author

a14n commented Aug 8, 2022

I perhaps missed some comments. Is there something blocking on this PR?

Top-level members in an executable library should be used directly inside this
library. An executable library is a library that contains a `main` top-level
function or that contains a top-level function annotated with
`@pragma('vm:entry-point')`). Executable libraries are usually never imported
Copy link
Member

Choose a reason for hiding this comment

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

nit: "usually never imported" is kind of an odd construct here. Maybe something like "typically not imported" (or "not typically imported"? IANATW*), or "not usually imported". I usually lean on @bwilkerson for word-smithing.

* I am not a technical writer

bool _isPragmaVmEntry(Annotation annotation) {
var elementAnnotation = annotation.elementAnnotation;
if (elementAnnotation != null) {
var value = elementAnnotation.computeConstantValue();
Copy link
Member

Choose a reason for hiding this comment

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

That is a good test case, but we can still do a pragma element check before we evaluate the constant. Something like:

var element = annotation.elementAnnotation?.element;
DartType elementType;
if (element is ConstructorElement) {
  elementType = element.returnType;
} else if (element is PropertyAccessorElement && element.isGetter) {
  elementType = element.returnType;
} else {
  // Dunno what this is.
  return false;
}
if (elementType.element.name != 'pragma' || !elementType.element.library.isDartCore) {
  return false;
}

This is similar to what analyzer does for DartType.isDartCoreBool etc in lib/src/dart/element/type.dart.

This works for the existing entryPoint annotation you have, as well as:

const entryPoint2 = entryPoint;
@entryPoint2
void f77() {} // OK

It looks like pragma cannot be extended because the only public constructor is a factory constructor. It can still be implemented, but I see no code (like test cases) with extends pragma so I don't think it's important. Since pragma cannot be extended I don't think we have to worry about other constructors and return types not being exactly the pragma element.

// BSD-style license that can be found in the LICENSE file.

// test w/ `dart test -N unused_top_members_in_executable_libraries`

Copy link
Member

Choose a reason for hiding this comment

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

However we decide this, there should be a test asserting the expected behavior.

I would prefer to not count it as usage, but I don't have a strong opinion.

@srawlins
Copy link
Member

srawlins commented Aug 9, 2022

I forgot to click Send on my last 3 comments; sorry about that! I think my open comments amount to:

  • How do we treat references in comments?
  • Can we avoid evaluating the @pragma constant until we know its a @pragma annotation?

@a14n
Copy link
Contributor Author

a14n commented Aug 9, 2022

  • How do we treat references in comments?

There was already a test to trigger a diagnostic on elements only ref in comment (see Comment in test file)

  • Can we avoid evaluating the @pragma constant until we know its a @pragma annotation?

Done. Thanks for the code snippet.

@a14n a14n force-pushed the unused_top_members_in_executable_libraries branch from 8d5f93f to 7bbd899 Compare August 9, 2022 09:54
@a14n
Copy link
Contributor Author

a14n commented Aug 9, 2022

After rebasing on main (and upgrading the analyzer) references in comments are now visited. I updated the test to not mark Comment as unreachable.

PTAL

Copy link
Member

@srawlins srawlins left a comment

Choose a reason for hiding this comment

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

Looks great! Thanks! 🎉

Top-level members in an executable library should be used directly inside this
library. An executable library is a library that contains a `main` top-level
function or that contains a top-level function annotated with
`@pragma('vm:entry-point')`). Executable libraries are not usually imported
Copy link
Member

Choose a reason for hiding this comment

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

I think there is a stray ) here, at the end of the sentence.

@srawlins srawlins merged commit 9b1f555 into dart-lang:main Aug 10, 2022
mockturtl added a commit to mockturtl/tidy that referenced this pull request Aug 10, 2022
@a14n a14n deleted the unused_top_members_in_executable_libraries branch August 28, 2022 13:51
copybara-service bot pushed a commit to dart-lang/sdk that referenced this pull request Aug 23, 2023
* add unused_top_members_in_executable_libraries

* address review comments

* address review comments

* rename to unreachable_from_main

* report on name

* address review comments

* address review comments

* fix post-rebase
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Report unused public elements in an entrypoint library
5 participants