blob: e772c7dedfbf5cfb835a3f14aa9a7cd9e9c6d1b5 [file] [log] [blame]
// Copyright (c) 2016, 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.util.unrelated_types_visitor;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:linter/src/linter.dart';
import 'package:linter/src/util/dart_type_utilities.dart';
typedef bool _InterfaceTypePredicate(InterfaceType type);
/// Base class for visitors used in rules where we want to lint about invoking
/// methods on generic classes where the parameter is unrelated to the parameter
/// type of the class. Extending this visitor is as simple as knowing the method,
/// class and library that uniquely define the target, i.e. implement only
/// [definition] and [methodName].
abstract class UnrelatedTypesVisitor extends SimpleAstVisitor {
final LintRule rule;
UnrelatedTypesVisitor(this.rule);
InterfaceTypeDefinition get definition;
String get methodName;
@override
void visitMethodInvocation(MethodInvocation node) {
if (!_isParameterizedMethodInvocation(methodName, node)) {
return;
}
DartType type = node.target != null
? node.target.bestType
: (node.getAncestor((a) => a is ClassDeclaration) as ClassDeclaration)
?.element?.type;
Expression argument = node.argumentList.arguments.first;
if (type is InterfaceType &&
DartTypeUtilities.unrelatedTypes(
argument.bestType, _findIterableTypeArgument(definition, type))) {
rule.reportLint(node);
}
}
}
_InterfaceTypePredicate _buildImplementsDefinitionPredicate(
InterfaceTypeDefinition definition) =>
(InterfaceType interface) =>
interface.name == definition.name &&
interface.element.library.name == definition.library;
List<InterfaceType> _findImplementedInterfaces(InterfaceType type,
{List<InterfaceType> acc: const []}) =>
acc.contains(type)
? acc
: type.interfaces.fold(
<InterfaceType>[type],
(List<InterfaceType> acc, InterfaceType e) => new List.from(acc)
..addAll(_findImplementedInterfaces(e, acc: acc)));
DartType _findIterableTypeArgument(
InterfaceTypeDefinition definition, InterfaceType type,
{List<InterfaceType> accumulator: const []}) {
if (type == null || type.isObject || type.isDynamic || accumulator.contains(type)) {
return null;
}
_InterfaceTypePredicate predicate =
_buildImplementsDefinitionPredicate(definition);
if (predicate(type)) {
return type.typeArguments.first;
}
List<InterfaceType> implementedInterfaces = _findImplementedInterfaces(type);
InterfaceType interface =
implementedInterfaces.firstWhere(predicate, orElse: () => null);
if (interface != null && interface.typeArguments.isNotEmpty) {
return interface.typeArguments.first;
}
return _findIterableTypeArgument(definition, type.superclass,
accumulator: [type]..addAll(accumulator)..addAll(implementedInterfaces));
}
bool _isParameterizedMethodInvocation(
String methodName, MethodInvocation node) =>
node.methodName.name == methodName &&
node.argumentList.arguments.length == 1;