-
Notifications
You must be signed in to change notification settings - Fork 170
avoid_shadowing #872
avoid_shadowing #872
Changes from 6 commits
6dd01e5
eb807eb
f5eff00
68ec3db
60814b8
ec198d2
7570ccd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
library linter.src.rules.avoid_shadowing; | ||
|
||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/ast/visitor.dart'; | ||
import 'package:analyzer/dart/element/element.dart'; | ||
import 'package:linter/src/analyzer.dart'; | ||
|
||
const desc = r'Do not use a name already visible.'; | ||
|
||
const details = r''' | ||
|
||
**DO** Do not use a name already visible as getter or variable. | ||
|
||
**BAD:** | ||
``` | ||
var k = null; | ||
|
||
class A { | ||
var a; | ||
} | ||
|
||
class B extends A { | ||
get b => null; | ||
|
||
m() { | ||
var a; // LINT | ||
var b; // LINT | ||
var k; // LINT | ||
} | ||
} | ||
``` | ||
|
||
**GOOD:** | ||
``` | ||
var k = null; | ||
|
||
class A { | ||
var a; | ||
} | ||
|
||
class B extends A { | ||
get b => null; | ||
|
||
m() { | ||
var c; // OK | ||
var d; // OK | ||
var e; // OK | ||
} | ||
} | ||
``` | ||
'''; | ||
|
||
class AvoidShadowing extends LintRule implements NodeLintRule { | ||
AvoidShadowing() | ||
: super( | ||
name: 'avoid_shadowing', | ||
description: desc, | ||
details: details, | ||
group: Group.errors); | ||
|
||
@override | ||
void registerNodeProcessors(NodeLintRegistry registry) { | ||
final visitor = new _Visitor(this); | ||
registry.addVariableDeclarationStatement(this, visitor); | ||
} | ||
} | ||
|
||
class _Visitor extends SimpleAstVisitor { | ||
final LintRule rule; | ||
|
||
_Visitor(this.rule); | ||
|
||
@override | ||
visitVariableDeclarationStatement(VariableDeclarationStatement node) { | ||
final variables = node.variables.variables.toList(); | ||
final library = variables.first.declaredElement.library; | ||
|
||
// exclude pattern : var name = this.name; | ||
variables.removeWhere((variable) { | ||
final initializer = variable.initializer; | ||
return initializer is PropertyAccess && | ||
initializer.propertyName.name == variable.name.name && | ||
(initializer.target is ThisExpression || | ||
initializer.target is SuperExpression); | ||
}); | ||
|
||
// exclude pattern : same name as current getter name | ||
var currentGetter = _getCurrentGetter(node); | ||
if (currentGetter != null) { | ||
variables.removeWhere( | ||
(variable) => currentGetter.name.name == variable.name.name); | ||
} | ||
|
||
bool skipInstanceMembers = false; | ||
AstNode current = node; | ||
while (current != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem very efficient, but perhaps in practice it doesn't matter. Have we done any timing measures for this rule? One easy improvement would be to return before line 98 if |
||
if (current is ClassDeclaration) { | ||
_checkClass(current, variables, onlyStatics: skipInstanceMembers); | ||
} else if (current is ConstructorDeclaration) { | ||
_checkParameters(current.parameters, variables); | ||
if (current.factoryKeyword != null) { | ||
skipInstanceMembers = true; | ||
} | ||
} else if (current is MethodDeclaration) { | ||
_checkParameters(current.parameters, variables); | ||
if (current.isStatic) { | ||
skipInstanceMembers = true; | ||
} | ||
} else if (current is FunctionExpression) { | ||
_checkParameters(current.parameters, variables); | ||
} else if (current is FunctionDeclaration) { | ||
_checkParameters(current.functionExpression.parameters, variables); | ||
} else if (current is FunctionDeclarationStatement) { | ||
_checkParameters( | ||
current.functionDeclaration.functionExpression.parameters, | ||
variables); | ||
} | ||
|
||
if (current.parent is Block) { | ||
_checkParentBlock(current, variables); | ||
} | ||
|
||
current = current.parent; | ||
} | ||
_checkLibrary(library, variables); | ||
} | ||
|
||
void _checkClass( | ||
ClassDeclaration clazz, | ||
List<VariableDeclaration> variables, { | ||
bool onlyStatics, | ||
}) { | ||
for (final variable in variables) { | ||
final name = variable.name.name; | ||
final getter = clazz.declaredElement | ||
.lookUpGetter(name, clazz.declaredElement.library); | ||
if (getter != null && (!onlyStatics || getter.isStatic)) | ||
rule.reportLint(variable); | ||
} | ||
} | ||
|
||
void _checkLibrary( | ||
LibraryElement library, List<VariableDeclaration> variables) { | ||
final topLevelVariableNames = library.units | ||
.expand((u) => u.topLevelVariables) | ||
.map((e) => e.name) | ||
.toList(); | ||
for (final variable in variables) { | ||
final name = variable.name.name; | ||
if (topLevelVariableNames.contains(name)) { | ||
rule.reportLint(variable); | ||
} | ||
} | ||
} | ||
|
||
void _checkParameters( | ||
FormalParameterList parameters, List<VariableDeclaration> variables) { | ||
if (parameters == null) return; | ||
|
||
final parameterNames = | ||
parameters.parameterElements.map((e) => e.name).toList(); | ||
|
||
for (final variable in variables) { | ||
final name = variable.name.name; | ||
if (parameterNames.contains(name)) { | ||
rule.reportLint(variable); | ||
} | ||
} | ||
} | ||
|
||
void _checkParentBlock(AstNode node, List<VariableDeclaration> variables) { | ||
final block = node.parent as Block; | ||
final names = <String>[]; | ||
for (final statement in block.statements.takeWhile((n) => n != node)) { | ||
if (statement is VariableDeclarationStatement) { | ||
names.addAll(statement.variables.variables.map((e) => e.name.name)); | ||
} | ||
} | ||
for (final variable in variables) { | ||
final name = variable.name.name; | ||
if (names.contains(name)) { | ||
rule.reportLint(variable); | ||
} | ||
} | ||
} | ||
|
||
MethodDeclaration _getCurrentGetter(VariableDeclarationStatement node) { | ||
AstNode current = node.parent; | ||
while (current != null) { | ||
if (current is Block || current is FunctionBody) { | ||
current = current.parent; | ||
} else if (current is MethodDeclaration && current.isGetter) { | ||
return current; | ||
} else { | ||
break; | ||
} | ||
} | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
// test w/ `pub run test -N avoid_shadowing` | ||
|
||
var top = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see any tests for shadowing parameter names. Also, I suspect that the rule as written will produce multiple lints for the same location if the name being shadowed is defined in multiple locations. Consider a test such as:
(Can we verify that only one lint is produced in this case?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. It looks like several report on the same token for the same rule do not result in lint duplications. |
||
|
||
m() { | ||
f() { | ||
var top; // LINT | ||
var a; // OK | ||
} | ||
|
||
var a; // OK | ||
var b; // OK | ||
var top; // LINT | ||
|
||
g() { | ||
var a; // LINT | ||
var c; // OK | ||
} | ||
} | ||
|
||
class A { | ||
var a; | ||
get b => null; | ||
|
||
ma() { | ||
var a; // LINT | ||
var b; // LINT | ||
var top; // LINT | ||
} | ||
|
||
static sm() { | ||
var a; // OK | ||
var b; // OK | ||
var c; // OK | ||
} | ||
} | ||
|
||
class B extends A { | ||
mb() { | ||
var a; // LINT | ||
var b; // LINT | ||
var c; // OK | ||
} | ||
} | ||
|
||
class C extends Object with A { | ||
mc() { | ||
var a; // LINT | ||
var b; // LINT | ||
var c; // OK | ||
} | ||
} | ||
|
||
// special case for factory and static | ||
class D { | ||
var a; | ||
get b => null; | ||
static get c => null; | ||
|
||
static sm() { | ||
var a; // OK | ||
var b; // OK | ||
var c; // LINT | ||
} | ||
|
||
factory D() { | ||
var a; // OK | ||
var b; // OK | ||
var c; // LINT | ||
} | ||
} | ||
|
||
// 2 shadowing | ||
class E { | ||
int x; | ||
m(int x) { | ||
int x; // LINT | ||
} | ||
} | ||
|
||
// exclude pattern : var x = this.x; | ||
class F { | ||
int x; | ||
m() { | ||
var x = this.x; // OK | ||
} | ||
} | ||
|
||
// exclude pattern : var x = super.x; | ||
class F2 extends F { | ||
m() { | ||
var x = super.x; // OK | ||
} | ||
} | ||
|
||
// exclude pattern : same name as current getter name | ||
class G { | ||
var i; | ||
get g { | ||
var i = ''; // LINT | ||
var g = ''; // OK | ||
return g; | ||
} | ||
} |
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.
Please remove the library directive.