| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tools.lint.checks; |
| |
| import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT; |
| import static com.android.SdkConstants.CLASS_ACTIVITY; |
| import static com.android.tools.lint.detector.api.TextFormat.RAW; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.tools.lint.client.api.JavaEvaluator; |
| import com.android.tools.lint.detector.api.Category; |
| import com.android.tools.lint.detector.api.Context; |
| 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.LintUtils; |
| import com.android.tools.lint.detector.api.Scope; |
| import com.android.tools.lint.detector.api.Severity; |
| import com.android.tools.lint.detector.api.TextFormat; |
| import com.intellij.psi.JavaElementVisitor; |
| import com.intellij.psi.PsiClass; |
| import com.intellij.psi.PsiMethod; |
| import com.intellij.psi.PsiMethodCallExpression; |
| import com.intellij.psi.util.PsiTreeUtil; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| |
| public class AppCompatCallDetector extends Detector implements Detector.JavaPsiScanner { |
| public static final Issue ISSUE = Issue.create( |
| "AppCompatMethod", |
| "Using Wrong AppCompat Method", |
| "When using the appcompat library, there are some methods you should be calling " + |
| "instead of the normal ones; for example, `getSupportActionBar()` instead of " + |
| "`getActionBar()`. This lint check looks for calls to the wrong method.", |
| Category.CORRECTNESS, 6, Severity.WARNING, |
| new Implementation( |
| AppCompatCallDetector.class, |
| Scope.JAVA_FILE_SCOPE)). |
| addMoreInfo("http://developer.android.com/tools/support-library/index.html"); |
| |
| private static final String GET_ACTION_BAR = "getActionBar"; |
| private static final String START_ACTION_MODE = "startActionMode"; |
| private static final String SET_PROGRESS_BAR_VIS = "setProgressBarVisibility"; |
| private static final String SET_PROGRESS_BAR_IN_VIS = "setProgressBarIndeterminateVisibility"; |
| private static final String SET_PROGRESS_BAR_INDETERMINATE = "setProgressBarIndeterminate"; |
| private static final String REQUEST_WINDOW_FEATURE = "requestWindowFeature"; |
| /** If you change number of parameters or order, update {@link #getMessagePart(String, int,TextFormat)} */ |
| private static final String ERROR_MESSAGE_FORMAT = "Should use `%1$s` instead of `%2$s` name"; |
| |
| private boolean mDependsOnAppCompat; |
| |
| public AppCompatCallDetector() { |
| } |
| |
| @Override |
| public void beforeCheckProject(@NonNull Context context) { |
| Boolean dependsOnAppCompat = context.getProject().dependsOn(APPCOMPAT_LIB_ARTIFACT); |
| mDependsOnAppCompat = dependsOnAppCompat != null && dependsOnAppCompat; |
| } |
| |
| @Nullable |
| @Override |
| public List<String> getApplicableMethodNames() { |
| return Arrays.asList( |
| GET_ACTION_BAR, |
| START_ACTION_MODE, |
| SET_PROGRESS_BAR_VIS, |
| SET_PROGRESS_BAR_IN_VIS, |
| SET_PROGRESS_BAR_INDETERMINATE, |
| REQUEST_WINDOW_FEATURE); |
| } |
| |
| @Override |
| public void visitMethod(@NonNull JavaContext context, @Nullable JavaElementVisitor visitor, |
| @NonNull PsiMethodCallExpression node, @NonNull PsiMethod method) { |
| if (mDependsOnAppCompat && isAppBarActivityCall(context, node, method)) { |
| String name = method.getName(); |
| String replace = null; |
| if (GET_ACTION_BAR.equals(name)) { |
| replace = "getSupportActionBar"; |
| } else if (START_ACTION_MODE.equals(name)) { |
| replace = "startSupportActionMode"; |
| } else if (SET_PROGRESS_BAR_VIS.equals(name)) { |
| replace = "setSupportProgressBarVisibility"; |
| } else if (SET_PROGRESS_BAR_IN_VIS.equals(name)) { |
| replace = "setSupportProgressBarIndeterminateVisibility"; |
| } else if (SET_PROGRESS_BAR_INDETERMINATE.equals(name)) { |
| replace = "setSupportProgressBarIndeterminate"; |
| } else if (REQUEST_WINDOW_FEATURE.equals(name)) { |
| replace = "supportRequestWindowFeature"; |
| } |
| |
| if (replace != null) { |
| String message = String.format(ERROR_MESSAGE_FORMAT, replace, name); |
| context.report(ISSUE, node, context.getLocation(node), message); |
| } |
| } |
| } |
| |
| private static boolean isAppBarActivityCall(@NonNull JavaContext context, |
| @NonNull PsiMethodCallExpression node, @NonNull PsiMethod method) { |
| JavaEvaluator evaluator = context.getEvaluator(); |
| if (evaluator.isMemberInSubClassOf(method, CLASS_ACTIVITY, false)) { |
| // Make sure that the calling context is a subclass of ActionBarActivity; |
| // we don't want to flag these calls if they are in non-appcompat activities |
| // such as PreferenceActivity (see b.android.com/58512) |
| PsiClass cls = PsiTreeUtil.getParentOfType(node, PsiClass.class, true); |
| return cls != null && evaluator.extendsClass(cls, |
| "android.support.v7.app.ActionBarActivity", false); |
| } |
| return false; |
| } |
| |
| /** |
| * Given an error message created by this lint check, return the corresponding old method name |
| * that it suggests should be deleted. (Intended to support quickfix implementations |
| * for this lint check.) |
| * |
| * @param errorMessage the error message originally produced by this detector |
| * @param format the format of the error message |
| * @return the corresponding old method name, or null if not recognized |
| */ |
| @Nullable |
| public static String getOldCall(@NonNull String errorMessage, @NonNull TextFormat format) { |
| return getMessagePart(errorMessage, 2, format); |
| } |
| |
| /** |
| * Given an error message created by this lint check, return the corresponding new method name |
| * that it suggests replace the old method name. (Intended to support quickfix implementations |
| * for this lint check.) |
| * |
| * @param errorMessage the error message originally produced by this detector |
| * @param format the format of the error message |
| * @return the corresponding new method name, or null if not recognized |
| */ |
| @Nullable |
| public static String getNewCall(@NonNull String errorMessage, @NonNull TextFormat format) { |
| return getMessagePart(errorMessage, 1, format); |
| } |
| |
| @Nullable |
| private static String getMessagePart(@NonNull String errorMessage, int group, |
| @NonNull TextFormat format) { |
| List<String> parameters = LintUtils.getFormattedParameters( |
| RAW.convertTo(ERROR_MESSAGE_FORMAT, format), |
| errorMessage); |
| if (parameters.size() == 2 && group <= 2) { |
| return parameters.get(group - 1); |
| } |
| |
| return null; |
| } |
| } |