| // 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 { |
| SimplifyVariablePattern() |
| : 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 extends SimpleAstVisitor<void> { |
| final AnalysisRule rule; |
| |
| final RuleContext context; |
| |
| _Visitor(this.rule, this.context); |
| |
| @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?.element case InstanceElement element) { |
| var methods = element.methods.map((e) => e.name).toList(); |
| if (element.isDartCoreFunction) { |
| methods.add(MethodElement.CALL_METHOD_NAME); |
| } |
| if (!element.getters.map((e) => e.name).contains(lexeme) && |
| !methods.contains(lexeme)) { |
| 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'; |
| } |