| // Copyright (c) 2025, 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/analysis_rule.dart'; | |
| import 'package:analyzer/analysis_rule/rule_context.dart'; | |
| import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; | |
| import 'package:analyzer/dart/analysis/features.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/dart/element/type.dart'; | |
| import 'package:analyzer/error/error.dart'; | |
| import '../analyzer.dart'; | |
| import '../diagnostic.dart' as diag; | |
| const _desc = 'Avoid unnecessary member names in variable patterns.'; | |
| class SimplifyVariablePattern extends AnalysisRule { | |
| new() : super(name: LintNames.simplify_variable_pattern, description: _desc); | |
| @override | |
| DiagnosticCode get diagnosticCode => diag.simplifyVariablePattern; | |
| @override | |
| void registerNodeProcessors( | |
| RuleVisitorRegistry registry, | |
| RuleContext context, | |
| ) { | |
| if (!context.isFeatureEnabled(Feature.patterns)) return; | |
| var visitor = _Visitor(this, context); | |
| registry.addPatternField(this, visitor); | |
| } | |
| } | |
| class _Visitor(final AnalysisRule rule, final RuleContext context) | |
| extends SimpleAstVisitor<void> { | |
| @override | |
| void visitPatternField(PatternField node) { | |
| var pattern = node.pattern.unParenthesized; | |
| if (pattern is! DeclaredVariablePattern) return; | |
| if (node.name?.name case Token(:var isSynthetic, :var lexeme) && var name | |
| when !isSynthetic && pattern.name.lexeme == lexeme) { | |
| // Make sure the name exists | |
| if (node.parent case RecordPattern(:var matchedValueType)) { | |
| _reportIfNeeded( | |
| name, | |
| matchedValueType, | |
| lexeme: lexeme, | |
| accessor: _accessor(node), | |
| ); | |
| } else if (node.parent case ObjectPattern( | |
| type: NamedType(:var element?, :var type), | |
| )) { | |
| while (element is TypeAliasElement || element is TypeParameterElement) { | |
| if (element case TypeAliasElement( | |
| aliasedType: DartType(element: var aliasedElement) && | |
| var aliasedType, | |
| )) { | |
| type = aliasedType; | |
| if (aliasedElement == null) { | |
| break; | |
| } | |
| element = aliasedElement; | |
| } else if (element is TypeParameterElement) { | |
| type = element.bound ?? context.typeProvider.objectQuestionType; | |
| var boundedElement = type.element; | |
| if (boundedElement == null) { | |
| break; | |
| } | |
| element = boundedElement; | |
| } | |
| } | |
| _reportIfNeeded(name, type, lexeme: lexeme, accessor: _accessor(node)); | |
| } | |
| } | |
| super.visitPatternField(node); | |
| } | |
| String _accessor(PatternField field) => switch (field.parent) { | |
| ObjectPattern() when field.element is MethodElement => 'method', | |
| ObjectPattern() when field.element is GetterElement => 'getter', | |
| _ => 'field', | |
| }; | |
| void _reportIfNeeded( | |
| Token name, | |
| DartType? type, { | |
| required String lexeme, | |
| required String accessor, | |
| }) { | |
| if (type is! DynamicType) { | |
| if (type is InterfaceType) { | |
| var library = context.libraryElement ?? type.element.library; | |
| var getter = type.lookUpGetter(lexeme, library); | |
| var method = type.lookUpMethod(lexeme, library); | |
| if (getter == null && | |
| method == null && | |
| !(type.element.isDartCoreFunction && | |
| lexeme == MethodElement.CALL_METHOD_NAME)) { | |
| return; | |
| } | |
| } else if (type case RecordType record) { | |
| if (!record.namedFields.map((field) => field.name).contains(lexeme)) { | |
| return; | |
| } | |
| } else if (type is FunctionType) { | |
| if (name.lexeme != MethodElement.CALL_METHOD_NAME) { | |
| return; | |
| } | |
| } else { | |
| return; | |
| } | |
| } | |
| rule.reportAtToken(name, arguments: [lexeme, accessor]); | |
| } | |
| } | |
| extension on InstanceElement { | |
| bool get isDartCoreFunction => library.isDartCore && name == 'Function'; | |
| } |