/** * Wire * Copyright (C) 2016 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.waz.lintrules.issues; import com.android.tools.lint.client.api.JavaParser; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Implementation; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.JavaContext; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import lombok.ast.AstVisitor; import lombok.ast.Expression; import lombok.ast.MethodInvocation; import lombok.ast.StrictListAccessor; import lombok.ast.StringLiteral; import lombok.ast.VariableReference; import java.util.Arrays; import java.util.Iterator; import java.util.List; public class ObjectAnimatorPropertyDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE = Issue.create( "com.waz.ObjectAnimatorProperty", "Use or create a Property", "When using ObjectAnimators it is recommended and more performant to use the associated property " + "like {@link View#ALPHA} or {@link View#TRANSLATE_X}.", Category.PERFORMANCE, 6, Severity.WARNING, new Implementation(ObjectAnimatorPropertyDetector.class, Scope.JAVA_FILE_SCOPE)); @Override public List<String> getApplicableMethodNames() { return Arrays.asList("ofFloat", "ofInt"); } @Override public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) { if (!(node.astOperand() instanceof VariableReference)) { return; } final VariableReference ref = (VariableReference) node.astOperand(); if (!"ObjectAnimator".equals(ref.astIdentifier().astValue())) { return; } final StrictListAccessor<Expression, MethodInvocation> astArguments = node.astArguments(); if (astArguments.size() <= 2) { return; } final Iterator<Expression> iterator = astArguments.iterator(); iterator.next(); // ignored to get the second final Expression property = iterator.next(); if (property instanceof StringLiteral) { context.report(ISSUE, context.getLocation(node), String.format("String '%s' should be replaced", ((StringLiteral) property).astValue())); return; } if (context.resolve(property) == null) { return; } if (property instanceof VariableReference) { if (!isSubclassOf(context, (VariableReference) property, "android.util.Property") && !isSubclassOf(context, (VariableReference) property, "com.nineoldandroids.util.Property")) { context.report(ISSUE, context.getLocation(node), String.format("'%s' should be replaced with a property", ((VariableReference) property).astIdentifier().astValue())); } } } private boolean isSubclassOf(JavaContext context, VariableReference variableReference, String clazz) { JavaParser.ResolvedField resolvedVar = (JavaParser.ResolvedField) context.resolve(variableReference); if (resolvedVar == null) { return false; } JavaParser.TypeDescriptor type = resolvedVar.getType(); if (type == null) { return false; } if (type.getTypeClass().isSubclassOf(clazz, false)) { return true; } return resolvedVar.getName().startsWith(clazz); } }