This repository has been archived by the owner on Nov 20, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 170
avoid_shadowing #872
Closed
Closed
avoid_shadowing #872
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6dd01e5
avoid_shadowing
a14n eb807eb
address review comments
a14n f5eff00
exclude pattern : var x = this.x;
a14n 68ec3db
same name as getter
a14n 60814b8
update to the current linter rule style
a14n ec198d2
fix ci
a14n 7570ccd
address review comments
a14n File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
// 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. | ||
|
||
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 | ||
variables.removeWhere(_hasSameNameAsItsGetter); | ||
|
||
// exit on empty variables to check | ||
if (variables.isEmpty) return; | ||
|
||
bool skipInstanceMembers = false; | ||
AstNode current = node; | ||
while (current != null) { | ||
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); | ||
} | ||
} | ||
} | ||
|
||
/// excludes pattern: | ||
/// | ||
/// ```dart | ||
/// get name { | ||
/// var name; | ||
/// ... | ||
/// } | ||
/// ``` | ||
bool _hasSameNameAsItsGetter(VariableDeclaration node) { | ||
final name = node.name.name; | ||
VariableDeclarationList variableList = node.parent; | ||
final variableDeclarationStatement = variableList.parent; | ||
|
||
var current = variableDeclarationStatement.parent; | ||
if (current is! Block) return false; | ||
|
||
current = current.parent; | ||
if (current is! BlockFunctionBody) return false; | ||
|
||
current = current.parent; | ||
if (current is MethodDeclaration && current.isGetter) | ||
return current.name.name == name; | ||
if (current is FunctionExpression) { | ||
final parent = current.parent; | ||
if (parent is FunctionDeclaration && parent.isGetter) | ||
return parent.name.name == name; | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// 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 in class | ||
class G { | ||
var i; | ||
get g { | ||
var g = ''; // OK | ||
var i = ''; // LINT | ||
return g; | ||
} | ||
} | ||
|
||
// exclude pattern : same name as current getter name in top level | ||
get g { | ||
var g = ''; // OK | ||
var top = ''; // LINT | ||
return g; | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
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
variables
is empty.