Skip to content

Commit 5e36364

Browse files
authored
unreachable_from_main: Report unreachable constructors (dart-archive/linter#4162)
1 parent 85f50c6 commit 5e36364

File tree

2 files changed

+433
-19
lines changed

2 files changed

+433
-19
lines changed

lib/src/rules/unreachable_from_main.dart

Lines changed: 148 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import 'package:analyzer/dart/ast/ast.dart';
88
import 'package:analyzer/dart/ast/visitor.dart';
99
import 'package:analyzer/dart/element/element.dart';
1010
import 'package:analyzer/dart/element/type.dart';
11+
import 'package:collection/collection.dart';
1112

1213
import '../analyzer.dart';
1314

1415
const _desc = 'Unreachable top-level members in executable libraries.';
1516

1617
const _details = r'''
17-
Top-level members in an executable library should be used directly inside this
18-
library. An executable library is a library that contains a `main` top-level
19-
function or that contains a top-level function annotated with
18+
Top-level members and static members in an executable library should be used
19+
directly inside this library. An executable library is a library that contains
20+
a `main` top-level function or that contains a top-level function annotated with
2021
`@pragma('vm:entry-point')`). Executable libraries are not usually imported
2122
and it's better to avoid defining unused members.
2223
@@ -43,7 +44,7 @@ void f() {}
4344

4445
class UnreachableFromMain extends LintRule {
4546
static const LintCode code = LintCode('unreachable_from_main',
46-
'Unreachable top-level member in an executable library.',
47+
"Unreachable member '{0}' in an executable library.",
4748
correctionMessage: 'Try referencing the member or removing it.');
4849

4950
UnreachableFromMain()
@@ -99,7 +100,12 @@ class _DeclarationGatherer {
99100
}
100101

101102
void _addStaticMember(ClassMember member) {
102-
if (member is FieldDeclaration && member.isStatic) {
103+
if (member is ConstructorDeclaration) {
104+
var e = member.declaredElement;
105+
if (e != null && e.isPublic && member.parent is! EnumDeclaration) {
106+
declarations.add(member);
107+
}
108+
} else if (member is FieldDeclaration && member.isStatic) {
103109
for (var field in member.fields.variables) {
104110
var e = field.declaredElement;
105111
if (e != null && e.isPublic) {
@@ -115,20 +121,72 @@ class _DeclarationGatherer {
115121
}
116122
}
117123

118-
/// A visitor which gathers the declarations of the identifiers it visits.
119-
class _IdentifierVisitor extends RecursiveAstVisitor {
124+
/// A visitor which gathers the declarations of the "references" it visits.
125+
///
126+
/// "References" are most often [SimpleIdentifier]s, but can also be other
127+
/// nodes which refer to a declaration.
128+
// TODO(srawlins): Add support for patterns.
129+
class _ReferenceVisitor extends RecursiveAstVisitor {
120130
Map<Element, Declaration> declarationMap;
121131

122132
Set<Declaration> declarations = {};
123133

124-
_IdentifierVisitor(this.declarationMap);
134+
_ReferenceVisitor(this.declarationMap);
135+
136+
@override
137+
void visitAnnotation(Annotation node) {
138+
var e = node.element;
139+
if (e != null) {
140+
_addDeclaration(e);
141+
}
142+
super.visitAnnotation(node);
143+
}
125144

126145
@override
127146
void visitAssignmentExpression(AssignmentExpression node) {
128147
_visitCompoundAssignmentExpression(node);
129148
super.visitAssignmentExpression(node);
130149
}
131150

151+
@override
152+
void visitClassDeclaration(ClassDeclaration node) {
153+
var element = node.declaredElement;
154+
if (element != null) {
155+
var hasConstructors =
156+
node.members.any((e) => e is ConstructorDeclaration);
157+
if (!hasConstructors) {
158+
// The default constructor will have an implicit super-initializer to
159+
// the super-type's unnamed constructor.
160+
_addDefaultSuperConstructorDeclaration(node);
161+
}
162+
}
163+
super.visitClassDeclaration(node);
164+
}
165+
166+
@override
167+
void visitConstructorDeclaration(ConstructorDeclaration node) {
168+
// If a constructor does not have an explicit super-initializer (or redirection?)
169+
// then it has an implicit super-initializer to the super-type's unnamed constructor.
170+
var hasSuperInitializer =
171+
node.initializers.any((e) => e is SuperConstructorInvocation);
172+
if (!hasSuperInitializer) {
173+
var enclosingClass = node.parent;
174+
if (enclosingClass is ClassDeclaration) {
175+
_addDefaultSuperConstructorDeclaration(enclosingClass);
176+
}
177+
}
178+
super.visitConstructorDeclaration(node);
179+
}
180+
181+
@override
182+
void visitConstructorName(ConstructorName node) {
183+
var e = node.staticElement;
184+
if (e != null) {
185+
_addDeclaration(e);
186+
}
187+
super.visitConstructorName(node);
188+
}
189+
132190
@override
133191
void visitPostfixExpression(PostfixExpression node) {
134192
_visitCompoundAssignmentExpression(node);
@@ -141,6 +199,16 @@ class _IdentifierVisitor extends RecursiveAstVisitor {
141199
super.visitPrefixExpression(node);
142200
}
143201

202+
@override
203+
void visitRedirectingConstructorInvocation(
204+
RedirectingConstructorInvocation node) {
205+
var element = node.staticElement;
206+
if (element != null) {
207+
_addDeclaration(element);
208+
}
209+
super.visitRedirectingConstructorInvocation(node);
210+
}
211+
144212
@override
145213
void visitSimpleIdentifier(SimpleIdentifier node) {
146214
if (!node.inDeclarationContext()) {
@@ -152,6 +220,15 @@ class _IdentifierVisitor extends RecursiveAstVisitor {
152220
super.visitSimpleIdentifier(node);
153221
}
154222

223+
@override
224+
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
225+
var e = node.staticElement;
226+
if (e != null) {
227+
_addDeclaration(e);
228+
}
229+
super.visitSuperConstructorInvocation(node);
230+
}
231+
155232
/// Adds the declaration of the top-level element which contains [element] to
156233
/// [declarations], if it is found in [declarationMap].
157234
///
@@ -167,8 +244,8 @@ class _IdentifierVisitor extends RecursiveAstVisitor {
167244
declarations.add(enclosingTopLevelDeclaration);
168245
}
169246

170-
// Also add [element]'s declaration if it is a static accessor or static
171-
// method.
247+
// Also add [element]'s declaration if it is a constructor, static accessor,
248+
// or static method.
172249
if (element.isPrivate) {
173250
return;
174251
}
@@ -178,7 +255,7 @@ class _IdentifierVisitor extends RecursiveAstVisitor {
178255
}
179256
if (enclosingElement is InterfaceElement ||
180257
enclosingElement is ExtensionElement) {
181-
if (element is PropertyAccessorElement && element.isStatic) {
258+
if (element is ConstructorElement) {
182259
var declaration = declarationMap[element];
183260
if (declaration != null) {
184261
declarations.add(declaration);
@@ -188,6 +265,23 @@ class _IdentifierVisitor extends RecursiveAstVisitor {
188265
if (declaration != null) {
189266
declarations.add(declaration);
190267
}
268+
} else if (element is PropertyAccessorElement && element.isStatic) {
269+
var declaration = declarationMap[element];
270+
if (declaration != null) {
271+
declarations.add(declaration);
272+
}
273+
}
274+
}
275+
}
276+
277+
void _addDefaultSuperConstructorDeclaration(ClassDeclaration class_) {
278+
var classElement = class_.declaredElement;
279+
var supertype = classElement?.supertype;
280+
if (supertype != null) {
281+
var unnamedConstructor =
282+
supertype.constructors.firstWhereOrNull((e) => e.name.isEmpty);
283+
if (unnamedConstructor != null) {
284+
_addDeclaration(unnamedConstructor);
191285
}
192286
}
193287
}
@@ -241,13 +335,14 @@ class _Visitor extends SimpleAstVisitor<void> {
241335
}
242336
}
243337

244-
// The set of the declarations which each top-level declaration references.
338+
// The set of the declarations which each top-level and static declaration
339+
// references.
245340
var dependencies = <Declaration, Set<Declaration>>{};
246341

247342
// Map each declaration to the collection of declarations which are
248343
// referenced within its body.
249344
for (var declaration in declarations) {
250-
var visitor = _IdentifierVisitor(declarationByElement);
345+
var visitor = _ReferenceVisitor(declarationByElement);
251346
declaration.accept(visitor);
252347
dependencies[declaration] = visitor.declarations;
253348
}
@@ -276,15 +371,25 @@ class _Visitor extends SimpleAstVisitor<void> {
276371
});
277372

278373
for (var member in unusedMembers) {
279-
if (member is NamedCompilationUnitMember) {
280-
rule.reportLintForToken(member.name);
374+
if (member is ConstructorDeclaration) {
375+
if (member.name == null) {
376+
rule.reportLint(member.returnType, arguments: [member.nameForError]);
377+
} else {
378+
rule.reportLintForToken(member.name,
379+
arguments: [member.nameForError]);
380+
}
381+
} else if (member is NamedCompilationUnitMember) {
382+
rule.reportLintForToken(member.name, arguments: [member.nameForError]);
281383
} else if (member is VariableDeclaration) {
282-
rule.reportLintForToken(member.name);
384+
rule.reportLintForToken(member.name, arguments: [member.nameForError]);
283385
} else if (member is ExtensionDeclaration) {
386+
var memberName = member.name;
284387
rule.reportLintForToken(
285-
member.name ?? member.firstTokenAfterCommentAndMetadata);
388+
memberName ?? member.firstTokenAfterCommentAndMetadata,
389+
arguments: [member.nameForError]);
286390
} else {
287-
rule.reportLintForToken(member.firstTokenAfterCommentAndMetadata);
391+
rule.reportLintForToken(member.firstTokenAfterCommentAndMetadata,
392+
arguments: [member.nameForError]);
288393
}
289394
}
290395
}
@@ -323,3 +428,28 @@ extension on Annotation {
323428
return type is InterfaceType && type.element.isPragma;
324429
}
325430
}
431+
432+
extension on Declaration {
433+
String get nameForError {
434+
// TODO(srawlins): Move this to analyzer when other uses are found.
435+
// TODO(srawlins): Convert to switch-expression, hopefully.
436+
var self = this;
437+
if (self is ConstructorDeclaration) {
438+
return self.name?.lexeme ?? self.returnType.name;
439+
} else if (self is EnumConstantDeclaration) {
440+
return self.name.lexeme;
441+
} else if (self is ExtensionDeclaration) {
442+
var name = self.name;
443+
return name?.lexeme ?? 'the unnamed extension';
444+
} else if (self is MethodDeclaration) {
445+
return self.name.lexeme;
446+
} else if (self is NamedCompilationUnitMember) {
447+
return self.name.lexeme;
448+
} else if (self is VariableDeclaration) {
449+
return self.name.lexeme;
450+
}
451+
452+
assert(false, 'Uncovered Declaration subtype: ${self.runtimeType}');
453+
return '';
454+
}
455+
}

0 commit comments

Comments
 (0)