| // Copyright (c) 2017, 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/analysis_rule/rule_context.dart'; |
| import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/error/error.dart'; |
| |
| import '../analyzer.dart'; |
| import '../ast.dart'; |
| import '../util/scope.dart'; |
| |
| const _desc = r"Don't access members with `this` unless avoiding shadowing."; |
| |
| class UnnecessaryThis extends LintRule { |
| UnnecessaryThis() |
| : super(name: LintNames.unnecessary_this, description: _desc); |
| |
| @override |
| DiagnosticCode get diagnosticCode => LinterLintCode.unnecessaryThis; |
| |
| @override |
| void registerNodeProcessors( |
| RuleVisitorRegistry registry, |
| RuleContext context, |
| ) { |
| var visitor = _Visitor(this, context); |
| registry.addConstructorFieldInitializer(this, visitor); |
| registry.addThisExpression(this, visitor); |
| } |
| } |
| |
| class _Visitor extends SimpleAstVisitor<void> { |
| final LintRule rule; |
| |
| final RuleContext context; |
| |
| _Visitor(this.rule, this.context); |
| |
| @override |
| void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| var thisKeyword = node.thisKeyword; |
| if (thisKeyword != null) { |
| rule.reportAtToken(thisKeyword); |
| } |
| } |
| |
| @override |
| void visitThisExpression(ThisExpression node) { |
| var parent = node.parent; |
| |
| Element? element; |
| if (parent is PropertyAccess && !parent.isNullAware) { |
| element = getWriteOrReadElement(parent.propertyName); |
| } else if (parent is MethodInvocation && !parent.isNullAware) { |
| element = parent.methodName.element; |
| } else { |
| return; |
| } |
| |
| element = element?.baseElement; |
| if (_canReferenceElementWithoutThisPrefix(element, node)) { |
| rule.reportAtToken(node.thisKeyword); |
| } |
| } |
| |
| bool _canReferenceElementWithoutThisPrefix(Element? element, AstNode node) { |
| if (element == null) return false; |
| |
| var id = element.displayName; |
| var result = resolveNameInScope( |
| id, |
| node, |
| shouldResolveSetter: element is SetterElement, |
| ); |
| |
| // No result, definitely no shadowing. |
| // The requested element is inherited, or from an extension. |
| if (result.isNone) return true; |
| |
| var resultElement = result.element; |
| |
| // The result has the matching name, might be shadowing. |
| // Check that the element is the same. |
| if (result.isRequestedName) { |
| return resultElement == element; |
| } |
| |
| // The result has the same basename, but not the same name. |
| // Must be an instance member, so that: |
| // - not shadowed by a local declaration; |
| // - prevents us from going up to the library scope; |
| // - the requested element must be inherited, or from an extension. |
| if (result.isDifferentName) { |
| var enclosing = resultElement?.enclosingElement; |
| return enclosing is ClassElement; |
| } |
| |
| // Should not happen. |
| return false; |
| } |
| } |