-
Notifications
You must be signed in to change notification settings - Fork 166
add unused_top_members_in_executable_libraries #3513
add unused_top_members_in_executable_libraries #3513
Conversation
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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).
PTAL |
@srawlins, since you provided motivation in dart-lang/sdk#58292, could you take a look? |
test_data/rules/unused_top_members_in_executable_libraries.dart
Outdated
Show resolved
Hide resolved
test_data/rules/unused_top_members_in_executable_libraries.dart
Outdated
Show resolved
Hide resolved
// BSD-style license that can be found in the LICENSE file. | ||
|
||
// test w/ `dart test -N unused_top_members_in_executable_libraries` | ||
|
There was a problem hiding this comment.
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() {}
There was a problem hiding this comment.
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.
test_data/rules/unused_top_members_in_executable_libraries.dart
Outdated
Show resolved
Hide resolved
test_data/rules/unused_top_members_in_executable_libraries.dart
Outdated
Show resolved
Hide resolved
@pq is there a way to benchmark this before it is landed? I worry about performance on this one because of traverseNodesInDFS. |
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: Click on details and you can look at the output. In this case, you're right that as implemented, this is relatively costly: |
aa0aa82
to
9911da4
Compare
element.isPublic && | ||
!element.hasVisibleForTesting; | ||
}); | ||
unusedMembers.forEach(rule.reportLint); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
For dart-archive/linter#3513 Change-Id: Ia522fc8958c2c7c48e3366a874ceae28cb9ed6bf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/253302 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
test_data/rules/unused_top_members_in_executable_libraries.dart
Outdated
Show resolved
Hide resolved
PTAL |
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 |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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` | ||
|
There was a problem hiding this comment.
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.
I forgot to click Send on my last 3 comments; sorry about that! I think my open comments amount to:
|
There was already a test to trigger a diagnostic on elements only ref in comment (see
Done. Thanks for the code snippet. |
8d5f93f
to
7bbd899
Compare
After rebasing on PTAL |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
Description
Report unused top-level members in executable libraries.
Fixes dart-lang/sdk#58292