Other code review changes

This commit is contained in:
Yan Zhulanow
2016-03-31 22:19:13 +03:00
parent 991e783ccf
commit 6b30b8ce1f
194 changed files with 2538 additions and 3079 deletions
-5
View File
@@ -5,7 +5,6 @@
<globalInspection hasStaticDescription="true" shortName="AndroidKLintAddJavascriptInterface" displayName="addJavascriptInterface Called" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAddJavascriptInterfaceInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintAlwaysShowAction" displayName="Usage of showAsAction=always" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAlwaysShowActionInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintAppCompatMethod" displayName="Using Wrong AppCompat Method" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAppCompatMethodInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintAppCompatResource" displayName="Menu namespace" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAppCompatResourceInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintAssert" displayName="Assertions" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAssertInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintCommitPrefEdits" displayName="Missing commit() on SharedPreference editor" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitPrefEditsInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintCommitTransaction" displayName="Missing commit() calls" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitTransactionInspection"/>
@@ -17,7 +16,6 @@
<globalInspection hasStaticDescription="true" shortName="AndroidKLintExportedPreferenceActivity" displayName="PreferenceActivity should not be exported" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintExportedPreferenceActivityInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintExportedReceiver" displayName="Receiver does not require permission" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintExportedReceiverInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintExportedService" displayName="Exported service does not require permission" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintExportedServiceInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintFullBackupContent" displayName="Valid Full Backup Content File" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintFullBackupContentInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintGetInstance" displayName="Cipher.getInstance with ECB" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGetInstanceInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintGifUsage" displayName="Using .gif format for bitmaps is discouraged" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGifUsageInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintGrantAllUris" displayName="Content provider shares everything" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGrantAllUrisInspection"/>
@@ -43,11 +41,9 @@
<globalInspection hasStaticDescription="true" shortName="AndroidKLintLogConditional" displayName="Unconditional Logging Calls" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="false" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogConditionalInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintLogTagMismatch" displayName="Mismatched Log Tags" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogTagMismatchInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintLongLogTag" displayName="Too Long Log Tags" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLongLogTagInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintMenuTitle" displayName="Missing menu title" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMenuTitleInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintMergeRootFrame" displayName="FrameLayout can be replaced with &lt;merge> tag" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMergeRootFrameInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintMissingSuperCall" displayName="Missing Super Call" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMissingSuperCallInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintNewApi" displayName="Calling new methods on older versions" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintNfcTechWhitespace" displayName="Whitespace in NFC tech lists" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNfcTechWhitespaceInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintOverdraw" displayName="Overdraw: Painting regions more than once" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverdrawInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintOverride" displayName="Method conflicts with new inherited method" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintOverrideAbstract" displayName="Not overriding abstract methods on older platforms" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideAbstractInspection"/>
@@ -56,7 +52,6 @@
<globalInspection hasStaticDescription="true" shortName="AndroidKLintPluralsCandidate" displayName="Potential Plurals" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintPluralsCandidateInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintPrivateResource" displayName="Using private resources" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintPrivateResourceInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintRecycle" displayName="Missing recycle() calls" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRecycleInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintRegistered" displayName="Class is not registered in the manifest" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRegisteredInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintRequiredSize" displayName="Missing layout_width or layout_height attributes" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRequiredSizeInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintRtlCompat" displayName="Right-to-left text compatibility issues" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRtlCompatInspection"/>
<globalInspection hasStaticDescription="true" shortName="AndroidKLintRtlEnabled" displayName="Using RTL attributes without enabling RTL support" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRtlEnabledInspection"/>
@@ -1044,7 +1044,7 @@ public class LintDriver {
}
for (File file : files) {
Context context = new Context(this, project, main, file);
fireEvent(EventType.SCANNING_FILE, context);
fireEvent(LintListener.EventType.SCANNING_FILE, context);
for (Detector detector : detectors) {
if (detector.appliesTo(context, file)) {
detector.beforeCheckFile(context);
@@ -1437,7 +1437,7 @@ public class LintDriver {
if (!sources.isEmpty()) {
List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(sources.size());
for (File file : sources) {
JavaContext context = new JavaContext(this, project, main, file, javaParser);
JavaContext context = new JavaContext(this, project, main, file);
contexts.add(context);
}
@@ -2377,7 +2377,7 @@ public class LintDriver {
return true;
}
} else if (valueNode instanceof UCallExpression
&& ((UCallExpression)valueNode).getKind() == JavaUastCallKinds.ARRAY_INITIALIZER) {
&& ((UCallExpression)valueNode).getKind() == UastCallKind.ARRAY_INITIALIZER) {
UCallExpression array = (UCallExpression) valueNode;
List<UExpression> expressions = array.getValueArguments();
for (UExpression arrayElement : expressions) {
@@ -510,7 +510,7 @@ public abstract class Detector {
return null;
}
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
}
@@ -226,7 +226,7 @@ public enum Scope {
/** Scope-set used for detectors which scan all resources */
public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
/** Scope-set used for detectors which are affected by a single Java source file */
public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(SOURCE_FILE);
public static final EnumSet<Scope> SOURCE_FILE_SCOPE = EnumSet.of(SOURCE_FILE);
/** Scope-set used for detectors which are affected by a single Java class file */
public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE);
/** Scope-set used for detectors which are affected by a single Gradle build file */
@@ -34,7 +34,6 @@ import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.Speed;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -54,21 +53,21 @@ public class AddJavascriptInterfaceDetector extends Detector implements UastScan
Severity.WARNING,
new Implementation(
AddJavascriptInterfaceDetector.class,
Scope.JAVA_FILE_SCOPE)).
Scope.SOURCE_FILE_SCOPE)).
addMoreInfo(
"https://labs.mwrinfosecurity.com/blog/2013/09/24/webview-addjavascriptinterface-remote-code-execution/");
private static final String WEB_VIEW = "android.webkit.WebView"; //$NON-NLS-1$
private static final String ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface"; //$NON-NLS-1$
// ---- Implements UastScanner ----
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
// ---- Implements UastScanner ----
@Nullable
@Override
public List<String> getApplicableFunctionNames() {
@@ -76,11 +75,13 @@ public class AddJavascriptInterfaceDetector extends Detector implements UastScan
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
// Ignore the issue if we never build for any API less than 17.
if (context.getLintContext().getMainProject().getMinSdk() >= 17) {
return;
}
// Ignore if the method doesn't fit our description.
UFunction resolvedFunction = node.resolveOrEmpty(context);
UClass containingClass = UastUtils.getContainingClassOrEmpty(resolvedFunction);
if (!containingClass.isSubclassOf(WEB_VIEW)) {
@@ -95,6 +96,6 @@ public class AddJavascriptInterfaceDetector extends Detector implements UastScan
String message = "`WebView.addJavascriptInterface` should not be called with minSdkVersion < 17 for security reasons: " +
"JavaScript can use reflection to manipulate application";
context.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
context.report(ISSUE, node, context.getLocation(node), message);
}
}
@@ -41,7 +41,7 @@ public class AlarmDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
AlarmDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Alarm set too soon/frequently */
public static final Issue ISSUE = Issue.create(
@@ -84,7 +84,7 @@ public class AlarmDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
UFunction setRepeatingFunction = node.resolve(context);
UClass containingClass = UastUtils.getContainingClassOrEmpty(setRepeatingFunction);
@@ -38,6 +38,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UQualifiedExpression;
import org.jetbrains.uast.USimpleReferenceExpression;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -220,22 +221,22 @@ public class AlwaysShowActionDetector extends ResourceXmlDetector implements Uas
boolean isIfRoom = description.equals("SHOW_AS_ACTION_IF_ROOM"); //$NON-NLS-1$
boolean isAlways = description.equals("SHOW_AS_ACTION_ALWAYS"); //$NON-NLS-1$
if ((isIfRoom || isAlways)
&& node.getReceiver().renderString().equals("MenuItem")) { //$NON-NLS-1$
&& UastUtils.endsWithQualified(node.getReceiver(), "MenuItem")) { //$NON-NLS-1$
if (isAlways) {
JavaContext lintContext = mContext.getLintContext();
if (lintContext.getDriver().isSuppressed(lintContext, ISSUE, node)) {
return false;
return super.visitQualifiedExpression(node);
}
if (mAlwaysFields == null) {
mAlwaysFields = new ArrayList<Location>();
}
mAlwaysFields.add(UastAndroidUtils.getLocation(node));
mAlwaysFields.add(mContext.getLocation(node));
} else {
mHasIfRoomRefs = true;
}
}
return false;
return super.visitQualifiedExpression(node);
}
}
}
@@ -35,7 +35,6 @@ import java.io.File;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.java.JavaUastCallKinds;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -62,7 +61,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
Severity.ERROR,
new Implementation(
AnnotationDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Constructs a new {@link AnnotationDetector} check */
public AnnotationDetector() {
@@ -108,7 +107,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
return super.visitAnnotation(node);
}
} else if (valueNode instanceof UCallExpression &&
((UCallExpression) valueNode).getKind() == JavaUastCallKinds.ARRAY_INITIALIZER) {
((UCallExpression) valueNode).getKind() == UastCallKind.ARRAY_INITIALIZER) {
UCallExpression array = (UCallExpression) valueNode;
if (array.getValueArgumentCount() == 0) {
continue;
@@ -126,7 +125,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
}
}
return false;
return super.visitAnnotation(node);
}
private boolean checkId(UAnnotation node, String id) {
@@ -143,7 +142,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
if (parent instanceof UFunction || parent instanceof UBlockExpression) {
break;
} else if (issue == ApiDetector.UNSUPPORTED && parent instanceof UDeclarationsExpression) {
UDeclarationsExpression declarations = (UDeclarationsExpression)parent;
UDeclarationsExpression declarations = (UDeclarationsExpression) parent;
for (UVariable var : declarations.getVariables()) {
if (var.getKind() != UastVariableKind.MEMBER && var.getInitializer() instanceof UQualifiedExpression) {
return true;
@@ -158,7 +157,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
// This issue doesn't have AST access: annotations are not
// available for local variables or parameters
mContext.report(ISSUE, node, UastAndroidUtils.getLocation(node), String.format(
mContext.report(ISSUE, node, mContext.getLocation(node), String.format(
"The `@SuppressLint` annotation cannot be used on a local " +
"variable with the lint check '%1$s': move out to the " +
"surrounding method", id));
@@ -199,7 +199,8 @@ open class ApiDetector : Detector(), UastScanner {
companion object {
private val AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null
private val SDK_INT_FQNAME = "android.os.Build.VERSION.SDK_INT"
private val SDK_INT_CONTAINING = "android.os.Build.VERSION"
private val SDK_INT = "SDK_INT"
tailrec fun getLocalMinSdk(scope: UElement?): Int {
if (scope == null) return -1
@@ -223,7 +224,7 @@ open class ApiDetector : Detector(), UastScanner {
val value = (valueNode as ULiteralExpression).value as String
return SdkVersionInfo.getApiByBuildCode(value, true)
} else if (valueNode is UQualifiedExpression) {
val codename = valueNode.selector.renderString()
val codename = valueNode.getSelectorAsIdentifier();
return SdkVersionInfo.getApiByBuildCode(codename, true)
} else if (valueNode is USimpleReferenceExpression) {
val codename = valueNode.identifier;
@@ -239,8 +240,8 @@ open class ApiDetector : Detector(), UastScanner {
fun isCheckedExplicitly(context: UastAndroidContext, requiredVersion: Int, node: UElement): Boolean {
tailrec fun UExpression.isSdkIntReference(): Boolean = when (this) {
is UParenthesizedExpression -> expression.isSdkIntReference()
is USimpleReferenceExpression -> resolve(context)?.fqName == SDK_INT_FQNAME
is UQualifiedExpression -> resolve(context)?.fqName == SDK_INT_FQNAME
is USimpleReferenceExpression -> resolve(context)?.matchesNameWithContaining(SDK_INT_CONTAINING, SDK_INT) ?: false
is UQualifiedExpression -> resolve(context)?.matchesNameWithContaining(SDK_INT_CONTAINING, SDK_INT) ?: false
else -> false
}
@@ -305,8 +306,8 @@ open class ApiDetector : Detector(), UastScanner {
else
check(node.parent, node, context)
}
is UExpressionSwitchClauseExpression -> {
if (checkCondition(node.caseValue) == true)
is USwitchClauseExpression -> {
if (node.caseValues?.any { checkCondition(it) == true } ?: false)
true
else
check(node.parent, node, context)
@@ -400,7 +401,7 @@ open class ApiDetector : Detector(), UastScanner {
Severity.WARNING,
Implementation(
ApiDetector::class.java,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
/** Accessing an unsupported API */
@JvmField
@@ -429,6 +430,6 @@ open class ApiDetector : Detector(), UastScanner {
Severity.ERROR,
Implementation(
ApiDetector::class.java,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
}
}
@@ -40,7 +40,6 @@ import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UFunction;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
public class AppCompatCallDetector extends Detector implements UastScanner {
@@ -53,7 +52,7 @@ public class AppCompatCallDetector extends Detector implements UastScanner {
Category.CORRECTNESS, 6, Severity.WARNING,
new Implementation(
AppCompatCallDetector.class,
Scope.JAVA_FILE_SCOPE)).
Scope.SOURCE_FILE_SCOPE)).
addMoreInfo("http://developer.android.com/tools/support-library/index.html");
private static final String GET_ACTION_BAR = "getActionBar";
@@ -94,26 +93,26 @@ public class AppCompatCallDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
if (mDependsOnAppCompat && isAppBarActivityCall(context, node)) {
String replace = null;
if (node.functionNameMatches(GET_ACTION_BAR)) {
if (node.matchesFunctionName(GET_ACTION_BAR)) {
replace = "getSupportActionBar";
} else if (node.functionNameMatches(START_ACTION_MODE)) {
} else if (node.matchesFunctionName(START_ACTION_MODE)) {
replace = "startSupportActionMode";
} else if (node.functionNameMatches(SET_PROGRESS_BAR_VIS)) {
} else if (node.matchesFunctionName(SET_PROGRESS_BAR_VIS)) {
replace = "setSupportProgressBarVisibility";
} else if (node.functionNameMatches(SET_PROGRESS_BAR_IN_VIS)) {
} else if (node.matchesFunctionName(SET_PROGRESS_BAR_IN_VIS)) {
replace = "setSupportProgressBarIndeterminateVisibility";
} else if (node.functionNameMatches(SET_PROGRESS_BAR_INDETERMINATE)) {
} else if (node.matchesFunctionName(SET_PROGRESS_BAR_INDETERMINATE)) {
replace = "setSupportProgressBarIndeterminate";
} else if (node.functionNameMatches(REQUEST_WINDOW_FEATURE)) {
} else if (node.matchesFunctionName(REQUEST_WINDOW_FEATURE)) {
replace = "supportRequestWindowFeature";
}
if (replace != null) {
String message = String.format(ERROR_MESSAGE_FORMAT, replace, node.getFunctionName());
context.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
context.report(ISSUE, node, context.getLocation(node), message);
}
}
}
@@ -1,109 +0,0 @@
/*
* 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.klint.checks;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_SHOW_AS_ACTION;
import com.android.annotations.NonNull;
import com.android.resources.ResourceFolderType;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.Project;
import com.android.tools.klint.detector.api.ResourceXmlDetector;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.Speed;
import com.android.tools.klint.detector.api.XmlContext;
import org.jetbrains.uast.check.UastScanner;
import org.w3c.dom.Attr;
import java.util.Collection;
import java.util.Collections;
/**
* Check that the right namespace is used for app compat menu items
*
* Using app:showAsAction instead of android:showAsAction leads to problems, but
* isn't caught by the API Detector since it's not in the Android namespace.
*/
public class AppCompatResourceDetector extends ResourceXmlDetector implements UastScanner {
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
"AppCompatResource", //$NON-NLS-1$
"Menu namespace",
"When using the appcompat library, menu resources should refer to the " +
"`showAsAction` in the `app:` namespace, not the `android:` namespace.\n" +
"\n" +
"Similarly, when *not* using the appcompat library, you should be using " +
"the `android:showAsAction` attribute.",
Category.USABILITY,
5,
Severity.ERROR,
new Implementation(
AppCompatResourceDetector.class,
Scope.RESOURCE_FILE_SCOPE));
/** Constructs a new {@link com.android.tools.lint.checks.AppCompatResourceDetector} */
public AppCompatResourceDetector() {
}
@Override
public boolean appliesTo(@NonNull ResourceFolderType folderType) {
return folderType == ResourceFolderType.MENU;
}
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
@Override
public Collection<String> getApplicableAttributes() {
return Collections.singletonList(ATTR_SHOW_AS_ACTION);
}
@Override
public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
Project mainProject = context.getMainProject();
if (mainProject.isGradleProject()) {
Boolean appCompat = mainProject.dependsOn("com.android.support:appcompat-v7");
if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
if (context.getFolderVersion() >= 14) {
return;
}
if (appCompat == Boolean.TRUE) {
context.report(ISSUE, attribute,
context.getLocation(attribute),
"Should use `app:showAsAction` with the appcompat library with "
+ "`xmlns:app=\"http://schemas.android.com/apk/res-auto\"`");
}
} else {
if (appCompat == Boolean.FALSE) {
context.report(ISSUE, attribute,
context.getLocation(attribute),
"Should use `android:showAsAction` when not using the appcompat library");
}
}
}
}
}
@@ -30,9 +30,8 @@ import java.io.File;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.java.JavaSpecialExpressionKinds;
import org.jetbrains.uast.java.JavaUAssertExpression;
import org.jetbrains.uast.visitor.UastVisitor;
import static org.jetbrains.uast.UastLiteralUtils.isNullLiteral;
@@ -65,7 +64,7 @@ public class AssertDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
AssertDetector.class,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
.addMoreInfo(
"https://code.google.com/p/android/issues/detail?id=65183"); //$NON-NLS-1$
@@ -84,8 +83,12 @@ public class AssertDetector extends Detector implements UastScanner {
public UastVisitor createUastVisitor(final UastAndroidContext context) {
return new UastVisitor() {
@Override
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
if (node.getKind() != JavaSpecialExpressionKinds.ASSERT) {
public boolean visitCallExpression(@NotNull UCallExpression node) {
if (!(node instanceof JavaUAssertExpression)) {
return true;
}
if (node.getValueArgumentCount() < 1) {
return true;
}
@@ -93,7 +96,7 @@ public class AssertDetector extends Detector implements UastScanner {
return true;
}
UExpression assertion = node.firstOrNull();
UExpression assertion = node.getValueArguments().get(0);
// Allow "assert true"; it's basically a no-op
if (assertion instanceof ULiteralExpression) {
ULiteralExpression literal = (ULiteralExpression) assertion;
@@ -114,8 +117,8 @@ public class AssertDetector extends Detector implements UastScanner {
}
}
String message
= "Assertions are unreliable. Use `BuildConfig.DEBUG` conditional checks instead.";
context.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
= "Assertions are unreliable. Use `BuildConfig.DEBUG` conditional checks instead.";
context.report(ISSUE, node, context.getLocation(node), message);
return false;
}
};
@@ -43,7 +43,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(ApiDetector.OVERRIDE);
issues.add(ApiDetector.UNSUPPORTED);
issues.add(AppCompatCallDetector.ISSUE);
issues.add(AppCompatResourceDetector.ISSUE);
issues.add(AssertDetector.ISSUE);
issues.add(CallSuperDetector.ISSUE);
issues.add(CipherGetInstanceDetector.ISSUE);
@@ -54,7 +53,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(CustomViewDetector.ISSUE);
issues.add(CutPasteDetector.ISSUE);
issues.add(DateFormatDetector.DATE_FORMAT);
issues.add(FullBackupContentDetector.ISSUE);
issues.add(FragmentDetector.ISSUE);
issues.add(GetSignaturesDetector.ISSUE);
issues.add(HandlerDetector.ISSUE);
@@ -82,7 +80,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(LogDetector.LONG_TAG);
issues.add(LogDetector.WRONG_TAG);
issues.add(MergeRootFrameLayoutDetector.ISSUE);
issues.add(NfcTechListDetector.ISSUE);
issues.add(NonInternationalizedSmsDetector.ISSUE);
issues.add(OverdrawDetector.ISSUE);
issues.add(OverrideConcreteDetector.ISSUE);
@@ -117,7 +114,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(SupportAnnotationDetector.RESOURCE_TYPE);
issues.add(SupportAnnotationDetector.THREAD);
issues.add(SupportAnnotationDetector.TYPE_DEF);
issues.add(TitleDetector.ISSUE);
issues.add(ToastDetector.ISSUE);
issues.add(UnusedResourceDetector.ISSUE);
issues.add(UnusedResourceDetector.ISSUE_IDS);
@@ -37,7 +37,6 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -51,7 +50,7 @@ public class CallSuperDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
CallSuperDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Missing call to super */
public static final Issue ISSUE = Issue.create(
@@ -103,7 +102,7 @@ public class CallSuperDetector extends Detector implements UastScanner {
String methodName = declaration.getName();
String message = "Overriding method should call `super."
+ methodName + "`";
Location location = UastAndroidUtils.getLocation(declaration.getNameElement());
Location location = context.getLocation(declaration.getNameElement());
context.report(ISSUE, declaration, location, message);
}
}
@@ -148,12 +147,12 @@ public class CallSuperDetector extends Detector implements UastScanner {
UFunction superMethod = directSuper;
while (superMethod != null) {
for (UAnnotation annotation : superMethod.getAnnotations()) {
annotation = SupportAnnotationDetector.getRelevantAnnotation(context, annotation);
annotation = SupportAnnotationDetector.getRelevantAnnotation(annotation, context);
if (annotation != null) {
String signature = annotation.getFqName();
if (CALL_SUPER_ANNOTATION.equals(signature)) {
String fqName = annotation.getFqName();
if (CALL_SUPER_ANNOTATION.equals(fqName)) {
return directSuper;
} else if (signature != null && signature.endsWith(".OverrideMustInvoke")) {
} else if (fqName != null && fqName.endsWith(".OverrideMustInvoke")) {
// Handle findbugs annotation on the fly too
return directSuper;
}
@@ -169,7 +168,8 @@ public class CallSuperDetector extends Detector implements UastScanner {
/** Visits a method and determines whether the method calls its super method */
private static class SuperCallVisitor extends UastVisitor {
private final UastAndroidContext mContext;
private final String mMethodFqName;
private final String mMethodContainingClassFqName;
private final String mMethodName;
private boolean mCallsSuper;
public static boolean callsSuper(
@@ -181,9 +181,10 @@ public class CallSuperDetector extends Detector implements UastScanner {
return visitor.mCallsSuper;
}
private SuperCallVisitor(@NonNull UastAndroidContext context, @NonNull UFunction method) {
private SuperCallVisitor(@NonNull UastAndroidContext context, @NonNull UFunction function) {
mContext = context;
mMethodFqName = UastUtils.getFqName(method);
mMethodContainingClassFqName = UastUtils.getContainingClassOrEmpty(function).getFqName();
mMethodName = function.getName();
}
@Override
@@ -193,7 +194,7 @@ public class CallSuperDetector extends Detector implements UastScanner {
if (receiver instanceof USuperExpression && selector instanceof UCallExpression) {
UFunction resolvedFunction = ((UCallExpression) selector).resolve(mContext);
if (mMethodFqName.equals(UastUtils.getFqName(resolvedFunction))) {
if (resolvedFunction != null && resolvedFunction.matchesNameWithContaining(mMethodContainingClassFqName, mMethodName)) {
mCallsSuper = true;
return true;
}
@@ -32,7 +32,6 @@ import java.util.Set;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
/**
@@ -50,7 +49,7 @@ public class CipherGetInstanceDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
CipherGetInstanceDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
private static final String CIPHER = "javax.crypto.Cipher"; //$NON-NLS-1$
private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
@@ -72,7 +71,7 @@ public class CipherGetInstanceDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
UClass containingClass = UastUtils.getContainingClass(node);
if (containingClass == null || !containingClass.isSubclassOf(CIPHER)) {
return;
@@ -107,13 +106,13 @@ public class CipherGetInstanceDetector extends Detector implements UastScanner {
if (ALGORITHM_ONLY.contains(value)) {
String message = "`Cipher.getInstance` should not be called without setting the"
+ " encryption mode and padding";
context.report(ISSUE, call, UastAndroidUtils.getLocation(arg), message);
context.report(ISSUE, call, context.getLocation(arg), message);
} else if (value.contains(ECB)) {
String message = "ECB encryption mode should not be used";
if (includeValue) {
message += " (was \"" + value + "\")";
}
context.report(ISSUE, call, UastAndroidUtils.getLocation(arg), message);
context.report(ISSUE, call, context.getLocation(arg), message);
}
}
}
@@ -35,7 +35,6 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -47,7 +46,7 @@ public class CleanupDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
CleanupDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Problems with missing recycle calls */
public static final Issue RECYCLE_RESOURCE = Issue.create(
@@ -156,8 +155,8 @@ public class CleanupDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
if (node.functionNameMatches(BEGIN_TRANSACTION)) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
if (node.matchesFunctionName(BEGIN_TRANSACTION)) {
checkTransactionCommits(context, node);
} else {
checkResourceRecycled(context, node, node.getFunctionName());
@@ -245,7 +244,7 @@ public class CleanupDetector extends Detector implements UastScanner {
FinishVisitor visitor = new FinishVisitor(context, boundVariable) {
@Override
protected boolean isCleanupCall(@NonNull UCallExpression call) {
if (!call.functionNameMatches(recycleName)) {
if (!call.matchesFunctionName(recycleName)) {
return false;
}
UDeclaration resolved = call.resolve(mContext);
@@ -286,7 +285,7 @@ public class CleanupDetector extends Detector implements UastScanner {
}
UElement locationNode = node instanceof UCallExpression ?
((UCallExpression) node).getFunctionNameElement() : node;
Location location = UastAndroidUtils.getLocation(locationNode);
Location location = context.getLocation(locationNode);
context.report(RECYCLE_RESOURCE, node, location, message);
}
@@ -353,7 +352,7 @@ public class CleanupDetector extends Detector implements UastScanner {
}
String message = "This transaction should be completed with a `commit()` call";
context.report(COMMIT_FRAGMENT, node, UastAndroidUtils.getLocation(node.getFunctionReference()),
context.report(COMMIT_FRAGMENT, node, context.getLocation(node.getFunctionReference()),
message);
}
return false;
@@ -383,15 +382,15 @@ public class CleanupDetector extends Detector implements UastScanner {
private static boolean isTransactionCommitMethodCall(@NonNull UastAndroidContext context,
@NonNull UCallExpression call) {
return (call.functionNameMatches(COMMIT) || call.functionNameMatches(COMMIT_ALLOWING_LOSS)) &&
isMethodOnFragmentClass(context, call,
return (call.matchesFunctionName(COMMIT) || call.matchesFunctionName(COMMIT_ALLOWING_LOSS)) &&
isMethodOnFragmentClass(context, call,
FRAGMENT_TRANSACTION_CLS,
FRAGMENT_TRANSACTION_V4_CLS);
}
private static boolean isShowFragmentMethodCall(@NonNull UastAndroidContext context,
@NonNull UCallExpression call) {
return call.functionNameMatches(SHOW)
return call.matchesFunctionName(SHOW)
&& isMethodOnFragmentClass(context, call,
DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT);
}
@@ -435,8 +434,8 @@ public class CleanupDetector extends Detector implements UastScanner {
private static boolean isBeginTransaction(@NonNull UastAndroidContext context,
@NonNull UCallExpression node) {
assert node.functionNameMatches(BEGIN_TRANSACTION) : node.renderString();
if (node.functionNameMatches(BEGIN_TRANSACTION)) {
assert node.matchesFunctionName(BEGIN_TRANSACTION) : node.renderString();
if (node.matchesFunctionName(BEGIN_TRANSACTION)) {
UFunction method = node.resolve(context);
if (method != null) {
UClass containingClass = UastUtils.getContainingClassOrEmpty(method);
@@ -565,15 +564,13 @@ public class CleanupDetector extends Detector implements UastScanner {
}
@Override
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
if (node.getKind() == UastSpecialExpressionKind.RETURN) {
UExpression value = node.firstOrNull();
if (value instanceof USimpleReferenceExpression) {
UDeclaration resolved = ((USimpleReferenceExpression) value).resolve(mContext);
//noinspection SuspiciousMethodCalls
if (resolved != null && mVariables.contains(resolved)) {
mEscapes = true;
}
public boolean visitReturnExpression(@NotNull UReturnExpression node) {
UExpression value = node.getReturnExpression();
if (value instanceof USimpleReferenceExpression) {
UDeclaration resolved = ((USimpleReferenceExpression) value).resolve(mContext);
//noinspection SuspiciousMethodCalls
if (resolved != null && mVariables.contains(resolved)) {
mEscapes = true;
}
}
@@ -38,7 +38,7 @@ public class CommentDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
CommentDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Looks for hidden code */
public static final Issue EASTER_EGG = Issue.create(
@@ -36,7 +36,6 @@ import java.util.Collections;
import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -48,7 +47,7 @@ public class CustomViewDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
CustomViewDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Mismatched style and class names */
public static final Issue ISSUE = Issue.create(
@@ -92,7 +91,7 @@ public class CustomViewDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
if (node.getParent() instanceof UExpression) {
if (!context.getLintContext().isContextMethod(node)) {
return;
@@ -137,7 +136,7 @@ public class CustomViewDetector extends Detector implements UastScanner {
+ "should have the same name (various editor features rely on "
+ "this convention)",
className, styleableName);
context.report(ISSUE, node, UastAndroidUtils.getLocation(expression), message);
context.report(ISSUE, node, context.getLocation(expression), message);
}
} else if (resolvedClass.isSubclassOf(CLASS_VIEWGROUP + DOT_LAYOUT_PARAMS)) {
UClass outer = UastUtils.getContainingClass(resolvedClass);
@@ -153,7 +152,7 @@ public class CustomViewDetector extends Detector implements UastScanner {
+ "class (`%3$s`) plus \"`_Layout`\", e.g. `%4$s`. "
+ "(Various editor features rely on this convention.)",
styleableName, className, layoutClassName, expectedName);
context.report(ISSUE, node, UastAndroidUtils.getLocation(expression), message);
context.report(ISSUE, node, context.getLocation(expression), message);
}
}
}
@@ -38,7 +38,6 @@ import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -63,7 +62,7 @@ public class CutPasteDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
CutPasteDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
private UFunction mLastMethod;
private Map<String, UCallExpression> mIds;
@@ -88,7 +87,7 @@ public class CutPasteDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
String lhs = getLhs(node);
if (lhs == null) {
return;
@@ -129,8 +128,8 @@ public class CutPasteDetector extends Detector implements UastScanner {
if (!isReachableFrom(method, earlierCall, node)) {
return;
}
Location location = UastAndroidUtils.getLocation(node);
Location secondary = UastAndroidUtils.getLocation(earlierCall);
Location location = context.getLocation(node);
Location secondary = context.getLocation(earlierCall);
if (location != null && secondary != null) {
secondary.setMessage("First usage here");
location.setSecondary(secondary);
@@ -34,7 +34,6 @@ import org.jetbrains.uast.UFunction;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UType;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
/**
@@ -44,7 +43,7 @@ public class DateFormatDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
DateFormatDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Constructing SimpleDateFormat without an explicit locale */
public static final Issue DATE_FORMAT = Issue.create(
@@ -93,7 +92,7 @@ public class DateFormatDetector extends Detector implements UastScanner {
@Override
public void visitConstructor(UastAndroidContext context, UCallExpression functionCall, UFunction constructor) {
if (!specifiesLocale(constructor)) {
Location location = UastAndroidUtils.getLocation(functionCall);
Location location = context.getLocation(functionCall);
String message =
"To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, " +
"or `getTimeInstance()`, or use `new SimpleDateFormat(String template, " +
@@ -33,10 +33,8 @@ import java.util.Arrays;
import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.kinds.UastClassKind;
/**
* Checks that Fragment subclasses can be instantiated via
@@ -66,7 +64,7 @@ public class FragmentDetector extends Detector implements UastScanner {
Severity.FATAL,
new Implementation(
FragmentDetector.class,
Scope.JAVA_FILE_SCOPE)
Scope.SOURCE_FILE_SCOPE)
).addMoreInfo(
"http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$
@@ -89,6 +87,16 @@ public class FragmentDetector extends Detector implements UastScanner {
return Arrays.asList(CLASS_FRAGMENT, CLASS_V4_FRAGMENT);
}
private boolean isAllParametersWithDefaultValue(UFunction function) {
List<UVariable> parameters = function.getValueParameters();
for (UVariable parameter : parameters) {
if (parameter.getInitializer() == null) {
return false;
}
}
return true;
}
@Override
public void visitClass(UastAndroidContext context, UClass cls) {
if (cls.hasModifier(UastModifier.ABSTRACT) || cls.getKind() != UastClassKind.CLASS) {
@@ -98,14 +106,14 @@ public class FragmentDetector extends Detector implements UastScanner {
if (!cls.getVisibility().isPublic()) {
String message = String.format("This fragment class should be public (%1$s)",
cls.getFqName());
context.report(ISSUE, cls, UastAndroidUtils.getLocation(cls.getNameElement()), message);
context.report(ISSUE, cls, context.getLocation(cls.getNameElement()), message);
return;
}
if (UastUtils.getContainingClass(cls) != null && !cls.hasModifier(UastModifier.STATIC)) {
String message = String.format(
"This fragment inner class should be static (%1$s)", cls.getName());
context.report(ISSUE, cls, UastAndroidUtils.getLocation(cls.getNameElement()), message);
context.report(ISSUE, cls, context.getLocation(cls.getNameElement()), message);
return;
}
@@ -114,11 +122,11 @@ public class FragmentDetector extends Detector implements UastScanner {
for (UFunction constructor : cls.getConstructors()) {
hasConstructor = true;
if (constructor.getValueParameterCount() == 0) {
if (constructor.getValueParameterCount() == 0 || isAllParametersWithDefaultValue(constructor)) {
if (constructor.getVisibility().isPublic()) {
hasDefaultConstructor = true;
} else {
Location location = UastAndroidUtils.getLocation(constructor.getNameElement());
Location location = context.getLocation(constructor.getNameElement());
context.report(ISSUE, constructor, location,
"The default constructor must be public");
// Also mark that we have a constructor so we don't complain again
@@ -127,7 +135,7 @@ public class FragmentDetector extends Detector implements UastScanner {
hasDefaultConstructor = true;
}
} else {
Location location = UastAndroidUtils.getLocation(constructor.getNameElement());
Location location = context.getLocation(constructor.getNameElement());
// TODO: Use separate issue for this which isn't an error
String message = "Avoid non-default constructors in fragments: "
+ "use a default constructor plus "
@@ -141,7 +149,7 @@ public class FragmentDetector extends Detector implements UastScanner {
"This fragment should provide a default constructor (a public " +
"constructor with no arguments) (`%1$s`)",
cls.getName());
context.report(ISSUE, cls, UastAndroidUtils.getLocation(cls.getNameElement()), message);
context.report(ISSUE, cls, context.getLocation(cls.getNameElement()), message);
}
}
}
@@ -1,234 +0,0 @@
/*
* Copyright (C) 2015 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.klint.checks;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.resources.ResourceFolderType;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.ResourceXmlDetector;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.XmlContext;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import org.jetbrains.uast.check.UastScanner;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Collection;
import java.util.List;
/**
* Check which makes sure that a full-backup-content descriptor file is valid and logical
*/
public class FullBackupContentDetector extends ResourceXmlDetector implements UastScanner {
/**
* Validation of {@code <full-backup-content>} XML elements
*/
public static final Issue ISSUE = Issue.create(
"FullBackupContent", //$NON-NLS-1$
"Valid Full Backup Content File",
"Ensures that a `<full-backup-content>` file, which is pointed to by a " +
"`android:fullBackupContent attribute` in the manifest file, is valid",
Category.CORRECTNESS,
5,
Severity.FATAL,
new Implementation(
FullBackupContentDetector.class,
Scope.RESOURCE_FILE_SCOPE));
@SuppressWarnings("SpellCheckingInspection")
private static final String DOMAIN_SHARED_PREF = "sharedpref";
private static final String DOMAIN_ROOT = "root";
private static final String DOMAIN_FILE = "file";
private static final String DOMAIN_DATABASE = "database";
private static final String DOMAIN_EXTERNAL = "external";
private static final String TAG_EXCLUDE = "exclude";
private static final String TAG_INCLUDE = "include";
private static final String TAG_FULL_BACKUP_CONTENT = "full-backup-content";
private static final String ATTR_PATH = "path";
private static final String ATTR_DOMAIN = "domain";
/**
* Constructs a new {@link FullBackupContentDetector}
*/
public FullBackupContentDetector() {
}
@Override
public boolean appliesTo(@NonNull ResourceFolderType folderType) {
return folderType == ResourceFolderType.XML;
}
@Override
public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
Element root = document.getDocumentElement();
if (root == null) {
return;
}
if (!TAG_FULL_BACKUP_CONTENT.equals(root.getTagName())) {
return;
}
List<Element> includes = Lists.newArrayList();
List<Element> excludes = Lists.newArrayList();
NodeList children = root.getChildNodes();
for (int i = 0, n = children.getLength(); i < n; i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) child;
String tag = element.getTagName();
if (TAG_INCLUDE.equals(tag)) {
includes.add(element);
} else if (TAG_EXCLUDE.equals(tag)) {
excludes.add(element);
} else {
// See FullBackup#validateInnerTagContents
context.report(ISSUE, element, context.getNameLocation(element),
String.format("Unexpected element `<%1$s>`", tag));
}
}
}
Multimap<String, String> includePaths = ArrayListMultimap.create(includes.size(), 4);
for (Element include : includes) {
String domain = validateDomain(context, include);
String path = validatePath(context, include);
if (domain == null) {
continue;
}
includePaths.put(domain, path);
}
for (Element exclude : excludes) {
String excludePath = validatePath(context, exclude);
if (excludePath.isEmpty()) {
continue;
}
String domain = validateDomain(context, exclude);
if (domain == null) {
continue;
}
if (includePaths.isEmpty()) {
// There is no <include> anywhere: that means that everything
// is considered included and there's no potential prefix mismatch
continue;
}
boolean hasPrefix = false;
Collection<String> included = includePaths.get(domain);
if (included == null) {
continue;
}
for (String includePath : included) {
if (excludePath.startsWith(includePath)) {
if (excludePath.equals(includePath)) {
Attr pathNode = exclude.getAttributeNode(ATTR_PATH);
assert pathNode != null;
Location location = context.getValueLocation(pathNode);
// Find corresponding include path so we can link to it in the
// chained location list
for (Element include : includes) {
Attr includePathNode = include.getAttributeNode(ATTR_PATH);
String includeDomain = include.getAttribute(ATTR_DOMAIN);
if (includePathNode != null
&& excludePath.equals(includePathNode.getValue())
&& domain.equals(includeDomain)) {
Location earlier = context.getLocation(includePathNode);
earlier.setMessage("Unnecessary/conflicting <include>");
location.setSecondary(earlier);
}
}
context.report(ISSUE, exclude, location,
String.format("Include `%1$s` is also excluded", excludePath));
}
hasPrefix = true;
break;
}
}
if (!hasPrefix) {
Attr pathNode = exclude.getAttributeNode(ATTR_PATH);
assert pathNode != null;
context.report(ISSUE, exclude, context.getValueLocation(pathNode),
String.format("`%1$s` is not in an included path", excludePath));
}
}
}
@NonNull
private static String validatePath(@NonNull XmlContext context, @NonNull Element element) {
Attr pathNode = element.getAttributeNode(ATTR_PATH);
if (pathNode == null) {
return "";
}
String value = pathNode.getValue();
if (value.contains("//")) {
context.report(ISSUE, element, context.getValueLocation(pathNode),
"Paths are not allowed to contain `//`");
} else if (value.contains("..")) {
context.report(ISSUE, element, context.getValueLocation(pathNode),
"Paths are not allowed to contain `..`");
} else if (value.contains("/")) {
String domain = element.getAttribute(ATTR_DOMAIN);
if (DOMAIN_SHARED_PREF.equals(domain) || DOMAIN_DATABASE.equals(domain)) {
context.report(ISSUE, element, context.getValueLocation(pathNode),
String.format("Subdirectories are not allowed for domain `%1$s`",
domain));
}
}
return value;
}
@Nullable
private static String validateDomain(@NonNull XmlContext context, @NonNull Element element) {
Attr domainNode = element.getAttributeNode(ATTR_DOMAIN);
if (domainNode == null) {
context.report(ISSUE, element, context.getLocation(element),
String.format("Missing domain attribute, expected one of %1$s",
Joiner.on(", ").join(VALID_DOMAINS)));
return null;
}
String domain = domainNode.getValue();
for (String availableDomain : VALID_DOMAINS) {
if (availableDomain.equals(domain)) {
return domain;
}
}
context.report(ISSUE, element, context.getValueLocation(domainNode),
String.format("Unexpected domain `%1$s`, expected one of %2$s", domain,
Joiner.on(", ").join(VALID_DOMAINS)));
return domain;
}
/** Valid domains; see FullBackup#getTokenForXmlDomain for authoritative list */
private static final String[] VALID_DOMAINS = new String[] {
DOMAIN_ROOT, DOMAIN_FILE, DOMAIN_DATABASE, DOMAIN_SHARED_PREF, DOMAIN_EXTERNAL
};
}
@@ -29,7 +29,6 @@ import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
public class GetSignaturesDetector extends Detector implements UastScanner {
@@ -47,7 +46,7 @@ public class GetSignaturesDetector extends Detector implements UastScanner {
Severity.INFORMATIONAL,
new Implementation(
GetSignaturesDetector.class,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
.addMoreInfo("https://bluebox.com/technical/android-fake-id-vulnerability/");
private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager"; //$NON-NLS-1$
@@ -63,7 +62,7 @@ public class GetSignaturesDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
UFunction resolved = node.resolve(context);
if (resolved == null ||
@@ -86,7 +85,7 @@ public class GetSignaturesDetector extends Detector implements UastScanner {
private static void maybeReportIssue(
int flagValue, UastAndroidContext context, UCallExpression node) {
if ((flagValue & GET_SIGNATURES_FLAG) != 0) {
context.report(ISSUE, node, UastAndroidUtils.getLocation(node.getValueArguments().get(1)),
context.report(ISSUE, node, context.getLocation(node.getValueArguments().get(1)),
"Reading app signatures from getPackageInfo: The app signatures "
+ "could be exploited if not validated properly; "
+ "see issue explanation for details.");
@@ -31,9 +31,7 @@ import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.kinds.UastClassKind;
/**
* Checks that Handler implementations are top level classes or static.
@@ -60,7 +58,7 @@ public class HandlerDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
HandlerDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
private static final String LOOPER_CLS = "android.os.Looper";
private static final String HANDLER_CLS = "android.os.Handler";
@@ -99,7 +97,7 @@ public class HandlerDetector extends Detector implements UastScanner {
}
UElement locationNode = node.getNameElement();
Location location = UastAndroidUtils.getLocation(locationNode);
Location location = context.getLocation(locationNode);
context.report(ISSUE, node, location, String.format(
"This Handler class should be static or leaks might occur (%1$s)",
node.getName()));
@@ -1958,6 +1958,7 @@ public class IconDetector extends ResourceXmlDetector implements UastScanner {
// ---- Implements UastScanner ----
private static final String NOTIFICATION_CLASS = "Notification"; //$NON-NLS-1$
private static final String NOTIFICATION_COMPAT_CLASS = "NotificationCompat"; //$NON-NLS-1$
private static final String NOTIFICATION_CLASS_FQNAME =
"android.app.Notification"; //$NON-NLS-1$
@@ -2006,6 +2007,8 @@ public class IconDetector extends ResourceXmlDetector implements UastScanner {
return;
}
List<String> parts = UastUtils.asQualifiedIdentifiers(UastUtils.getQualifiedCallElement(node));
String typeName = reference.getIdentifier();
if (NOTIFICATION_CLASS.equals(typeName)) {
List<UExpression> args = node.getValueArguments();
@@ -2028,10 +2031,16 @@ public class IconDetector extends ResourceXmlDetector implements UastScanner {
}
}
} else if (BUILDER_CLASS.equals(typeName)) {
UClass resolvedClass = reference.resolveClass(mContext);
boolean isBuilder = resolvedClass != null
&& (resolvedClass.matchesFqName(NOTIFICATION_CLASS_FQNAME)
|| resolvedClass.matchesFqName(NOTIFICATION_COMPAT_CLASS_FQNAME));
boolean isBuilder = false;
if (parts != null && parts.size() == 1) {
isBuilder = true;
} else if (parts != null && parts.size() == 2) {
String clz = parts.get(0);
if (NOTIFICATION_CLASS.equals(clz) || NOTIFICATION_COMPAT_CLASS_FQNAME.equals(clz)) {
isBuilder = true;
}
}
if (isBuilder) {
UFunction method = UastUtils.getContainingFunction(node);
if (method != null) {
@@ -56,7 +56,7 @@ public class JavaPerformanceDetector extends Detector implements UastScanner {
private static final Implementation
IMPLEMENTATION = new Implementation(
JavaPerformanceDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Allocating objects during a paint method */
public static final Issue PAINT_ALLOC = Issue.create(
@@ -227,7 +227,7 @@ public class JavaPerformanceDetector extends Detector implements UastScanner {
}
}
if (mFlagAllocations && !(UastUtils.isThrow(node.getParent())) && mCheckAllocations) {
if (mFlagAllocations && !(node.getParent() instanceof UThrowExpression) && mCheckAllocations) {
// Make sure we're still inside the method declaration that marked
// mInDraw as true, in case we've left it and we're in a static
// block or something:
@@ -34,7 +34,6 @@ import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -56,7 +55,7 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
Severity.ERROR,
new Implementation(
JavaScriptInterfaceDetector.class,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
.addMoreInfo(
"http://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)"); //$NON-NLS-1$
@@ -83,7 +82,7 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
if (context.getLintContext().getMainProject().getTargetSdk() < 17) {
return;
}
@@ -146,7 +145,7 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
return;
}
Location location = UastAndroidUtils.getLocation(node.getFunctionNameElement());
Location location = context.getLocation(node.getFunctionNameElement());
String message = String.format(
"None of the methods in the added interface (%1$s) have been annotated " +
"with `@android.webkit.JavascriptInterface`; they will not " +
@@ -216,29 +215,19 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
}
@Override
public boolean visitCallExpression(@NotNull UCallExpression node) {
public void afterVisitCallExpression(@NotNull UCallExpression node) {
if (node.getKind() == UastCallKind.FUNCTION_CALL && node == mTargetCall) {
mFoundCall = true;
} else if (node.getKind() == UastCallKind.CONSTRUCTOR_CALL) {
UFunction resolved = node.resolve(mContext);
if (resolved != null) {
mTypes.put(node, UastUtils.getContainingClass(resolved));
} else {
// Implicit constructor?
UType type = node.getExpressionType();
if (type != null) {
UClass typeClass = type.resolve(mContext);
if (typeClass != null) {
mTypes.put(node, typeClass);
}
}
}
}
return false;
}
@Override
public boolean visitSimpleReferenceExpression(@NotNull USimpleReferenceExpression node) {
public void afterVisitSimpleReferenceExpression(@NotNull USimpleReferenceExpression node) {
if (mTypes.get(node) == null) {
UElement resolved = node.resolve(mContext);
if (resolved instanceof UVariable) {
@@ -248,11 +237,10 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
}
}
}
return false;
}
@Override
public boolean visitBinaryExpression(@NotNull UBinaryExpression node) {
public void afterVisitBinaryExpression(@NotNull UBinaryExpression node) {
if (node.getOperator() == UastBinaryOperator.ASSIGN) {
UExpression rhs = node.getRightOperand();
UClass resolvedClass = mTypes.get(rhs);
@@ -267,11 +255,10 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
}
}
}
return false;
}
@Override
public boolean visitIfExpression(@NotNull UIfExpression node) {
public void afterVisitIfExpression(@NotNull UIfExpression node) {
if (node.isTernary()) {
UClass resolvedClass = mTypes.get(node.getThenBranch());
if (resolvedClass == null) {
@@ -281,11 +268,10 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
mTypes.put(node, resolvedClass);
}
}
return false;
}
@Override
public boolean visitVariable(@NotNull UVariable node) {
public void afterVisitVariable(@NotNull UVariable node) {
UExpression initializer = node.getInitializer();
if (initializer != null) {
UClass resolvedClass = mTypes.get(initializer);
@@ -294,16 +280,14 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
mVariableTypes.put(node, resolvedClass);
}
}
return false;
}
@Override
public boolean visitBinaryExpressionWithType(@NotNull UBinaryExpressionWithType node) {
public void afterVisitBinaryExpressionWithType(@NotNull UBinaryExpressionWithType node) {
UClass resolvedClass = mTypes.get(node);
if (resolvedClass != null) {
mTypes.put(node, resolvedClass);
}
return false;
}
}
}
@@ -427,7 +427,7 @@ public class LayoutConsistencyDetector extends LayoutDetector implements UastSca
return locations.get(0);
}
// ---- Implements JavaScanner ----
// ---- Implements UastScanner ----
@Override
public boolean appliesToResourceRefs() {
@@ -46,7 +46,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.kxml2.io.KXmlParser;
@@ -75,7 +74,7 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
private static final Implementation IMPLEMENTATION = new Implementation(
LayoutInflationDetector.class,
Scope.SOURCE_AND_RESOURCE_FILES,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Passing in a null parent to a layout inflater */
public static final Issue ISSUE = Issue.create(
@@ -153,7 +152,7 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
assert ViewHolderDetector.INFLATE.equals(node.getFunctionName());
if (node instanceof USimpleReferenceExpression) {
return;
@@ -169,8 +168,8 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
return;
}
UQualifiedExpression select = (UQualifiedExpression) first;
UExpression selector = select.getSelector();
UExpression receiver = select.getReceiver();
UExpression selector = select.getSelector();
if (receiver instanceof UQualifiedExpression && selector instanceof USimpleReferenceExpression) {
UQualifiedExpression rLayout = (UQualifiedExpression) receiver;
if (rLayout.selectorMatches(ResourceType.LAYOUT.getName()) &&
@@ -186,11 +185,11 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
if (mPendingErrors == null) {
mPendingErrors = Lists.newArrayList();
}
Location location = UastAndroidUtils.getLocation(second);
Location location = context.getLocation(second);
mPendingErrors.add(Pair.of(layoutName, location));
}
} else if (hasLayoutParams(lintContext, layoutName)) {
context.report(ISSUE, node, UastAndroidUtils.getLocation(second), ERROR_MESSAGE);
context.report(ISSUE, node, context.getLocation(second), ERROR_MESSAGE);
}
}
}
@@ -35,7 +35,6 @@ import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
/**
@@ -43,7 +42,7 @@ import org.jetbrains.uast.check.UastScanner;
*/
public class LogDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
LogDetector.class, Scope.JAVA_FILE_SCOPE);
LogDetector.class, Scope.SOURCE_FILE_SCOPE);
/** Log call missing surrounding if */
@@ -113,7 +112,7 @@ public class LogDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
UFunction method = node.resolve(context);
if (method == null) {
return;
@@ -137,7 +136,7 @@ public class LogDetector extends Detector implements UastScanner {
"conditional: surround with `if (Log.isLoggable(...))` or " +
"`if (BuildConfig.DEBUG) { ... }`",
node.getFunctionName());
context.report(CONDITIONAL, node, UastAndroidUtils.getLocation(node), message);
context.report(CONDITIONAL, node, context.getLocation(node), message);
}
// Check tag length
@@ -156,7 +155,7 @@ public class LogDetector extends Detector implements UastScanner {
String message = String.format(
"The logging tag can be at most 23 characters, was %1$d (%2$s)",
tag.length(), tag);
context.report(LONG_TAG, node, UastAndroidUtils.getLocation(node), message);
context.report(LONG_TAG, node, context.getLocation(node), message);
}
}
}
@@ -276,8 +275,8 @@ public class LogDetector extends Detector implements UastScanner {
}
if (!isOk) {
Location location = UastAndroidUtils.getLocation(logTag);
Location alternate = UastAndroidUtils.getLocation(isLoggableTag);
Location location = context.getLocation(logTag);
Location alternate = context.getLocation(isLoggableTag);
if (location != null && alternate != null) {
alternate.setMessage("Conflicting tag");
location.setSecondary(alternate);
@@ -305,7 +304,10 @@ public class LogDetector extends Detector implements UastScanner {
}
String levelString = isLoggableLevel.toString();
if (isLoggableLevel instanceof UQualifiedExpression) {
levelString = ((UQualifiedExpression)isLoggableLevel).getSelector().renderString();
String identifier = ((UQualifiedExpression)isLoggableLevel).getSelectorAsIdentifier();
if (identifier != null) {
levelString = identifier;
}
}
if (levelString.isEmpty()) {
return;
@@ -331,8 +333,8 @@ public class LogDetector extends Detector implements UastScanner {
"Mismatched logging levels: when checking `isLoggable` level `%1$s`, the " +
"corresponding log call should be `Log.%2$s`, not `Log.%3$s`",
levelString, expectedCall, logCallName);
Location location = UastAndroidUtils.getLocation(logCall.getFunctionNameElement());
Location alternate = UastAndroidUtils.getLocation(isLoggableLevel);
Location location = context.getLocation(logCall.getFunctionNameElement());
Location alternate = context.getLocation(isLoggableLevel);
if (location != null && alternate != null) {
alternate.setMessage("Conflicting tag");
location.setSecondary(alternate);
@@ -45,6 +45,7 @@ import com.android.utils.Pair;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UQualifiedExpression;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.w3c.dom.Element;
@@ -196,7 +197,7 @@ public class MergeRootFrameLayoutDetector extends LayoutDetector implements Uast
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
if (node.getValueArgumentCount() != 1) {
return;
}
@@ -1,110 +0,0 @@
/*
* 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.klint.checks;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.resources.ResourceFolderType;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.ResourceXmlDetector;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.Speed;
import com.android.tools.klint.detector.api.XmlContext;
import org.jetbrains.uast.check.UastScanner;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Collection;
import java.util.Collections;
/**
* Check which makes sure NFC tech lists do not include spaces around {@code <tech>} values
* since that's not handled correctly by the inflater
*/
public class NfcTechListDetector extends ResourceXmlDetector implements UastScanner {
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
"NfcTechWhitespace", //$NON-NLS-1$
"Whitespace in NFC tech lists",
"In a <tech-list>, there can be whitespace around the <tech> elements," +
"but not inside them. This is because the code which reads in the tech " +
"list is currently very strict and will include the whitespace as part " +
"of the name.\n" +
"\n" +
"In other words, use <tech>name</tech>, not <tech> name </tech>.",
Category.CORRECTNESS,
5,
Severity.FATAL,
new Implementation(
NfcTechListDetector.class,
Scope.RESOURCE_FILE_SCOPE))
.addMoreInfo(
"https://code.google.com/p/android/issues/detail?id=65351"); //$NON-NLS-1$
/** Constructs a new {@link NfcTechListDetector} */
public NfcTechListDetector() {
}
@Override
public boolean appliesTo(@NonNull ResourceFolderType folderType) {
return folderType == ResourceFolderType.XML;
}
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
@Override
@Nullable
public Collection<String> getApplicableElements() {
return Collections.singletonList("tech");
}
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
Node parentNode = element.getParentNode();
if (parentNode == null || parentNode.getNodeType() != Node.ELEMENT_NODE ||
!"tech-list".equals(parentNode.getNodeName())) {
return;
}
NodeList children = element.getChildNodes();
if (children.getLength() != 1) {
return;
}
Node child = children.item(0);
if (child.getNodeType() != Node.TEXT_NODE) {
// TODO: Warn if you have comment nodes etc too? Will probably also break inflater.
return;
}
String text = child.getNodeValue();
if (!text.equals(text.trim())) {
String message = "There should not be any whitespace inside `<tech>` elements";
context.report(ISSUE, element, context.getLocation(child), message);
}
}
}
@@ -33,7 +33,6 @@ import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.USimpleReferenceExpression;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -53,7 +52,7 @@ public class NonInternationalizedSmsDetector extends Detector implements UastSca
Severity.WARNING,
new Implementation(
NonInternationalizedSmsDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Constructs a new {@link NonInternationalizedSmsDetector} check */
@@ -77,15 +76,11 @@ public class NonInternationalizedSmsDetector extends Detector implements UastSca
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
String functionName = node.getFunctionName();
assert "sendTextMessage".equals(functionName) || //$NON-NLS-1$
"sendMultipartTextMessage".equals(functionName); //$NON-NLS-1$
if (node instanceof USimpleReferenceExpression) {
// "sendTextMessage"/"sendMultipartTextMessage" in the code with no operand
return;
}
List<UExpression> args = node.getValueArguments();
if (args.size() == 5) {
@@ -95,7 +90,7 @@ public class NonInternationalizedSmsDetector extends Detector implements UastSca
String number = (String) ((ULiteralExpression) destinationAddress).getValue();
if (number != null && !number.startsWith("+")) { //$NON-NLS-1$
context.report(ISSUE, node, UastAndroidUtils.getLocation(destinationAddress),
context.report(ISSUE, node, context.getLocation(destinationAddress),
"To make sure the SMS can be sent by all users, please start the SMS number " +
"with a + and a country code or restrict the code invocation to people in the country " +
"you are targeting.");
@@ -486,16 +486,7 @@ public class OverdrawDetector extends LayoutDetector implements UastScanner {
String name = node.getName();
if (mActivities != null && mActivities.contains(mClassFqn) || name.endsWith(ACTIVITY)
|| node.isSubclassOf(CLASS_ACTIVITY)) {
String packageName = "";
if (node.getParent() instanceof UFile) {
UFile file = (UFile) node.getParent();
if (file.getPackageFqName() == null) {
return true;
}
packageName = file.getPackageFqName();
}
mClassFqn = (!packageName.isEmpty() ? (packageName + '.') : "") + name;
mClassFqn = node.getFqName();
return false;
}
@@ -55,7 +55,7 @@ public class OverrideConcreteDetector extends Detector implements UastScanner {
Severity.FATAL,
new Implementation(
OverrideConcreteDetector.class,
Scope.JAVA_FILE_SCOPE)
Scope.SOURCE_FILE_SCOPE)
);
// This check is currently hardcoded for the specific case of the
@@ -27,10 +27,8 @@ import com.android.tools.klint.detector.api.Speed;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.kinds.UastClassKind;
import org.jetbrains.uast.visitor.UastVisitor;
/**
@@ -53,7 +51,7 @@ public class ParcelDetector extends Detector implements UastScanner {
Severity.ERROR,
new Implementation(
ParcelDetector.class,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
.addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html");
/** Constructs a new {@link ParcelDetector} check */
@@ -100,7 +98,7 @@ public class ParcelDetector extends Detector implements UastScanner {
return true;
}
}
Location location = UastAndroidUtils.getLocation(node.getNameElement());
Location location = mContext.getLocation(node.getNameElement());
mContext.report(ISSUE, node, location,
"This class implements `Parcelable` but does not "
+ "provide a `CREATOR` field");
@@ -1,329 +0,0 @@
/*
* 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.klint.checks;
import static com.android.tools.klint.checks.PluralsDatabase.Quantity.few;
import static com.android.tools.klint.checks.PluralsDatabase.Quantity.many;
import static com.android.tools.klint.checks.PluralsDatabase.Quantity.one;
import static com.android.tools.klint.checks.PluralsDatabase.Quantity.two;
import static com.android.tools.klint.checks.PluralsDatabase.Quantity.zero;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.klint.detector.api.LintUtils;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
/**
* Database used by the {@link PluralsDetector} to get information
* about plural forms for a given language
*/
public class PluralsDatabase {
private static final EnumSet<Quantity> NONE = EnumSet.noneOf(Quantity.class);
private static final PluralsDatabase sInstance = new PluralsDatabase();
private Map<String, EnumSet<Quantity>> mPlurals = Maps.newHashMap();
/** Bit set if this language uses quantity zero */
@SuppressWarnings("PointlessBitwiseExpression")
static final int FLAG_ZERO = 1 << 0;
/** Bit set if this language uses quantity one */
static final int FLAG_ONE = 1 << 1;
/** Bit set if this language uses quantity two */
static final int FLAG_TWO = 1 << 2;
/** Bit set if this language uses quantity few */
static final int FLAG_FEW = 1 << 3;
/** Bit set if this language uses quantity many */
static final int FLAG_MANY = 1 << 4;
/** Bit set if this language has multiple values that match quantity zero */
static final int FLAG_MULTIPLE_ZERO = 1 << 5;
/** Bit set if this language has multiple values that match quantity one */
static final int FLAG_MULTIPLE_ONE = 1 << 6;
/** Bit set if this language has multiple values that match quantity two */
static final int FLAG_MULTIPLE_TWO = 1 << 7;
@NonNull
public static PluralsDatabase get() {
return sInstance;
}
private static int getFlags(@NonNull String language) {
int index = getLanguageIndex(language);
if (index != -1) {
return FLAGS[index];
}
return 0;
}
private static int getLanguageIndex(@NonNull String language) {
int index = Arrays.binarySearch(LANGUAGE_CODES, language);
if (index >= 0) {
assert LANGUAGE_CODES[index].equals(language);
return index;
} else {
return -1;
}
}
@Nullable
public EnumSet<Quantity> getRelevant(@NonNull String language) {
EnumSet<Quantity> set = mPlurals.get(language);
if (set == null) {
int index = getLanguageIndex(language);
if (index == -1) {
mPlurals.put(language, NONE);
return null;
}
// Process each item and look for relevance
int flag = FLAGS[index];
set = EnumSet.noneOf(Quantity.class);
if ((flag & FLAG_ZERO) != 0) {
set.add(zero);
}
if ((flag & FLAG_ONE) != 0) {
set.add(one);
}
if ((flag & FLAG_TWO) != 0) {
set.add(two);
}
if ((flag & FLAG_FEW) != 0) {
set.add(few);
}
if ((flag & FLAG_MANY) != 0) {
set.add(many);
}
mPlurals.put(language, set);
}
return set == NONE ? null : set;
}
@SuppressWarnings("MethodMayBeStatic")
public boolean hasMultipleValuesForQuantity(
@NonNull String language,
@NonNull Quantity quantity) {
if (quantity == one) {
return (getFlags(language) & FLAG_MULTIPLE_ONE) != 0;
} else if (quantity == two) {
return (getFlags(language) & FLAG_MULTIPLE_TWO) != 0;
} else {
return quantity == zero && (getFlags(language) & FLAG_MULTIPLE_ZERO) != 0;
}
}
@SuppressWarnings("MethodMayBeStatic")
@Nullable
public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) {
if (quantity == one) {
return getExampleForQuantityOne(language);
} else if (quantity == two) {
return getExampleForQuantityTwo(language);
} else if (quantity == zero) {
return getExampleForQuantityZero(language);
} else {
return null;
}
}
public enum Quantity {
// deliberately lower case to match attribute names
few, many, one, two, zero, other;
@Nullable
public static Quantity get(@NonNull String name) {
for (Quantity quantity : values()) {
if (name.equals(quantity.name())) {
return quantity;
}
}
return null;
}
public static String formatSet(@NonNull EnumSet<Quantity> set) {
List<String> list = new ArrayList<String>(set.size());
for (Quantity quantity : set) {
list.add('`' + quantity.name() + '`');
}
return LintUtils.formatList(list, Integer.MAX_VALUE);
}
}
// GENERATED DATA.
// This data is generated by the #testDatabaseAccurate method in PluralsDatabaseTest
// which will generate the following if it can find an ICU plurals database file
// in the unit test data folder.
/** Set of language codes relevant to plurals data */
private static final String[] LANGUAGE_CODES = new String[] {
"af", "ak", "am", "ar", "az", "be", "bg", "bh", "bm", "bn",
"bo", "br", "bs", "ca", "cs", "cy", "da", "de", "dv", "dz",
"ee", "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi",
"fo", "fr", "fy", "ga", "gd", "gl", "gu", "gv", "ha", "he",
"hi", "hr", "hu", "hy", "id", "ig", "ii", "in", "is", "it",
"iu", "iw", "ja", "ji", "jv", "ka", "kk", "kl", "km", "kn",
"ko", "ks", "ku", "kw", "ky", "lb", "lg", "ln", "lo", "lt",
"lv", "mg", "mk", "ml", "mn", "mr", "ms", "mt", "my", "nb",
"nd", "ne", "nl", "nn", "no", "nr", "ny", "om", "or", "os",
"pa", "pl", "ps", "pt", "rm", "ro", "ru", "se", "sg", "si",
"sk", "sl", "sn", "so", "sq", "sr", "ss", "st", "sv", "sw",
"ta", "te", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts",
"ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh",
"yi", "yo", "zh", "zu"
};
/**
* Relevant flags for each language (corresponding to each language listed
* in the same position in {@link #LANGUAGE_CODES}).
*/
private static final int[] FLAGS = new int[] {
0x0002, 0x0042, 0x0042, 0x001f, 0x0002, 0x005a, 0x0002, 0x0042,
0x0000, 0x0042, 0x0000, 0x00de, 0x004a, 0x0002, 0x000a, 0x001f,
0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0002, 0x0002, 0x0002,
0x0002, 0x0002, 0x0002, 0x0042, 0x0042, 0x0002, 0x0002, 0x0042,
0x0002, 0x001e, 0x00ce, 0x0002, 0x0042, 0x00ce, 0x0002, 0x0016,
0x0042, 0x004a, 0x0002, 0x0042, 0x0000, 0x0000, 0x0000, 0x0000,
0x0042, 0x0002, 0x0006, 0x0016, 0x0000, 0x0002, 0x0000, 0x0002,
0x0002, 0x0002, 0x0000, 0x0042, 0x0000, 0x0002, 0x0002, 0x0006,
0x0002, 0x0002, 0x0002, 0x0042, 0x0000, 0x004a, 0x0063, 0x0042,
0x0042, 0x0002, 0x0002, 0x0042, 0x0000, 0x001a, 0x0000, 0x0002,
0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0002, 0x0002, 0x0042, 0x001a, 0x0002, 0x0002, 0x0002, 0x000a,
0x005a, 0x0006, 0x0000, 0x0042, 0x000a, 0x00ce, 0x0002, 0x0002,
0x0002, 0x004a, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0000, 0x0042, 0x0002, 0x0042, 0x0002, 0x0000, 0x0002, 0x0002,
0x0002, 0x005a, 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0042,
0x0000, 0x0002, 0x0002, 0x0000, 0x0000, 0x0042
};
@Nullable
private static String getExampleForQuantityZero(@NonNull String language) {
int index = getLanguageIndex(language);
switch (index) {
// set14
case 70: // lv
return "0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, \u2026";
case -1:
default:
return null;
}
}
@Nullable
private static String getExampleForQuantityOne(@NonNull String language) {
int index = getLanguageIndex(language);
switch (index) {
// set1
case 2: // am
case 9: // bn
case 27: // fa
case 36: // gu
case 40: // hi
case 59: // kn
case 75: // mr
case 133: // zu
return "0, 1";
// set11
case 48: // is
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set12
case 72: // mk
return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
// set13
case 115: // tl
return "0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, \u2026";
// set14
case 70: // lv
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set2
case 28: // ff
case 31: // fr
case 43: // hy
return "0, 1";
// set20
case 12: // bs
case 41: // hr
case 105: // sr
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set21
case 34: // gd
return "1, 11";
// set22
case 101: // sl
return "1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026";
// set26
case 5: // be
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set27
case 69: // lt
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set29
case 96: // ru
case 121: // uk
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set30
case 11: // br
return "1, 21, 31, 41, 51, 61, 81, 101, 1001, \u2026";
// set32
case 37: // gv
return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
// set5
case 99: // si
return "0, 1";
// set6
case 1: // ak
case 7: // bh
case 67: // ln
case 71: // mg
case 90: // pa
case 113: // ti
case 127: // wa
return "0, 1";
case -1:
default:
return null;
}
}
@Nullable
private static String getExampleForQuantityTwo(@NonNull String language) {
int index = getLanguageIndex(language);
switch (index) {
// set21
case 34: // gd
return "2, 12";
// set22
case 101: // sl
return "2, 102, 202, 302, 402, 502, 602, 702, 1002, \u2026";
// set30
case 11: // br
return "2, 22, 32, 42, 52, 62, 82, 102, 1002, \u2026";
// set32
case 37: // gv
return "2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, \u2026";
case -1:
default:
return null;
}
}
}
@@ -81,7 +81,7 @@ public class PrivateResourceDetector extends ResourceXmlDetector implements Uast
private static final Implementation IMPLEMENTATION = new Implementation(
PrivateResourceDetector.class,
Scope.SOURCE_AND_RESOURCE_FILES,
Scope.JAVA_FILE_SCOPE,
Scope.SOURCE_FILE_SCOPE,
Scope.RESOURCE_FILE_SCOPE);
/** The main issue discovered by this detector */
@@ -259,7 +259,7 @@ public class PrivateResourceDetector extends ResourceXmlDetector implements Uast
}
@Override
public void beforeCheckProject(@NonNull Context context) {
public void beforeCheckFile(@NonNull Context context) {
File file = context.file;
boolean isXmlFile = LintUtils.isXmlFile(file);
if (!isXmlFile && !LintUtils.isBitmapFile(file)) {
@@ -1,263 +0,0 @@
/*
* Copyright (C) 2012 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.klint.checks;
import static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
import static com.android.SdkConstants.ANDROID_APP_SERVICE;
import static com.android.SdkConstants.ANDROID_CONTENT_BROADCAST_RECEIVER;
import static com.android.SdkConstants.ANDROID_CONTENT_CONTENT_PROVIDER;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.TAG_ACTIVITY;
import static com.android.SdkConstants.TAG_PROVIDER;
import static com.android.SdkConstants.TAG_RECEIVER;
import static com.android.SdkConstants.TAG_SERVICE;
import com.android.annotations.NonNull;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.ClassContext;
import com.android.tools.klint.detector.api.Detector.ClassScanner;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.LayoutDetector;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.Speed;
import com.android.tools.klint.detector.api.XmlContext;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.w3c.dom.Element;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Map.Entry;
/**
* Checks for missing manifest registrations for activities, services etc
* and also makes sure that they are registered with the correct tag
* <p>
* TODO: Rewrite as Java visitor!
*/
public class RegistrationDetector extends LayoutDetector implements ClassScanner {
/** Unregistered activities and services */
public static final Issue ISSUE = Issue.create(
"Registered", //$NON-NLS-1$
"Class is not registered in the manifest",
"Activities, services and content providers should be registered in the " +
"`AndroidManifest.xml` file using `<activity>`, `<service>` and `<provider>` tags.\n" +
"\n" +
"If your activity is simply a parent class intended to be subclassed by other " +
"\"real\" activities, make it an abstract class.",
Category.CORRECTNESS,
6,
Severity.WARNING,
new Implementation(
RegistrationDetector.class,
EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE)))
.addMoreInfo(
"http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
protected Multimap<String, String> mManifestRegistrations;
/** Constructs a new {@link RegistrationDetector} */
public RegistrationDetector() {
}
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
// ---- Implements XmlScanner ----
@Override
public Collection<String> getApplicableElements() {
return Arrays.asList(sTags);
}
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
String fqcn = getFqcn(context, element);
String tag = element.getTagName();
String frameworkClass = tagToClass(tag);
if (frameworkClass != null) {
String signature = ClassContext.getInternalName(fqcn);
if (mManifestRegistrations == null) {
mManifestRegistrations = ArrayListMultimap.create(4, 8);
}
mManifestRegistrations.put(frameworkClass, signature);
if (signature.indexOf('$') != -1) {
// The internal name contains a $ which means it's an inner class.
// The conversion from fqcn to internal name is a bit ambiguous:
// "a.b.C.D" usually means "inner class D in class C in package a.b".
// However, it can (see issue 31592) also mean class D in package "a.b.C".
// Place *both* of these possibilities in the registered map, since this
// is only used to check that an activity is registered, not the other way
// (so it's okay to have entries there that do not correspond to real classes).
signature = signature.replace('$', '/');
mManifestRegistrations.put(frameworkClass, signature);
}
}
}
/**
* Returns the fully qualified class name for a manifest entry element that
* specifies a name attribute
*
* @param context the query context providing the project
* @param element the element
* @return the fully qualified class name
*/
@NonNull
private static String getFqcn(@NonNull XmlContext context, @NonNull Element element) {
String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
if (className.startsWith(".")) { //$NON-NLS-1$
return context.getMainProject().getPackage() + className;
} else if (className.indexOf('.') == -1) {
// According to the <activity> manifest element documentation, this is not
// valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
// but it appears in manifest files and appears to be supported by the runtime
// so handle this in code as well:
return context.getMainProject().getPackage() + '.' + className;
} // else: the class name is already a fully qualified class name
return className;
}
// ---- Implements ClassScanner ----
@Override
public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
// Abstract classes do not need to be registered
if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
return;
}
String curr = classNode.name;
int lastIndex = curr.lastIndexOf('$');
if (lastIndex != -1 && lastIndex < curr.length() - 1) {
if (Character.isDigit(curr.charAt(lastIndex+1))) {
// Anonymous inner class, doesn't need to be registered
return;
}
}
while (curr != null) {
for (String s : sClasses) {
if (curr.equals(s)) {
Collection<String> registered = mManifestRegistrations != null ?
mManifestRegistrations.get(curr) : null;
if (registered == null || !registered.contains(classNode.name)) {
report(context, classNode, curr);
}
}
}
curr = context.getDriver().getSuperClass(curr);
}
}
private void report(ClassContext context, ClassNode classNode, String curr) {
String tag = classToTag(curr);
String className = ClassContext.createSignature(classNode.name, null, null);
String wrongClass = null; // The framework class this class actually extends
if (mManifestRegistrations != null) {
Collection<Entry<String,String>> entries =
mManifestRegistrations.entries();
for (Entry<String,String> entry : entries) {
if (entry.getValue().equals(classNode.name)) {
wrongClass = entry.getKey();
break;
}
}
}
if (wrongClass != null) {
Location location = context.getLocation(classNode);
context.report(
ISSUE,
location,
String.format(
"`%1$s` is a `<%2$s>` but is registered in the manifest as a `<%3$s>`",
className, tag, classToTag(wrongClass)));
} else if (!TAG_RECEIVER.equals(tag)) { // don't need to be registered
if (context.getMainProject().isGradleProject()) {
// Disabled for now; we need to formalize the difference between
// the *manifest* package and the variant package, since in some contexts
// (such as manifest registrations) we should be using the manifest package,
// not the gradle package
return;
}
Location location = context.getLocation(classNode);
context.report(
ISSUE,
location,
String.format(
"The `<%1$s> %2$s` is not registered in the manifest",
tag, className));
}
}
/** The manifest tags we care about */
private static final String[] sTags = new String[] {
TAG_ACTIVITY,
TAG_SERVICE,
TAG_RECEIVER,
TAG_PROVIDER,
// Keep synchronized with {@link #sClasses}
};
/** The corresponding framework classes that the tags in {@link #sTags} should extend */
private static final String[] sClasses = new String[] {
ANDROID_APP_ACTIVITY,
ANDROID_APP_SERVICE,
ANDROID_CONTENT_BROADCAST_RECEIVER,
ANDROID_CONTENT_CONTENT_PROVIDER,
// Keep synchronized with {@link #sTags}
};
/** Looks up the corresponding framework class a given manifest tag's class should extend */
private static String tagToClass(String tag) {
for (int i = 0, n = sTags.length; i < n; i++) {
if (sTags[i].equals(tag)) {
return sClasses[i];
}
}
return null;
}
/** Looks up the tag a given framework class should be registered with */
protected static String classToTag(String className) {
for (int i = 0, n = sClasses.length; i < n; i++) {
if (sClasses[i].equals(className)) {
return sTags[i];
}
}
return null;
}
}
@@ -538,7 +538,7 @@ public class RequiredAttributeDetector extends LayoutDetector implements UastSca
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
// Handle
// View#inflate(Context context, int resource, ViewGroup root)
// LayoutInflater#inflate(int resource, ViewGroup root)
@@ -73,7 +73,6 @@ import com.android.tools.klint.detector.api.XmlContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.EmptyUastVisitor;
@@ -99,7 +98,7 @@ public class RtlDetector extends LayoutDetector implements UastScanner {
RtlDetector.class,
EnumSet.of(Scope.RESOURCE_FILE, Scope.SOURCE_FILE, Scope.MANIFEST),
Scope.RESOURCE_FILE_SCOPE,
Scope.JAVA_FILE_SCOPE,
Scope.SOURCE_FILE_SCOPE,
Scope.MANIFEST_SCOPE
);
@@ -607,7 +606,7 @@ public class RtlDetector extends LayoutDetector implements UastScanner {
// Can't resolve types (for example while editing code with errors):
// rely on heuristics like import statements and class qualifiers
if (parent instanceof UQualifiedExpression &&
!(GRAVITY_CLASS.equals(((UQualifiedExpression) parent).getReceiver().renderString()))) {
!(UastUtils.matchesQualified(((UQualifiedExpression) parent).getReceiver(), GRAVITY_CLASS))) {
return false;
}
if (parent instanceof USimpleReferenceExpression) {
@@ -624,7 +623,7 @@ public class RtlDetector extends LayoutDetector implements UastScanner {
+ "behavior in right-to-left locales",
(isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END).toUpperCase(Locale.US),
(isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT).toUpperCase(Locale.US));
Location location = UastAndroidUtils.getLocation(node);
Location location = mContext.getLocation(node);
mContext.report(USE_START, node, location, message);
return true;
@@ -29,7 +29,6 @@ import java.util.Collections;
import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -38,7 +37,7 @@ import org.jetbrains.uast.check.UastScanner;
*/
public class SQLiteDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
SQLiteDetector.class, Scope.JAVA_FILE_SCOPE);
SQLiteDetector.class, Scope.SOURCE_FILE_SCOPE);
/** Using STRING instead of TEXT for columns */
public static final Issue ISSUE = Issue.create(
@@ -77,7 +76,7 @@ public class SQLiteDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
UFunction resolvedFunction = node.resolve(context);
UClass containingClass = UastUtils.getContainingClass(resolvedFunction);
if (resolvedFunction == null || containingClass == null) {
@@ -100,7 +99,7 @@ public class SQLiteDetector extends Detector implements UastScanner {
+ "(STRING is a numeric type and its value can be adjusted; for example,"
+ "strings that look like integers can drop leading zeroes. See issue "
+ "explanation for details.)";
context.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
context.report(ISSUE, node, context.getLocation(node), message);
}
}
}
@@ -31,7 +31,6 @@ import java.io.File;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -57,7 +56,7 @@ public class SdCardDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
SdCardDetector.class,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
.addMoreInfo(
"http://developer.android.com/guide/topics/data/data-storage.html#filesExternal"); //$NON-NLS-1$
@@ -110,13 +109,13 @@ public class SdCardDetector extends Detector implements UastScanner {
|| s.startsWith("file:///sdcard/")) { //$NON-NLS-1$
String message = "Do not hardcode \"/sdcard/\"; " +
"use `Environment.getExternalStorageDirectory().getPath()` instead";
Location location = UastAndroidUtils.getLocation(node);
Location location = mContext.getLocation(node);
mContext.report(ISSUE, node, location, message);
} else if (s.startsWith("/data/data/") //$NON-NLS-1$
|| s.startsWith("/data/user/")) { //$NON-NLS-1$
String message = "Do not hardcode \"`/data/`\"; " +
"use `Context.getFilesDir().getPath()` instead";
Location location = UastAndroidUtils.getLocation(node);
Location location = mContext.getLocation(node);
mContext.report(ISSUE, node, location, message);
}
@@ -52,7 +52,6 @@ import com.android.tools.klint.detector.api.XmlContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.USimpleReferenceExpression;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -75,7 +74,7 @@ public class SecurityDetector extends Detector implements Detector.XmlScanner, U
private static final Implementation IMPLEMENTATION_JAVA = new Implementation(
SecurityDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Exported services */
public static final Issue EXPORTED_SERVICE = Issue.create(
@@ -366,12 +365,12 @@ public class SecurityDetector extends Detector implements Detector.XmlScanner, U
@Override
public boolean visitSimpleReferenceExpression(@NotNull USimpleReferenceExpression node) {
if ("MODE_WORLD_WRITEABLE".equals(node.getIdentifier())) { //$NON-NLS-1$
Location location = UastAndroidUtils.getLocation(node);
Location location = mContext.getLocation(node);
mContext.report(WORLD_WRITEABLE, node, location,
"Using `MODE_WORLD_WRITEABLE` when creating files can be " +
"risky, review carefully");
} else if ("MODE_WORLD_READABLE".equals(node.getIdentifier())) { //$NON-NLS-1$
Location location = UastAndroidUtils.getLocation(node);
Location location = mContext.getLocation(node);
mContext.report(WORLD_READABLE, node, location,
"Using `MODE_WORLD_READABLE` when creating files can be " +
"risky, review carefully");
@@ -33,7 +33,6 @@ import java.util.List;
import java.util.Map;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -55,7 +54,7 @@ public class ServiceCastDetector extends Detector implements UastScanner {
Severity.FATAL,
new Implementation(
ServiceCastDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Constructs a new {@link ServiceCastDetector} check */
public ServiceCastDetector() {
@@ -75,7 +74,7 @@ public class ServiceCastDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
UExpression receiver = UastUtils.getQualifiedCallElement(node);
UElement parent = receiver.getParent();
if (!(parent instanceof UBinaryExpressionWithType)
@@ -103,7 +102,7 @@ public class ServiceCastDetector extends Detector implements UastScanner {
String message = String.format(
"Suspicious cast to `%1$s` for a `%2$s`: expected `%3$s`",
stripPackage(castType), name, stripPackage(expectedClass));
context.report(ISSUE, node, UastAndroidUtils.getLocation(cast), message);
context.report(ISSUE, node, context.getLocation(cast), message);
}
}
}
@@ -27,7 +27,6 @@ import java.util.Collections;
import java.util.List;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -47,7 +46,7 @@ public class SetJavaScriptEnabledDetector extends Detector implements UastScanne
Severity.WARNING,
new Implementation(
SetJavaScriptEnabledDetector.class,
Scope.JAVA_FILE_SCOPE))
Scope.SOURCE_FILE_SCOPE))
.addMoreInfo(
"http://developer.android.com/guide/practices/security.html"); //$NON-NLS-1$
@@ -58,14 +57,14 @@ public class SetJavaScriptEnabledDetector extends Detector implements UastScanne
// ---- Implements UastScanner ----
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
if (node.getValueArgumentCount() != 1) {
return;
}
Object value = node.getValueArguments().get(0).evaluate();
if (value instanceof Boolean && (Boolean) value) {
context.report(ISSUE, node, UastAndroidUtils.getLocation(node),
context.report(ISSUE, node, context.getLocation(node),
"Using `setJavaScriptEnabled` can introduce XSS vulnerabilities " +
"into you application, review carefully.");
}
@@ -34,9 +34,8 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.java.JavaSpecialExpressionKinds;
import org.jetbrains.uast.java.JavaUAssertExpression;
import org.jetbrains.uast.visitor.UastVisitor;
/**
@@ -57,7 +56,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
SharedPrefsDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
public static final String ANDROID_CONTENT_SHARED_PREFERENCES =
"android.content.SharedPreferences"; //$NON-NLS-1$
@@ -82,7 +81,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
assert "edit".equals(node.getFunctionName());
boolean verifiedType = false;
@@ -149,7 +148,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
CommitFinder finder = new CommitFinder(context, node, allowCommitBeforeTarget);
method.accept(finder);
if (!finder.isCommitCalled()) {
context.report(ISSUE, method, UastAndroidUtils.getLocation(node),
context.report(ISSUE, method, context.getLocation(node),
"`SharedPreferences.edit()` without a corresponding `commit()` or `apply()` call");
}
}
@@ -220,8 +219,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
returnValueIgnored = true;
} else if (qualifiedElement instanceof UIfExpression) {
returnValueIgnored = ((UIfExpression) qualifiedElement).getCondition() != node;
} else if (qualifiedElement instanceof USpecialExpressionList &&
((USpecialExpressionList)qualifiedElement).getKind() == UastSpecialExpressionKind.RETURN) {
} else if (qualifiedElement instanceof UReturnExpression) {
returnValueIgnored = false;
} else if (qualifiedElement instanceof UVariable) {
returnValueIgnored = false;
@@ -231,11 +229,16 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
returnValueIgnored = ((UWhileExpression) qualifiedElement).getCondition() != node;
} else if (qualifiedElement instanceof UDoWhileExpression) {
returnValueIgnored = ((UDoWhileExpression) qualifiedElement).getCondition() != node;
} else if (qualifiedElement instanceof UExpressionSwitchClauseExpression) {
returnValueIgnored = ((UExpressionSwitchClauseExpression) qualifiedElement).getCaseValue() != node;
} else if (qualifiedElement instanceof USpecialExpressionList &&
((USpecialExpressionList)qualifiedElement).getKind() == JavaSpecialExpressionKinds.ASSERT) {
returnValueIgnored = !((USpecialExpressionList) qualifiedElement).getExpressions().contains(node);
} else if (qualifiedElement instanceof USwitchClauseExpression) {
List<UExpression> caseValues = ((USwitchClauseExpression) qualifiedElement).getCaseValues();
for (UExpression caseValue : caseValues) {
if (caseValue == node) {
returnValueIgnored = false;
break;
}
}
} else if (qualifiedElement instanceof JavaUAssertExpression) {
returnValueIgnored = !((JavaUAssertExpression) qualifiedElement).getCondition().equals(node);
} else {
returnValueIgnored = true;
}
@@ -243,7 +246,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
String message = "Consider using `apply()` instead; `commit` writes "
+ "its data to persistent storage immediately, whereas "
+ "`apply` will handle it in the background";
mContext.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
mContext.report(ISSUE, node, mContext.getLocation(node), message);
}
}
}
@@ -254,11 +257,9 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
}
@Override
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
if (node.getKind() == UastSpecialExpressionKind.RETURN) {
if (node.getExpressions().contains(mTarget)) {
mFound = true;
}
public boolean visitReturnExpression(@NotNull UReturnExpression node) {
if (mTarget.equals(node.getExpressionType())) {
mFound = true;
}
return false;
@@ -102,7 +102,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements UastSca
private static final Implementation IMPLEMENTATION_XML_AND_JAVA = new Implementation(
StringFormatDetector.class,
EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.SOURCE_FILE),
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Whether formatting strings are invalid */
@@ -987,7 +987,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements UastSca
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
if (mFormatStrings == null && !context.getLintContext().getClient().supportsProjectResources()) {
return;
}
@@ -53,7 +53,6 @@ import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.java.JavaUFunction;
import org.jetbrains.uast.java.JavaUastCallKinds;
import org.jetbrains.uast.kinds.UastOperator;
import org.jetbrains.uast.visitor.UastVisitor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -79,7 +78,7 @@ import java.util.Set;
public class SupportAnnotationDetector extends Detector implements UastScanner {
public static final Implementation IMPLEMENTATION
= new Implementation(SupportAnnotationDetector.class, Scope.JAVA_FILE_SCOPE);
= new Implementation(SupportAnnotationDetector.class, Scope.SOURCE_FILE_SCOPE);
/** Method result should be used */
public static final Issue RANGE = Issue.create(
@@ -753,7 +752,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
UQualifiedExpression select = (UQualifiedExpression) node.getReceiver();
if (select.getReceiver() instanceof UQualifiedExpression) { // android.R....
UQualifiedExpression innerSelect = (UQualifiedExpression) select.getReceiver();
if (innerSelect.getSelector().renderString().equals(R_CLASS)) {
if (UastUtils.matchesQualified(innerSelect.getSelector(), R_CLASS)) {
String typeName = select.getSelector().renderString();
ResourceType type = ResourceType.getEnum(typeName);
return type != null ? Collections.singletonList(type) : null;
@@ -770,7 +769,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
}
// Arbitrary packages -- android.R.type.name, foo.bar.R.type.name
if (node.getSelector().renderString().equals(R_CLASS)) {
if (UastUtils.matchesQualified(node.getSelector(), R_CLASS)) {
UElement parent = node.getParent();
if (parent instanceof UQualifiedExpression) {
UElement grandParent = parent.getParent();
@@ -788,8 +787,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
}
} else if (argument instanceof UCallExpression) {
UDeclaration resolved = ((UCallExpression)argument).resolve(context);
//noinspection ConstantConditions
if (resolved instanceof UAnnotated) {
if (resolved != null) {
List<UAnnotation> annotations = ((UAnnotated) resolved).getAnnotations();
for (UAnnotation annotation : annotations) {
String signature = annotation.getFqName();
@@ -972,7 +970,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
assert s != null;
actual = s.length();
} else if (argument instanceof UCallExpression
&& ((UCallExpression)argument).getKind() == JavaUastCallKinds.ARRAY_INITIALIZER) {
&& ((UCallExpression)argument).getKind() == UastCallKind.ARRAY_INITIALIZER) {
UCallExpression initializer = (UCallExpression) argument;
actual = initializer.getValueArgumentCount();
} else {
@@ -1059,7 +1057,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
if (!flag) {
reportTypeDef(context, annotation, argument, errorNode);
}
} else if (argument instanceof UIfExpression && ((UIfExpression) argument).isTernary()) {
} else if (argument instanceof UIfExpression) {
UIfExpression expression = (UIfExpression) argument;
if (expression.getThenBranch() != null) {
checkTypeDefConstant(context, annotation, expression.getThenBranch(), errorNode, flag);
@@ -1077,7 +1075,6 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
"Flag not allowed here");
}
} else if (argument instanceof UBinaryExpression) {
// If it's ?: then check both the if and else clauses
UBinaryExpression expression = (UBinaryExpression) argument;
if (flag) {
checkTypeDefConstant(context, annotation, expression.getLeftOperand(), errorNode, true);
@@ -1102,13 +1099,18 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
private static void checkTypeDefConstant(@NonNull UastAndroidContext context,
@NonNull UAnnotation annotation, @NonNull UElement argument,
@Nullable UElement errorNode, boolean flag, Object value) {
List<Pair<String, Object>> valueArguments = annotation.getValues();
for (Pair<String, Object> o : valueArguments) {
if (o.getSecond().equals(value)) {
return;
for (UNamedExpression namedExpression : annotation.getValueArguments()) {
UExpression expression = namedExpression.getExpression();
if (expression instanceof UCallExpression &&
((UCallExpression) expression).getKind() == UastCallKind.ARRAY_INITIALIZER) {
for (UExpression arg : ((UCallExpression) expression).getValueArguments()) {
if (value.equals(arg.evaluate())) {
return;
}
}
}
}
reportTypeDef(context, argument, errorNode, flag, valueArguments);
reportTypeDef(context, argument, errorNode, flag, annotation.getValues());
}
private static void reportTypeDef(@NonNull UastAndroidContext context,
@@ -1242,47 +1244,6 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
return null;
}
@Nullable
static UAnnotation getRelevantAnnotation(
@NonNull UastAndroidContext context,
@NonNull UAnnotation annotation
) {
String signature = annotation.getFqName();
if (signature != null && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
// Bail on the nullness annotations early since they're the most commonly
// defined ones. They're not analyzed in lint yet.
if (signature.endsWith(".Nullable") || signature.endsWith(".NonNull")) {
return null;
}
return annotation;
}
if (signature != null && signature.startsWith("java.")) {
// @Override, @SuppressWarnings etc. Ignore
return null;
}
// Special case @IntDef and @StringDef: These are used on annotations
// themselves. For example, you create a new annotation named @foo.bar.Baz,
// annotate it with @IntDef, and then use @foo.bar.Baz in your signatures.
// Here we want to map from @foo.bar.Baz to the corresponding int def.
// Don't need to compute this if performing @IntDef or @StringDef lookup
UClass type = annotation.resolve(context);
if (type != null) {
for (UAnnotation inner : type.getAnnotations()) {
if (INT_DEF_ANNOTATION.equals(inner.getFqName())
|| STRING_DEF_ANNOTATION.equals(inner.getFqName())
|| PERMISSION_ANNOTATION.equals(inner.getFqName())) {
return inner;
}
}
}
return null;
}
// ---- Implements UastScanner ----
@Override
@@ -1,114 +0,0 @@
/*
* Copyright (C) 2012 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.klint.checks;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_TITLE;
import static com.android.SdkConstants.ATTR_VISIBLE;
import static com.android.SdkConstants.TAG_ITEM;
import static com.android.SdkConstants.VALUE_FALSE;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.resources.ResourceFolderType;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.ResourceXmlDetector;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.Speed;
import com.android.tools.klint.detector.api.XmlContext;
import org.jetbrains.uast.check.UastScanner;
import org.w3c.dom.Element;
import java.util.Collection;
import java.util.Collections;
/**
* Check which makes sure menu items specify a title
*/
public class TitleDetector extends ResourceXmlDetector implements UastScanner {
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
"MenuTitle", //$NON-NLS-1$
"Missing menu title",
"From the action bar documentation:\n" +
// u2014: em dash
"\"It's important that you always define android:title for each menu item \u2014 " +
"even if you don't declare that the title appear with the action item \u2014 for " +
"three reasons:\n" +
"\n" +
"* If there's not enough room in the action bar for the action item, the menu " +
"item appears in the overflow menu and only the title appears.\n" +
"* Screen readers for sight-impaired users read the menu item's title.\n" +
"* If the action item appears with only the icon, a user can long-press the item " +
"to reveal a tool-tip that displays the action item's title.\n" +
"The android:icon is always optional, but recommended.",
Category.USABILITY,
5,
Severity.ERROR,
new Implementation(
TitleDetector.class,
Scope.RESOURCE_FILE_SCOPE))
.addMoreInfo(
"http://developer.android.com/guide/topics/ui/actionbar.html"); //$NON-NLS-1$
/** Constructs a new {@link TitleDetector} */
public TitleDetector() {
}
@Override
public boolean appliesTo(@NonNull ResourceFolderType folderType) {
return folderType == ResourceFolderType.MENU;
}
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
@Override
@Nullable
public Collection<String> getApplicableElements() {
return Collections.singletonList(TAG_ITEM);
}
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
if (element.hasAttributeNS(ANDROID_URI, ATTR_TITLE)) {
return;
}
// TODO: Find out if this is necessary on older versions too.
// I swear I saw it mentioned.
if (context.getMainProject().getTargetSdk() < 11) {
return;
}
if (VALUE_FALSE.equals(element.getAttributeNS(ANDROID_URI, ATTR_VISIBLE))) {
return;
}
String message = "Menu items should specify a `title`";
context.report(ISSUE, element, context.getLocation(element), message);
}
}
@@ -31,7 +31,6 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -51,7 +50,7 @@ public class ToastDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
ToastDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Constructs a new {@link ToastDetector} check */
@@ -72,7 +71,7 @@ public class ToastDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
assert "makeText".equals(node.getFunctionName());
UElement qualifiedExpression = node.getParent();
@@ -80,8 +79,7 @@ public class ToastDetector extends Detector implements UastScanner {
return;
}
String operand = ((UQualifiedExpression)qualifiedExpression).getReceiver().renderString();
if (!(operand.equals("Toast") || operand.endsWith(".Toast"))) {
if (!UastUtils.endsWithQualified(((UQualifiedExpression) qualifiedExpression).getReceiver(), "Toast")) {
return;
}
@@ -107,7 +105,7 @@ public class ToastDetector extends Detector implements UastScanner {
ShowFinder finder = new ShowFinder(nodeWithPossibleQualifier);
method.accept(finder);
if (!finder.isShowCalled()) {
context.report(ISSUE, node, UastAndroidUtils.getLocation(node),
context.report(ISSUE, node, context.getLocation(node),
"Toast created but not shown: did you forget to call `show()` ?");
}
}
@@ -128,7 +126,7 @@ public class ToastDetector extends Detector implements UastScanner {
public boolean visitCallExpression(@NotNull UCallExpression node) {
if (node == mTarget) {
mSeenTarget = true;
} else if (mSeenTarget && node.functionNameMatches("show")) { //$NON-NLS-1$
} else if (mSeenTarget && node.matchesFunctionName("show")) { //$NON-NLS-1$
// TODO: Do more flow analysis to see whether we're really calling show
// on the right type of object?
mFound = true;
@@ -147,9 +145,8 @@ public class ToastDetector extends Detector implements UastScanner {
}
@Override
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
if (node.getKind() == UastSpecialExpressionKind.RETURN && node.firstOrNull() == mTarget) {
// If you just do "return Toast.makeText(...) don't warn
public boolean visitReturnExpression(@NotNull UReturnExpression node) {
if (mTarget.equals(node.getReturnExpression())) {
mFound = true;
}
@@ -35,10 +35,8 @@ import java.util.Collections;
import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.kinds.UastClassKind;
/**
* Looks for custom views that do not define the view constructors needed by UI builders
@@ -65,7 +63,7 @@ public class ViewConstructorDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
ViewConstructorDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Constructs a new {@link ViewConstructorDetector} check */
public ViewConstructorDetector() {
@@ -140,7 +138,7 @@ public class ViewConstructorDetector extends Detector implements UastScanner {
+ "`(Context)` or `(Context,AttributeSet)` "
+ "or `(Context,AttributeSet,int)`",
node.getFqName());
Location location = UastAndroidUtils.getLocation(node.getNameElement());
Location location = context.getLocation(node.getNameElement());
context.report(ISSUE, node, location, message /*data*/);
}
}
@@ -34,7 +34,6 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -45,7 +44,7 @@ public class ViewHolderDetector extends Detector implements UastScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
ViewHolderDetector.class,
Scope.JAVA_FILE_SCOPE);
Scope.SOURCE_FILE_SCOPE);
/** Using a view inflater unconditionally in an AdapterView */
public static final Issue ISSUE = Issue.create(
@@ -109,32 +108,8 @@ public class ViewHolderDetector extends Detector implements UastScanner {
if (GET_VIEW.equals(node.getName())) {
List<UVariable> parameters = node.getValueParameters();
if (parameters.size() == 3) {
Iterator<UVariable> iterator = parameters.iterator();
if (!iterator.hasNext()) {
return false;
}
UVariable first = iterator.next();
if (!first.getType().isInt()) {
return false;
}
if (!iterator.hasNext()) {
return false;
}
UVariable second = iterator.next();
if (!second.getType().matchesFqName(CLASS_VIEW)) {
return false;
}
if (!iterator.hasNext()) {
return false;
}
UVariable third = iterator.next();
//noinspection RedundantIfStatement
if (!third.getType().matchesFqName(CLASS_VIEWGROUP)) {
if (!parameters.get(2).getType().matchesFqName(CLASS_VIEWGROUP)) {
return false;
}
@@ -202,7 +177,7 @@ public class ViewHolderDetector extends Detector implements UastScanner {
+ "Should use View Holder pattern (use recycled view passed "
+ "into this method as the second parameter) for smoother "
+ "scrolling";
mContext.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
mContext.report(ISSUE, node, mContext.getLocation(node), message);
}
}
}
@@ -54,7 +54,6 @@ import com.google.common.collect.Sets;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
@@ -98,7 +97,7 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
new Implementation(
ViewTypeDetector.class,
EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_SOURCE_FILES),
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Flag used to do no work if we're running in incremental mode in a .java file without
* a client supporting project resources */
@@ -168,7 +167,7 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
JavaContext lintContext = context.getLintContext();
LintClient client = lintContext.getClient();
if (mIgnore == Boolean.TRUE) {
@@ -193,42 +192,42 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
// TODO: Do flow analysis as in the StringFormatDetector in order
// to handle variable references too
if (first instanceof UQualifiedExpression) {
String resource = first.renderString();
if (resource.startsWith("R.id.")) { //$NON-NLS-1$
String id = ((UQualifiedExpression) first).getSelector().renderString();
if (UastUtils.startsWithQualified(first, "R.id")) { //$NON-NLS-1$
String id = ((UQualifiedExpression) first).getSelectorAsIdentifier();
if (client.supportsProjectResources()) {
AbstractResourceRepository resources = client
.getProjectResources(lintContext.getMainProject(), true);
if (resources == null) {
return;
}
if (id != null) {
if (client.supportsProjectResources()) {
AbstractResourceRepository resources = client
.getProjectResources(lintContext.getMainProject(), true);
if (resources == null) {
return;
}
List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
id);
if (items != null && !items.isEmpty()) {
Set<String> compatible = Sets.newHashSet();
for (ResourceItem item : items) {
Collection<String> tags = getViewTags(lintContext, item);
if (tags != null) {
compatible.addAll(tags);
List<ResourceItem> items = resources.getResourceItem(ResourceType.ID, id);
if (items != null && !items.isEmpty()) {
Set<String> compatible = Sets.newHashSet();
for (ResourceItem item : items) {
Collection<String> tags = getViewTags(lintContext, item);
if (tags != null) {
compatible.addAll(tags);
}
}
if (!compatible.isEmpty()) {
ArrayList<String> layoutTypes = Lists.newArrayList(compatible);
checkCompatible(lintContext, castType, null, layoutTypes, cast);
}
}
if (!compatible.isEmpty()) {
ArrayList<String> layoutTypes = Lists.newArrayList(compatible);
} else {
Object types = mIdToViewTag.get(id);
if (types instanceof String) {
String layoutType = (String) types;
checkCompatible(lintContext, castType, layoutType, null, cast);
} else if (types instanceof List<?>) {
@SuppressWarnings("unchecked")
List<String> layoutTypes = (List<String>) types;
checkCompatible(lintContext, castType, null, layoutTypes, cast);
}
}
} else {
Object types = mIdToViewTag.get(id);
if (types instanceof String) {
String layoutType = (String) types;
checkCompatible(lintContext, castType, layoutType, null, cast);
} else if (types instanceof List<?>) {
@SuppressWarnings("unchecked")
List<String> layoutTypes = (List<String>) types;
checkCompatible(lintContext, castType, null, layoutTypes, cast);
}
}
}
}
@@ -240,6 +239,8 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
return (UBinaryExpressionWithType) expression;
} else if (expression instanceof UQualifiedExpression) {
return findContainingTypeCast(expression.getParent());
} else if (expression instanceof UParenthesizedExpression) {
return findContainingTypeCast(((UParenthesizedExpression) expression).getExpression());
} else {
return null;
}
@@ -337,7 +338,7 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
String message = String.format(
"Unexpected cast to `%1$s`: layout tag was `%2$s`",
castType, layoutType);
context.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
context.report(ISSUE, node, context.getLocation(node), message);
}
}
}
@@ -37,7 +37,6 @@ import java.util.Arrays;
import java.util.List;
import org.jetbrains.uast.*;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
@@ -58,7 +57,7 @@ public class WrongCallDetector extends Detector implements UastScanner {
Severity.FATAL,
new Implementation(
WrongCallDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Constructs a new {@link WrongCallDetector} */
public WrongCallDetector() {
@@ -83,7 +82,7 @@ public class WrongCallDetector extends Detector implements UastScanner {
}
@Override
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
public void visitCall(UastAndroidContext context, UCallExpression node) {
// Call is only allowed if it is both only called on the super class (invoke special)
// as well as within the same overriding method (e.g. you can't call super.onLayout
// from the onMeasure method)
@@ -130,7 +129,7 @@ public class WrongCallDetector extends Detector implements UastScanner {
// Keep in sync with {@link #getOldValue} and {@link #getNewValue} below!
"Suspicious method call; should probably call \"`%1$s`\" rather than \"`%2$s`\"",
suggestion, name);
context.report(ISSUE, node, UastAndroidUtils.getLocation(node.getFunctionReference()), message);
context.report(ISSUE, node, context.getLocation(node.getFunctionReference()), message);
}
/**
@@ -28,7 +28,6 @@ import com.android.tools.klint.detector.api.Speed;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UImportStatement;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -62,7 +61,7 @@ public class WrongImportDetector extends Detector implements UastScanner {
Severity.WARNING,
new Implementation(
WrongImportDetector.class,
Scope.JAVA_FILE_SCOPE));
Scope.SOURCE_FILE_SCOPE));
/** Constructs a new {@link WrongImportDetector} check */
public WrongImportDetector() {
@@ -92,7 +91,7 @@ public class WrongImportDetector extends Detector implements UastScanner {
public boolean visitImportStatement(@NotNull UImportStatement node) {
String fqn = node.getFqNameToImport();
if (fqn != null && fqn.equals("android.R")) { //$NON-NLS-1$
Location location = UastAndroidUtils.getLocation(node);
Location location = mContext.getLocation(node);
mContext.report(ISSUE, node, location,
"Don't include `android.R` here; use a fully qualified name for "
+ "each usage instead");
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.intellij.codeInspection.HTMLComposer;
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.SdkConstants;
@@ -139,14 +123,14 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
scope = Scope.RESOURCE_FILE_SCOPE;
}
} else if (fileType == StdFileTypes.JAVA) {
scope = Scope.JAVA_FILE_SCOPE;
scope = Scope.SOURCE_FILE_SCOPE;
} else if (name.equals(OLD_PROGUARD_FILE) || name.equals(FN_PROJECT_PROGUARD_FILE)) {
scope = EnumSet.of(Scope.PROGUARD_FILE);
} else if (fileType == StdFileTypes.PROPERTIES) {
scope = Scope.PROPERTY_SCOPE;
} else {
if (UastConverterUtils.isFileSupported(client.getLanguagePlugins(), mainFile.getName())) {
scope = Scope.JAVA_FILE_SCOPE;
scope = Scope.SOURCE_FILE_SCOPE;
} else {
// #collectionInformation above should have prevented this
assert false;
@@ -178,8 +162,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
final IssueRegistry fullRegistry = new IntellijLintIssueRegistry();
for (Issue issue : fullRegistry.getIssues()) {
final String inspectionShortName = org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
.getInspectionShortNameByIssue(project, issue);
final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(project, issue);
if (inspectionShortName == null) {
continue;
}
@@ -216,12 +199,12 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
continue;
}
final Pair<org.jetbrains.android.inspections.klint.AndroidLintInspectionBase, HighlightDisplayLevel> pair =
final Pair<AndroidLintInspectionBase, HighlightDisplayLevel> pair =
AndroidLintUtil.getHighlighLevelAndInspection(project, issue, file);
if (pair == null) {
continue;
}
final org.jetbrains.android.inspections.klint.AndroidLintInspectionBase inspection = pair.getFirst();
final AndroidLintInspectionBase inspection = pair.getFirst();
HighlightDisplayLevel displayLevel = pair.getSecond();
if (inspection != null) {
@@ -259,7 +242,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
if (action.isAvailable(project, startElement)) {
ProblemHighlightType type = annotation.getHighlightType();
annotation.registerFix(action, null, key, InspectionManager.getInstance(project).createProblemDescriptor(
startElement, endElement, message, type, true, LocalQuickFix.EMPTY_ARRAY));
startElement, endElement, message, type, true, LocalQuickFix.EMPTY_ARRAY));
}
}
}
@@ -295,10 +278,10 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
if (createHtmlAnnotation != null) {
// Based on LocalInspectionsPass#createHighlightInfo
String link = " <a "
+ "href=\"#lint/" + issue.getId() + "\""
+ (UIUtil.isUnderDarcula() ? " color=\"7AB4C9\" " : "")
+ ">" + DaemonBundle.message("inspection.extended.description")
+ "</a> " + getShowMoreShortCut();
+ "href=\"#lint/" + issue.getId() + "\""
+ (UIUtil.isUnderDarcula() ? " color=\"7AB4C9\" " : "")
+ ">" + DaemonBundle.message("inspection.extended.description")
+ "</a> " + getShowMoreShortCut();
String tooltip = XmlStringUtil.wrapInHtml(RAW.convertTo(message, HTML) + link);
try {
@@ -333,7 +316,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
ourCreateHtmlAnnotationMethodFailed = true;
try {
ourCreateHtmlAnnotationMethod = AnnotationHolder.class.getMethod("createAnnotation", HighlightSeverity.class,
TextRange.class, String.class, String.class);
TextRange.class, String.class, String.class);
}
catch (NoSuchMethodException ignore) {
}
@@ -440,7 +423,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
}
private static class MyEditInspectionToolsSettingsAction extends CustomEditInspectionToolsSettingsAction {
private MyEditInspectionToolsSettingsAction(@NotNull HighlightDisplayKey key, @NotNull final org.jetbrains.android.inspections.klint.AndroidLintInspectionBase inspection) {
private MyEditInspectionToolsSettingsAction(@NotNull HighlightDisplayKey key, @NotNull final AndroidLintInspectionBase inspection) {
super(key, new Computable<String>() {
@Override
public String compute() {
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.tools.klint.client.api.LintDriver;
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.annotations.concurrency.GuardedBy;
@@ -215,8 +199,7 @@ public abstract class AndroidLintInspectionBase extends GlobalInspectionTool {
if (AndroidLintExternalAnnotator.INCLUDE_IDEA_SUPPRESS_ACTIONS) {
final List<SuppressQuickFix> result = new ArrayList<SuppressQuickFix>();
result.add(suppressLintQuickFix);
result.addAll(Arrays.asList(
BatchSuppressManager.SERVICE.getInstance().createBatchSuppressActions(HighlightDisplayKey.find(getShortName()))));
result.addAll(Arrays.asList(BatchSuppressManager.SERVICE.getInstance().createBatchSuppressActions(HighlightDisplayKey.find(getShortName()))));
result.addAll(Arrays.asList(new XmlSuppressableInspectionTool.SuppressTagStatic(getShortName()),
new XmlSuppressableInspectionTool.SuppressForFile(getShortName())));
return result.toArray(new SuppressQuickFix[result.size()]);
@@ -250,13 +233,7 @@ public abstract class AndroidLintInspectionBase extends GlobalInspectionTool {
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement myElement = descriptor.getPsiElement();
PsiFile file = PsiTreeUtil.getParentOfType(myElement, PsiFile.class, false);
if (file != null) {
new SuppressLintIntentionAction(myIssue.getId(), myElement).invoke(project, null, file);
}
}
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {}
}
@TestOnly
@@ -454,9 +431,9 @@ public abstract class AndroidLintInspectionBase extends GlobalInspectionTool {
}
/**
* A {@link ProblemDescriptor} for image and directory files. This is
* A {@link com.intellij.codeInspection.ProblemDescriptor} for image and directory files. This is
* necessary because the {@link InspectionManager}'s createProblemDescriptor methods
* all use {@link ProblemDescriptorBase} where in the constructor
* all use {@link com.intellij.codeInspection.ProblemDescriptorBase} where in the constructor
* it insists that the start and end {@link PsiElement} instances must have a valid
* <b>text</b> range, which does not apply for images.
* <p>
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.tools.klint.checks.*;
@@ -67,12 +51,6 @@ public class AndroidLintInspectionToolProvider {
}
}
public static class AndroidKLintFullBackupContentInspection extends AndroidLintInspectionBase {
public AndroidKLintFullBackupContentInspection() {
super(AndroidBundle.message("android.lint.inspections.full.backup.content"), FullBackupContentDetector.ISSUE);
}
}
public static class AndroidKLintGetInstanceInspection extends AndroidLintInspectionBase {
public AndroidKLintGetInstanceInspection() {
super(AndroidBundle.message("android.lint.inspections.get.instance"), CipherGetInstanceDetector.ISSUE);
@@ -139,12 +117,6 @@ public class AndroidLintInspectionToolProvider {
}
}
public static class AndroidKLintAppCompatResourceInspection extends AndroidLintInspectionBase {
public AndroidKLintAppCompatResourceInspection() {
super(AndroidBundle.message("android.lint.inspections.app.compat.resource"), AppCompatResourceDetector.ISSUE);
}
}
public static class AndroidKLintAssertInspection extends AndroidLintInspectionBase {
public AndroidKLintAssertInspection() {
super(AndroidBundle.message("android.lint.inspections.assert"), AssertDetector.ISSUE);
@@ -271,12 +243,6 @@ public class AndroidLintInspectionToolProvider {
}
}
public static class AndroidKLintNfcTechWhitespaceInspection extends AndroidLintInspectionBase {
public AndroidKLintNfcTechWhitespaceInspection() {
super(AndroidBundle.message("android.lint.inspections.nfc.tech.whitespace"), NfcTechListDetector.ISSUE);
}
}
public static class AndroidKLintExportedServiceInspection extends AndroidLintInspectionBase {
public AndroidKLintExportedServiceInspection() {
super(AndroidBundle.message("android.lint.inspections.exported.service"), SecurityDetector.EXPORTED_SERVICE);
@@ -441,12 +407,6 @@ public class AndroidLintInspectionToolProvider {
}
}
public static class AndroidKLintMenuTitleInspection extends AndroidLintInspectionBase {
public AndroidKLintMenuTitleInspection() {
super(AndroidBundle.message("android.lint.inspections.menu.title"), TitleDetector.ISSUE);
}
}
public static class AndroidKLintOverrideAbstractInspection extends AndroidLintInspectionBase {
public AndroidKLintOverrideAbstractInspection() {
super(AndroidBundle.message("android.lint.inspections.override.abstract"), OverrideConcreteDetector.ISSUE);
@@ -459,12 +419,6 @@ public class AndroidLintInspectionToolProvider {
}
}
public static class AndroidKLintRegisteredInspection extends AndroidLintInspectionBase {
public AndroidKLintRegisteredInspection() {
super(AndroidBundle.message("android.lint.inspections.registered"), RegistrationDetector.ISSUE);
}
}
public static class AndroidKLintRequiredSizeInspection extends AndroidLintInspectionBase {
public AndroidKLintRequiredSizeInspection() {
super(AndroidBundle.message("android.lint.inspections.required.size"), RequiredAttributeDetector.ISSUE);
@@ -1,32 +1,16 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
public interface AndroidLintQuickFix {
AndroidLintQuickFix[] EMPTY_ARRAY = new AndroidLintQuickFix[0];
AndroidLintQuickFix[] EMPTY_ARRAY = new AndroidLintQuickFix[0];
void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull AndroidQuickfixContexts.Context context);
void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull AndroidQuickfixContexts.Context context);
boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull AndroidQuickfixContexts.ContextType contextType);
boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull AndroidQuickfixContexts.ContextType contextType);
@NotNull
String getName();
@NotNull
String getName();
}
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.tools.klint.detector.api.Issue;
@@ -1,35 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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.
*/
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint
import com.android.builder.model.AndroidProject
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.intellij.openapi.editor.Editor;
@@ -57,13 +57,13 @@ class DomPsiParser extends XmlParser {
@Override
public int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node) {
TextRange textRange = org.jetbrains.android.inspections.klint.DomPsiConverter.getTextRange(node);
TextRange textRange = DomPsiConverter.getTextRange(node);
return textRange.getStartOffset();
}
@Override
public int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node) {
TextRange textRange = org.jetbrains.android.inspections.klint.DomPsiConverter.getTextRange(node);
TextRange textRange = DomPsiConverter.getTextRange(node);
return textRange.getEndOffset();
}
@@ -92,7 +92,7 @@ class DomPsiParser extends XmlParser {
XmlFile xmlFile = (XmlFile)psiFile;
try {
return org.jetbrains.android.inspections.klint.DomPsiConverter.convert(xmlFile);
return DomPsiConverter.convert(xmlFile);
} catch (Throwable t) {
myClient.log(t, "Failed converting PSI parse tree to DOM for file %1$s",
context.file.getPath());
@@ -103,7 +103,7 @@ class DomPsiParser extends XmlParser {
@NonNull
@Override
public Location getLocation(@NonNull XmlContext context, @NonNull Node node) {
TextRange textRange = org.jetbrains.android.inspections.klint.DomPsiConverter.getTextRange(node);
TextRange textRange = DomPsiConverter.getTextRange(node);
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(context.file, start, end);
@@ -112,7 +112,7 @@ class DomPsiParser extends XmlParser {
@NonNull
@Override
public Location getLocation(@NonNull XmlContext context, @NonNull Node node, int startDelta, int endDelta) {
TextRange textRange = org.jetbrains.android.inspections.klint.DomPsiConverter.getTextRange(node);
TextRange textRange = DomPsiConverter.getTextRange(node);
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset() + startDelta);
Position end = new DefaultPosition(-1, -1, textRange.getStartOffset() + endDelta);
return Location.create(context.file, start, end);
@@ -121,7 +121,7 @@ class DomPsiParser extends XmlParser {
@NonNull
@Override
public Location getNameLocation(@NonNull XmlContext context, @NonNull Node node) {
TextRange textRange = org.jetbrains.android.inspections.klint.DomPsiConverter.getTextNameRange(node);
TextRange textRange = DomPsiConverter.getTextNameRange(node);
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(context.file, start, end);
@@ -130,7 +130,7 @@ class DomPsiParser extends XmlParser {
@NonNull
@Override
public Location getValueLocation(@NonNull XmlContext context, @NonNull Attr node) {
TextRange textRange = org.jetbrains.android.inspections.klint.DomPsiConverter.getTextValueRange(node);
TextRange textRange = DomPsiConverter.getTextValueRange(node);
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(context.file, start, end);
@@ -163,7 +163,7 @@ class DomPsiParser extends XmlParser {
}
});
}
TextRange textRange = org.jetbrains.android.inspections.klint.DomPsiConverter.getTextRange(myNode);
TextRange textRange = DomPsiConverter.getTextRange(myNode);
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(myFile, start, end);
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.annotations.NonNull;
@@ -30,6 +14,13 @@ import com.android.tools.idea.rendering.LocalResourceRepository;
import com.android.tools.idea.sdk.IdeSdks;
import com.android.tools.klint.client.api.*;
import com.android.tools.klint.checks.ApiLookup;
import com.android.tools.klint.detector.api.Context;
import com.android.tools.klint.detector.api.DefaultPosition;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Position;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.TextFormat;
import com.intellij.analysis.AnalysisScope;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
@@ -77,10 +68,10 @@ import java.util.Map;
import static com.android.tools.klint.detector.api.TextFormat.RAW;
/**
* Implementation of the {@linkplain com.android.tools.klint.client.api.LintClient} API for executing lint within the IDE:
* Implementation of the {@linkplain LintClient} API for executing lint within the IDE:
* reading files, reporting issues, logging errors, etc.
*/
public class IntellijLintClient extends com.android.tools.klint.client.api.LintClient implements Disposable {
public class IntellijLintClient extends LintClient implements Disposable {
protected static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.IntellijLintClient");
@NonNull protected Project myProject;
@@ -96,9 +87,9 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
/** Creates a lint client for batch inspections */
public static IntellijLintClient forBatch(@NotNull Project project,
@NotNull Map<com.android.tools.klint.detector.api.Issue, Map<File, List<org.jetbrains.android.inspections.klint.ProblemData>>> problemMap,
@NotNull Map<Issue, Map<File, List<ProblemData>>> problemMap,
@NotNull AnalysisScope scope,
@NotNull List<com.android.tools.klint.detector.api.Issue> issues) {
@NotNull List<Issue> issues) {
return new BatchLintClient(project, problemMap, scope, issues);
}
@@ -139,7 +130,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
}
@Override
public com.android.tools.klint.client.api.Configuration getConfiguration(@NonNull com.android.tools.klint.detector.api.Project project) {
public Configuration getConfiguration(@NonNull com.android.tools.klint.detector.api.Project project) {
if (project.isGradleProject() && project.isAndroidProject() && !project.isLibrary()) {
AndroidProject model = project.getGradleProjectModel();
if (model != null) {
@@ -147,30 +138,30 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
LintOptions lintOptions = model.getLintOptions();
final Map<String, Integer> overrides = lintOptions.getSeverityOverrides();
if (overrides != null && !overrides.isEmpty()) {
return new com.android.tools.klint.client.api.DefaultConfiguration(this, project, null) {
return new DefaultConfiguration(this, project, null) {
@NonNull
@Override
public com.android.tools.klint.detector.api.Severity getSeverity(@NonNull com.android.tools.klint.detector.api.Issue issue) {
public Severity getSeverity(@NonNull Issue issue) {
Integer severity = overrides.get(issue.getId());
if (severity != null) {
switch (severity.intValue()) {
case LintOptions.SEVERITY_FATAL:
return com.android.tools.klint.detector.api.Severity.FATAL;
return Severity.FATAL;
case LintOptions.SEVERITY_ERROR:
return com.android.tools.klint.detector.api.Severity.ERROR;
return Severity.ERROR;
case LintOptions.SEVERITY_WARNING:
return com.android.tools.klint.detector.api.Severity.WARNING;
return Severity.WARNING;
case LintOptions.SEVERITY_INFORMATIONAL:
return com.android.tools.klint.detector.api.Severity.INFORMATIONAL;
return Severity.INFORMATIONAL;
case LintOptions.SEVERITY_IGNORE:
default:
return com.android.tools.klint.detector.api.Severity.IGNORE;
return Severity.IGNORE;
}
}
// This is a LIST lookup. I should make this faster!
if (!getIssues().contains(issue)) {
return com.android.tools.klint.detector.api.Severity.IGNORE;
return Severity.IGNORE;
}
return super.getSeverity(issue);
@@ -182,25 +173,25 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
}
}
}
return new com.android.tools.klint.client.api.DefaultConfiguration(this, project, null) {
return new DefaultConfiguration(this, project, null) {
@Override
public boolean isEnabled(@NonNull com.android.tools.klint.detector.api.Issue issue) {
public boolean isEnabled(@NonNull Issue issue) {
return getIssues().contains(issue) && super.isEnabled(issue);
}
};
}
@Override
public void report(@NonNull com.android.tools.klint.detector.api.Context context,
@NonNull com.android.tools.klint.detector.api.Issue issue,
@NonNull com.android.tools.klint.detector.api.Severity severity,
@Nullable com.android.tools.klint.detector.api.Location location,
public void report(@NonNull Context context,
@NonNull Issue issue,
@NonNull Severity severity,
@Nullable Location location,
@NonNull String message,
@NonNull com.android.tools.klint.detector.api.TextFormat format) {
@NonNull TextFormat format) {
assert false : message;
}
@NonNull protected List<com.android.tools.klint.detector.api.Issue> getIssues() {
@NonNull protected List<Issue> getIssues() {
return Collections.emptyList();
}
@@ -214,9 +205,9 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
* linked location, and so on.This is necessary since IntelliJ problems don't have secondary locations; instead, we create one
* problem for each location associated with the lint error.
*/
protected void reportSecondary(@NonNull com.android.tools.klint.detector.api.Context context, @NonNull com.android.tools.klint.detector.api.Issue issue, @NonNull com.android.tools.klint.detector.api.Severity severity, @NonNull com.android.tools.klint.detector.api.Location location,
@NonNull String message, @NonNull com.android.tools.klint.detector.api.TextFormat format) {
com.android.tools.klint.detector.api.Location secondary = location.getSecondary();
protected void reportSecondary(@NonNull Context context, @NonNull Issue issue, @NonNull Severity severity, @NonNull Location location,
@NonNull String message, @NonNull TextFormat format) {
Location secondary = location.getSecondary();
if (secondary != null) {
if (secondary.getMessage() != null) {
message = message + " (" + secondary.getMessage() + ")";
@@ -226,14 +217,14 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
}
@Override
public void log(@NonNull com.android.tools.klint.detector.api.Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) {
if (severity == com.android.tools.klint.detector.api.Severity.ERROR || severity == com.android.tools.klint.detector.api.Severity.FATAL) {
public void log(@NonNull Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) {
if (severity == Severity.ERROR || severity == Severity.FATAL) {
if (format != null) {
LOG.error(String.format(format, args), exception);
} else if (exception != null) {
LOG.error(exception);
}
} else if (severity == com.android.tools.klint.detector.api.Severity.WARNING) {
} else if (severity == Severity.WARNING) {
if (format != null) {
LOG.warn(String.format(format, args), exception);
} else if (exception != null) {
@@ -249,7 +240,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
}
@Override
public com.android.tools.klint.client.api.XmlParser getXmlParser() {
public XmlParser getXmlParser() {
return new DomPsiParser(this);
}
@@ -453,35 +444,35 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
@NonNull
@Override
protected List<com.android.tools.klint.detector.api.Issue> getIssues() {
protected List<Issue> getIssues() {
return myState.getIssues();
}
@Override
public void report(@NonNull com.android.tools.klint.detector.api.Context context,
@NonNull com.android.tools.klint.detector.api.Issue issue,
@NonNull com.android.tools.klint.detector.api.Severity severity,
@Nullable com.android.tools.klint.detector.api.Location location,
public void report(@NonNull Context context,
@NonNull Issue issue,
@NonNull Severity severity,
@Nullable Location location,
@NonNull String message,
@NonNull com.android.tools.klint.detector.api.TextFormat format) {
@NonNull TextFormat format) {
if (location != null) {
final File file = location.getFile();
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
if (myState.getMainFile().equals(vFile)) {
final com.android.tools.klint.detector.api.Position start = location.getStart();
final com.android.tools.klint.detector.api.Position end = location.getEnd();
final Position start = location.getStart();
final Position end = location.getEnd();
final TextRange textRange = start != null && end != null && start.getOffset() <= end.getOffset()
? new TextRange(start.getOffset(), end.getOffset())
: TextRange.EMPTY_RANGE;
com.android.tools.klint.detector.api.Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null;
Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null;
message = format.convertTo(message, RAW);
myState.getProblems().add(new org.jetbrains.android.inspections.klint.ProblemData(issue, message, textRange, configuredSeverity));
myState.getProblems().add(new ProblemData(issue, message, textRange, configuredSeverity));
}
com.android.tools.klint.detector.api.Location secondary = location.getSecondary();
Location secondary = location.getSecondary();
if (secondary != null && myState.getMainFile().equals(LocalFileSystem.getInstance().findFileByIoFile(secondary.getFile()))) {
reportSecondary(context, issue, severity, location, message, format);
}
@@ -560,7 +551,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
public List<File> getResourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) {
AndroidFacet facet = AndroidFacet.getInstance(myState.getModule());
if (facet != null) {
return org.jetbrains.android.inspections.klint.IntellijLintUtils.getResourceDirectories(facet);
return IntellijLintUtils.getResourceDirectories(facet);
}
return super.getResourceFolders(project);
}
@@ -568,14 +559,14 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
/** Lint client used for batch operations */
private static class BatchLintClient extends IntellijLintClient {
private final Map<com.android.tools.klint.detector.api.Issue, Map<File, List<org.jetbrains.android.inspections.klint.ProblemData>>> myProblemMap;
private final Map<Issue, Map<File, List<ProblemData>>> myProblemMap;
private final AnalysisScope myScope;
private final List<com.android.tools.klint.detector.api.Issue> myIssues;
private final List<Issue> myIssues;
public BatchLintClient(@NotNull Project project,
@NotNull Map<com.android.tools.klint.detector.api.Issue, Map<File, List<org.jetbrains.android.inspections.klint.ProblemData>>> problemMap,
@NotNull Map<Issue, Map<File, List<ProblemData>>> problemMap,
@NotNull AnalysisScope scope,
@NotNull List<com.android.tools.klint.detector.api.Issue> issues) {
@NotNull List<Issue> issues) {
super(project);
myProblemMap = problemMap;
myScope = scope;
@@ -591,17 +582,17 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
@NonNull
@Override
protected List<com.android.tools.klint.detector.api.Issue> getIssues() {
protected List<Issue> getIssues() {
return myIssues;
}
@Override
public void report(@NonNull com.android.tools.klint.detector.api.Context context,
@NonNull com.android.tools.klint.detector.api.Issue issue,
@NonNull com.android.tools.klint.detector.api.Severity severity,
@Nullable com.android.tools.klint.detector.api.Location location,
public void report(@NonNull Context context,
@NonNull Issue issue,
@NonNull Severity severity,
@Nullable Location location,
@NonNull String message,
@NonNull com.android.tools.klint.detector.api.TextFormat format) {
@NonNull TextFormat format) {
VirtualFile vFile = null;
File file = null;
@@ -642,31 +633,31 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
if (inScope) {
file = new File(PathUtil.getCanonicalPath(file.getPath()));
Map<File, List<org.jetbrains.android.inspections.klint.ProblemData>> file2ProblemList = myProblemMap.get(issue);
Map<File, List<ProblemData>> file2ProblemList = myProblemMap.get(issue);
if (file2ProblemList == null) {
file2ProblemList = new HashMap<File, List<org.jetbrains.android.inspections.klint.ProblemData>>();
file2ProblemList = new HashMap<File, List<ProblemData>>();
myProblemMap.put(issue, file2ProblemList);
}
List<org.jetbrains.android.inspections.klint.ProblemData> problemList = file2ProblemList.get(file);
List<ProblemData> problemList = file2ProblemList.get(file);
if (problemList == null) {
problemList = new ArrayList<org.jetbrains.android.inspections.klint.ProblemData>();
problemList = new ArrayList<ProblemData>();
file2ProblemList.put(file, problemList);
}
TextRange textRange = TextRange.EMPTY_RANGE;
if (location != null) {
final com.android.tools.klint.detector.api.Position start = location.getStart();
final com.android.tools.klint.detector.api.Position end = location.getEnd();
final Position start = location.getStart();
final Position end = location.getEnd();
if (start != null && end != null && start.getOffset() <= end.getOffset()) {
textRange = new TextRange(start.getOffset(), end.getOffset());
}
}
com.android.tools.klint.detector.api.Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null;
Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null;
message = format.convertTo(message, RAW);
problemList.add(new org.jetbrains.android.inspections.klint.ProblemData(issue, message, textRange, configuredSeverity));
problemList.add(new ProblemData(issue, message, textRange, configuredSeverity));
if (location != null && location.getSecondary() != null) {
reportSecondary(context, issue, severity, location, message, format);
@@ -697,7 +688,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
if (module != null) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
return org.jetbrains.android.inspections.klint.IntellijLintUtils.getResourceDirectories(facet);
return IntellijLintUtils.getResourceDirectories(facet);
}
}
return super.getResourceFolders(project);
@@ -736,7 +727,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
@NonNull
@Override
public com.android.tools.klint.detector.api.Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
XmlTag tag = LocalResourceRepository.getItemTag(myProject, item);
if (tag != null) {
ResourceFile source = item.getSource();
@@ -762,7 +753,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
return super.getResourceVisibilityProvider();
}
private static class LocationHandle implements com.android.tools.klint.detector.api.Location.Handle, Computable<com.android.tools.klint.detector.api.Location> {
private static class LocationHandle implements Location.Handle, Computable<Location> {
private final File myFile;
private final XmlElement myNode;
private Object myClientData;
@@ -774,7 +765,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
@NonNull
@Override
public com.android.tools.klint.detector.api.Location resolve() {
public Location resolve() {
if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
return ApplicationManager.getApplication().runReadAction(this);
}
@@ -791,13 +782,13 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
}
}
com.android.tools.klint.detector.api.Position start = new com.android.tools.klint.detector.api.DefaultPosition(-1, -1, textRange.getStartOffset());
com.android.tools.klint.detector.api.Position end = new com.android.tools.klint.detector.api.DefaultPosition(-1, -1, textRange.getEndOffset());
return com.android.tools.klint.detector.api.Location.create(myFile, start, end);
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(myFile, start, end);
}
@Override
public com.android.tools.klint.detector.api.Location compute() {
public Location compute() {
return resolve();
}
@@ -49,16 +49,12 @@ public class IntellijLintIssueRegistry extends BuiltinIssueRegistry {
Implementation implementation = issue.getImplementation();
EnumSet<Scope> scope = implementation.getScope();
Class<? extends Detector> detectorClass = implementation.getDetectorClass();
if (detectorClass == RegistrationDetector.class) {
issue.setImplementation(IntellijRegistrationDetector.IMPLEMENTATION);
} else if (detectorClass == ViewTypeDetector.class) {
issue.setImplementation(IntellijViewTypeDetector.IMPLEMENTATION);
} else if (detectorClass == SupportAnnotationDetector.class) {
if (detectorClass == SupportAnnotationDetector.class) {
// Handled by the ResourceTypeInspection
continue;
} else if (scope.contains(Scope.CLASS_FILE) ||
scope.contains(Scope.ALL_CLASS_FILES) ||
scope.contains(Scope.JAVA_LIBRARIES)) {
scope.contains(Scope.ALL_CLASS_FILES) ||
scope.contains(Scope.JAVA_LIBRARIES)) {
//noinspection ConstantConditions
assert !IntellijLintProject.SUPPORT_CLASS_FILES; // When enabled, adjust this to include class detector based issues
@@ -73,8 +73,8 @@ public class IntellijLintRequest extends LintRequest {
mScope = Scope.infer(projects);
//noinspection ConstantConditions
if (!org.jetbrains.android.inspections.klint.IntellijLintProject.SUPPORT_CLASS_FILES && (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.ALL_CLASS_FILES)
|| mScope.contains(Scope.JAVA_LIBRARIES))) {
if (!IntellijLintProject.SUPPORT_CLASS_FILES && (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.ALL_CLASS_FILES)
|| mScope.contains(Scope.JAVA_LIBRARIES))) {
mScope = EnumSet.copyOf(mScope); // make mutable
// Can't run class file based checks
mScope.remove(Scope.CLASS_FILE);
@@ -93,7 +93,7 @@ public class IntellijLintRequest extends LintRequest {
if (mProjects == null) {
if (myIncremental && myFileList != null && myFileList.size() == 1 && myModules.size() == 1) {
Pair<com.android.tools.klint.detector.api.Project, com.android.tools.klint.detector.api.Project> pair =
org.jetbrains.android.inspections.klint.IntellijLintProject.createForSingleFile(mLintClient, myFileList.get(0), myModules.get(0));
IntellijLintProject.createForSingleFile(mLintClient, myFileList.get(0), myModules.get(0));
mProjects = pair.first != null ? Collections.singletonList(pair.first)
: Collections.<com.android.tools.klint.detector.api.Project>emptyList();
myMainProject = pair.second;
@@ -102,7 +102,7 @@ public class IntellijLintRequest extends LintRequest {
// and add projects for the gradle libraries and set error reporting to
// false on those
//mProjects = computeProjects()
mProjects = org.jetbrains.android.inspections.klint.IntellijLintProject.create(mLintClient, myFileList, myModules.toArray(new Module[myModules.size()]));
mProjects = IntellijLintProject.create(mLintClient, myFileList, myModules.toArray(new Module[myModules.size()]));
} else {
mProjects = super.getProjects();
}
@@ -20,6 +20,12 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.builder.model.SourceProvider;
import com.android.tools.klint.client.api.LintRequest;
import com.android.tools.klint.detector.api.ClassContext;
import com.android.tools.klint.detector.api.Context;
import com.android.tools.klint.detector.api.DefaultPosition;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Position;
import com.google.common.base.Splitter;
import com.intellij.debugger.engine.JVMNameUtil;
import com.intellij.facet.Facet;
@@ -36,9 +42,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
@@ -49,9 +53,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
import static com.android.SdkConstants.SUPPRESS_ALL;
/**
* Common utilities for handling lint within IntelliJ
* TODO: Merge with {@link AndroidLintUtil}
@@ -60,11 +61,6 @@ public class IntellijLintUtils {
private IntellijLintUtils() {
}
@NonNls
public static final String SUPPRESS_LINT_FQCN = "android.annotation.SuppressLint";
@NonNls
public static final String SUPPRESS_WARNINGS_FQCN = "java.lang.SuppressWarnings";
private static final ProjectSystemId GRADLE_ID = new ProjectSystemId("GRADLE");
/**
@@ -75,7 +71,7 @@ public class IntellijLintUtils {
* @return the location of the given element
*/
@NonNull
public static com.android.tools.klint.detector.api.Location getLocation(@NonNull File file, @NonNull PsiElement element) {
public static Location getLocation(@NonNull File file, @NonNull PsiElement element) {
//noinspection ConstantConditions
assert element.getContainingFile().getVirtualFile() == null
|| FileUtil.filesEqual(VfsUtilCore.virtualToIoFile(element.getContainingFile().getVirtualFile()), file);
@@ -90,19 +86,19 @@ public class IntellijLintUtils {
}
TextRange textRange = element.getTextRange();
com.android.tools.klint.detector.api.Position start = new com.android.tools.klint.detector.api.DefaultPosition(-1, -1, textRange.getStartOffset());
com.android.tools.klint.detector.api.Position end = new com.android.tools.klint.detector.api.DefaultPosition(-1, -1, textRange.getEndOffset());
return com.android.tools.klint.detector.api.Location.create(file, start, end);
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(file, start, end);
}
/**
* Returns the {@link PsiFile} associated with a given lint {@link com.android.tools.klint.detector.api.Context}
* Returns the {@link PsiFile} associated with a given lint {@link Context}
*
* @param context the context to look up the file for
* @return the corresponding {@link PsiFile}, or null
*/
@Nullable
public static PsiFile getPsiFile(@NonNull com.android.tools.klint.detector.api.Context context) {
public static PsiFile getPsiFile(@NonNull Context context) {
VirtualFile file = VfsUtil.findFileByIoFile(context.file, false);
if (file == null) {
return null;
@@ -115,106 +111,17 @@ public class IntellijLintUtils {
return PsiManager.getInstance(project).findFile(file);
}
public static boolean isSuppressed(@NonNull UElement element, @NonNull UFile file, @NonNull com.android.tools.klint.detector.api.Issue issue) {
//TODO
return true;
}
/**
* Returns true if the given issue is suppressed at the given element within the given file
*
* @param element the element to check
* @param file the file containing the element
* @param issue the issue to check
* @return true if the given issue is suppressed
*/
public static boolean isSuppressed(@NonNull PsiElement element, @NonNull PsiFile file, @NonNull com.android.tools.klint.detector.api.Issue issue) {
// Search upwards for suppress lint and suppress warnings annotations
// Search upwards for target api annotations
while (element != null && element != file) { // otherwise it will keep going into directories!
if (element instanceof PsiModifierListOwner) {
PsiModifierListOwner owner = (PsiModifierListOwner)element;
PsiModifierList modifierList = owner.getModifierList();
if (modifierList != null) {
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
String fqcn = annotation.getQualifiedName();
if (fqcn.equals(SUPPRESS_LINT_FQCN) || fqcn.equals(SUPPRESS_WARNINGS_FQCN)) {
PsiAnnotationParameterList parameterList = annotation.getParameterList();
for (PsiNameValuePair pair : parameterList.getAttributes()) {
PsiAnnotationMemberValue v = pair.getValue();
String text = v.getText().trim(); // UGH! Find better way to access value!
if (text.isEmpty()) {
continue;
}
if (v instanceof PsiLiteral) {
PsiLiteral literal = (PsiLiteral)v;
Object value = literal.getValue();
if (value instanceof String) {
text = (String) value;
}
} else if (v instanceof PsiArrayInitializerMemberValue) {
PsiArrayInitializerMemberValue mv = (PsiArrayInitializerMemberValue)v;
for (PsiAnnotationMemberValue mmv : mv.getInitializers()) {
if (mmv instanceof PsiLiteral) {
PsiLiteral literal = (PsiLiteral) mmv;
Object value = literal.getValue();
if (value instanceof String) {
text = (String) value;
break;
}
}
}
}
if (text != null) {
for (String id : Splitter.on(',').trimResults().split(text)) {
if (id.equals(issue.getId()) || id.equals(SUPPRESS_ALL)) {
return true;
}
}
}
}
}
}
}
}
element = element.getParent();
}
public static boolean isSuppressed(@NonNull UElement element, @NonNull UFile file, @NonNull Issue issue) {
return false;
}
/** Returns the internal method name */
@NonNull
public static String getInternalMethodName(@NonNull PsiMethod method) {
if (method.isConstructor()) {
return SdkConstants.CONSTRUCTOR_NAME;
}
else {
return method.getName();
}
}
@Nullable
public static PsiElement getCallName(@NonNull PsiCallExpression expression) {
PsiElement firstChild = expression.getFirstChild();
if (firstChild != null) {
PsiElement lastChild = firstChild.getLastChild();
if (lastChild instanceof PsiIdentifier) {
return lastChild;
}
}
return null;
}
/**
* Computes the internal class name of the given class.
* For example, for PsiClass foo.bar.Foo.Bar it returns foo/bar/Foo$Bar.
*
* @param psiClass the class to look up the internal name for
* @return the internal class name
* @see com.android.tools.klint.detector.api.ClassContext#getInternalName(String)
* @see ClassContext#getInternalName(String)
*/
@Nullable
public static String getInternalName(@NonNull PsiClass psiClass) {
@@ -232,13 +139,13 @@ public class IntellijLintUtils {
if (sig == null) {
String qualifiedName = psiClass.getQualifiedName();
if (qualifiedName != null) {
return com.android.tools.klint.detector.api.ClassContext.getInternalName(qualifiedName);
return ClassContext.getInternalName(qualifiedName);
}
return null;
} else if (sig.indexOf('.') != -1) {
// Workaround -- ClassUtil doesn't treat this correctly!
// .replace('.', '/');
sig = com.android.tools.klint.detector.api.ClassContext.getInternalName(sig);
sig = ClassContext.getInternalName(sig);
}
return sig;
}
@@ -251,134 +158,6 @@ public class IntellijLintUtils {
}
}
/**
* Computes the internal class name of the given class type.
* For example, for PsiClassType foo.bar.Foo.Bar it returns foo/bar/Foo$Bar.
*
* @param psiClassType the class type to look up the internal name for
* @return the internal class name
* @see com.android.tools.klint.detector.api.ClassContext#getInternalName(String)
*/
@Nullable
public static String getInternalName(@NonNull PsiClassType psiClassType) {
PsiClass resolved = psiClassType.resolve();
if (resolved != null) {
return getInternalName(resolved);
}
String className = psiClassType.getClassName();
if (className != null) {
return com.android.tools.klint.detector.api.ClassContext.getInternalName(className);
}
return null;
}
/**
* Computes the internal JVM description of the given method. This is in the same
* format as the ASM desc fields for methods; meaning that a method named foo which for example takes an
* int and a String and returns a void will have description {@code foo(ILjava/lang/String;):V}.
*
* @param method the method to look up the description for
* @param includeName whether the name should be included
* @param includeReturn whether the return type should be included
* @return the internal JVM description for this method
*/
@Nullable
public static String getInternalDescription(@NonNull PsiMethod method, boolean includeName, boolean includeReturn) {
assert !includeName; // not yet tested
assert !includeReturn; // not yet tested
StringBuilder signature = new StringBuilder();
if (includeName) {
if (method.isConstructor()) {
final PsiClass declaringClass = method.getContainingClass();
if (declaringClass != null) {
final PsiClass outerClass = declaringClass.getContainingClass();
if (outerClass != null) {
// declaring class is an inner class
if (!declaringClass.hasModifierProperty(PsiModifier.STATIC)) {
if (!appendJvmTypeName(signature, outerClass)) {
return null;
}
}
}
}
signature.append(CONSTRUCTOR_NAME);
} else {
signature.append(method.getName());
}
}
signature.append('(');
for (PsiParameter psiParameter : method.getParameterList().getParameters()) {
if (!appendJvmSignature(signature, psiParameter.getType())) {
return null;
}
}
signature.append(')');
if (includeReturn) {
if (!method.isConstructor()) {
if (!appendJvmSignature(signature, method.getReturnType())) {
return null;
}
}
else {
signature.append('V');
}
}
return signature.toString();
}
private static boolean appendJvmTypeName(@NonNull StringBuilder signature, @NonNull PsiClass outerClass) {
String className = getInternalName(outerClass);
if (className == null) {
return false;
}
signature.append('L').append(className.replace('.', '/')).append(';');
return true;
}
private static boolean appendJvmSignature(@NonNull StringBuilder buffer, @Nullable PsiType type) {
if (type == null) {
return false;
}
final PsiType psiType = TypeConversionUtil.erasure(type);
if (psiType instanceof PsiArrayType) {
buffer.append('[');
appendJvmSignature(buffer, ((PsiArrayType)psiType).getComponentType());
}
else if (psiType instanceof PsiClassType) {
PsiClass resolved = ((PsiClassType)psiType).resolve();
if (resolved == null) {
return false;
}
if (!appendJvmTypeName(buffer, resolved)) {
return false;
}
}
else if (psiType instanceof PsiPrimitiveType) {
buffer.append(JVMNameUtil.getPrimitiveSignature(psiType.getCanonicalText()));
}
else {
return false;
}
return true;
}
public static boolean isProjectReady(AndroidFacet facet) {
try {
return AndroidFacet.class.getMethod("getIdeaAndroidProject", AndroidFacet.class).invoke(facet) != null;
} catch (Exception e) {
try {
return AndroidFacet.class.getMethod("getAndroidModel", AndroidFacet.class).invoke(facet) != null;
} catch (Exception ignored) {}
}
return false;
}
public static AndroidModelFacade getModelFacade(AndroidFacet facet) {
return new AndroidModelFacade(facet);
}
@@ -1,125 +0,0 @@
/*
* Copyright (C) 2013 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 org.jetbrains.android.inspections.klint;
import com.android.tools.klint.checks.RegistrationDetector;
import com.android.tools.klint.detector.api.ClassContext;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Scope;
import com.intellij.openapi.application.ApplicationManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UFile;
import org.jetbrains.uast.UastModifier;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.check.UastAndroidContext;
import org.jetbrains.uast.check.UastAndroidUtils;
import org.jetbrains.uast.check.UastScanner;
import org.jetbrains.uast.kinds.UastClassKind;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Collection;
import java.util.EnumSet;
import static com.android.SdkConstants.*;
/**
* Intellij-specific version of the {@link RegistrationDetector} which uses the PSI structure
* to check classes.
* <p>
* <ul>
* <li>Unit tests, and compare to the bytecode based results</li>
* </ul>
*/
public class IntellijRegistrationDetector extends RegistrationDetector implements UastScanner {
static final Implementation IMPLEMENTATION = new Implementation(
IntellijRegistrationDetector.class,
EnumSet.of(Scope.MANIFEST, Scope.SOURCE_FILE));
@Override
public UastVisitor createUastVisitor(final UastAndroidContext context) {
return new UastVisitor() {
@Override
public boolean visitFile(@NotNull UFile file) {
check(context, file);
return true;
}
};
}
private void check(final UastAndroidContext context, final UFile file) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
for (UClass clz : file.getClasses()) {
check(context, clz);
}
}
});
}
private void check(UastAndroidContext context, UClass clz) {
for (UClass current = clz.getSuperClass(context); current != null; current = current.getSuperClass(context)) {
// Ignore abstract classes
if (clz.hasModifier(UastModifier.ABSTRACT) || clz.getKind() != UastClassKind.CLASS) {
continue;
}
String fqcn = current.getFqName();
if (fqcn == null) {
continue;
}
if (CLASS_ACTIVITY.equals(fqcn)
|| CLASS_SERVICE.equals(fqcn)
|| CLASS_CONTENTPROVIDER.equals(fqcn)) {
String internalName = clz.getInternalName();
if (internalName == null) {
internalName = IntellijLintUtils.getInternalName(clz);
}
if (internalName == null) {
continue;
}
String frameworkClass = ClassContext.getInternalName(fqcn);
Collection<String> registered = mManifestRegistrations != null ? mManifestRegistrations.get(frameworkClass) : null;
if (registered == null || !registered.contains(internalName)) {
report(context, clz, frameworkClass);
}
break;
}
}
for (UClass innerClass : clz.getNestedClasses()) {
check(context, innerClass);
}
}
private static void report(UastAndroidContext context, UClass clz, String internalName) {
// Unlike the superclass, we don't have to check that the tags are compatible;
// IDEA already checks that as part of its XML validation
if (IntellijLintUtils.isSuppressed(clz, UastUtils.getContainingFile(clz), ISSUE)) {
return;
}
String tag = classToTag(internalName);
Location location = UastAndroidUtils.getLocation(clz);
String fqcn = clz.getFqName();
if (fqcn == null) {
fqcn = clz.getName();
}
context.report(ISSUE, clz, location, String.format("The <%1$s> %2$s is not registered in the manifest", tag, fqcn));
}
}
@@ -30,8 +30,8 @@ import java.util.Collections;
public class IntellijViewTypeDetector extends ViewTypeDetector {
static final Implementation IMPLEMENTATION = new Implementation(
IntellijViewTypeDetector.class,
Scope.JAVA_FILE_SCOPE);
IntellijViewTypeDetector.class,
Scope.SOURCE_FILE_SCOPE);
@Nullable
@Override
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.tools.klint.detector.api.Issue;
@@ -1,19 +1,3 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.android.tools.klint.detector.api.Issue;
@@ -1,276 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.android.inspections.klint;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.intention.AddAnnotationFix;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.android.SdkConstants.*;
/** Intention for adding a {@code @SuppressLint} annotation on the given element for the given id */
public class SuppressLintIntentionAction implements IntentionAction, Iconable {
private static final String NO_INSPECTION_PREFIX = "//noinspection ";
private final String myId;
private final PsiElement myElement;
SuppressLintIntentionAction(String id, PsiElement element) {
myId = id;
myElement = element;
}
@Override
public Icon getIcon(@IconFlags int flags) {
return AllIcons.Actions.Cancel;
}
@NotNull
@Override
public String getText() {
String id = getLintId(myId);
final PsiFile file = PsiTreeUtil.getParentOfType(myElement, PsiFile.class);
if (file == null) {
return "";
} else if (file instanceof XmlFile) {
return AndroidBundle.message("android.lint.fix.suppress.lint.api.attr", id);
} else if (file instanceof PsiJavaFile) {
return AndroidBundle.message("android.lint.fix.suppress.lint.api.annotation", id);
} else {
return "";
}
}
@NotNull
@Override
public String getFamilyName() {
return getText();
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return true;
}
@Override
public void invoke(@NotNull Project project, @Nullable Editor editor, @NotNull PsiFile file) throws IncorrectOperationException {
if (file instanceof XmlFile) {
final XmlTag element = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
if (element == null) {
return;
}
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) {
return;
}
String lintId = getLintId(myId);
addSuppressAttribute(project, (XmlFile) file, element, lintId);
} else if (file instanceof PsiJavaFile) {
final PsiModifierListOwner container =
PsiTreeUtil.getParentOfType(myElement, PsiModifierListOwner.class);
if (container == null) {
return;
}
if (!FileModificationService.getInstance().preparePsiElementForWrite(container)) {
return;
}
final PsiModifierList modifierList = container.getModifierList();
if (modifierList != null) {
String lintId = getLintId(myId);
addSuppressAnnotation(project, container, container, lintId);
}
}
}
/**
* TODO: There is probably an existing utility method somewhere in IntelliJ for this;
* find it and inline. Possible candidate: {@link com.intellij.xml.XmlNamespaceHelper#insertNamespaceDeclaration}.
* See also code in {@link com.intellij.codeInsight.completion.XmlAttributeInsertHandler} for additional useful
* code such as code to pick a unique prefix, look up the prefix from the schema provider etc (which presumably would
* consult {@link org.jetbrains.android.AndroidXmlSchemaProvider}).
*
*/
@NotNull
public static String ensureNamespaceImported(@NotNull Project project, @NotNull XmlFile file, @NotNull String namespaceUri) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
final XmlTag rootTag = file.getRootTag();
assert rootTag != null;
final XmlElementFactory elementFactory = XmlElementFactory.getInstance(project);
String prefix = rootTag.getPrefixByNamespace(namespaceUri);
if (prefix != null) {
return prefix;
}
if (TOOLS_URI.equals(namespaceUri)) {
prefix = TOOLS_PREFIX;
} else if (ANDROID_URI.equals(namespaceUri)) {
prefix = ANDROID_NS_NAME;
} else {
prefix = APP_PREFIX;
}
if (rootTag.getAttribute(XMLNS_PREFIX + prefix) != null) {
String base = prefix;
for (int i = 2; ; i++) {
prefix = base + Integer.toString(i);
if (rootTag.getAttribute(XMLNS_PREFIX + prefix) == null) {
break;
}
}
}
String name = XMLNS_PREFIX + prefix;
final XmlAttribute xmlnsAttr = elementFactory.createXmlAttribute(name, namespaceUri);
final XmlAttribute[] attributes = rootTag.getAttributes();
XmlAttribute next = attributes.length > 0 ? attributes[0] : null;
for (XmlAttribute attribute : attributes) {
String attributeName = attribute.getName();
if (!attributeName.startsWith(XMLNS_PREFIX) || attributeName.compareTo(name) > 0) {
next = attribute;
break;
}
}
if (next != null) {
rootTag.addBefore(xmlnsAttr, next);
}
else {
rootTag.add(xmlnsAttr);
}
return prefix;
}
static String getLintId(String intentionId) {
String lintId = intentionId;
if (lintId.startsWith("AndroidLint")) {
lintId = lintId.substring("AndroidLint".length());
}
return lintId;
}
@Override
public boolean startInWriteAction() {
return true;
}
private static void addSuppressAttribute(final Project project,
final XmlFile file,
final XmlTag element,
final String id) throws IncorrectOperationException {
XmlAttribute attribute = element.getAttribute(ATTR_IGNORE, TOOLS_URI);
String value;
if (attribute == null) {
value = id;
} else {
List<String> ids = new ArrayList<String>();
for (String existing : Splitter.on(',').trimResults().split(attribute.getValue())) {
if (!existing.equals(id)) {
ids.add(existing);
}
}
ids.add(id);
Collections.sort(ids);
value = Joiner.on(',').join(ids);
}
ensureNamespaceImported(project, file, TOOLS_URI);
element.setAttribute(ATTR_IGNORE, TOOLS_URI, value);
}
// Based on the equivalent code in com.intellij.codeInsight.daemon.impl.actions.SuppressFix
// to add @SuppressWarnings annotations
private static void addSuppressAnnotation(final Project project,
final PsiElement container,
final PsiModifierListOwner modifierOwner,
final String id) throws IncorrectOperationException {
PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierOwner, FQCN_SUPPRESS_LINT);
final PsiAnnotation newAnnotation = createNewAnnotation(project, container, annotation, id);
if (newAnnotation != null) {
if (annotation != null && annotation.isPhysical()) {
annotation.replace(newAnnotation);
}
else {
final PsiNameValuePair[] attributes = newAnnotation.getParameterList().getAttributes();
//noinspection ConstantConditions
new AddAnnotationFix(FQCN_SUPPRESS_LINT, modifierOwner, attributes).invoke(project, null /*editor*/,
container.getContainingFile());
}
}
}
@Nullable
private static PsiAnnotation createNewAnnotation(@NotNull final Project project,
@NotNull final PsiElement container,
@Nullable final PsiAnnotation annotation,
@NotNull final String id) {
if (annotation != null) {
final String currentSuppressedId = "\"" + id + "\"";
String annotationText = annotation.getText();
if (!annotationText.contains("{")) {
final PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length == 1) {
final String suppressedWarnings = attributes[0].getText();
if (suppressedWarnings.contains(currentSuppressedId)) return null;
return JavaPsiFacade.getInstance(project).getElementFactory().createAnnotationFromText(
"@" + FQCN_SUPPRESS_LINT + "({" + suppressedWarnings + ", " + currentSuppressedId + "})", container);
}
}
else {
final int curlyBraceIndex = annotationText.lastIndexOf("}");
if (curlyBraceIndex > 0) {
final String oldSuppressWarning = annotationText.substring(0, curlyBraceIndex);
if (oldSuppressWarning.contains(currentSuppressedId)) return null;
return JavaPsiFacade.getInstance(project).getElementFactory().createAnnotationFromText(
oldSuppressWarning + ", " + currentSuppressedId + "})", container);
}
}
}
else {
return JavaPsiFacade.getInstance(project).getElementFactory()
.createAnnotationFromText("@" + FQCN_SUPPRESS_LINT + "(\"" + id + "\")", container);
}
return null;
}
}
@@ -75,7 +75,7 @@ object UastChecker {
override fun visitCallExpression(node: UCallExpression): Boolean {
if (applicableFunctionNames.isNotEmpty()) {
if (node.kind == FUNCTION_CALL && node.functionName in applicableFunctionNames) {
scanner.visitFunctionCall(context, node)
scanner.visitCall(context, node)
}
}
@@ -30,7 +30,7 @@ public interface UastScanner {
List<String> getApplicableSuperClasses();
List<String> getApplicableConstructorTypes();
void visitFunctionCall(UastAndroidContext context, UCallExpression node);
void visitCall(UastAndroidContext context, UCallExpression node);
void visitClass(UastAndroidContext context, UClass node);
void visitConstructor(UastAndroidContext context, UCallExpression functionCall, UFunction constructor);
@@ -16,15 +16,28 @@
@file:JvmName("UastUtils")
package org.jetbrains.uast
import org.jetbrains.uast.kinds.UastClassKind
import org.jetbrains.uast.visitor.UastVisitor
internal val ERROR_NAME = "<error>"
/**
* Returns the containing class of an element.
*
* @return the containing [UClass] element,
* or null if the receiver is null, or it is a top-level declaration.
*/
tailrec fun UElement?.getContainingClass(): UClass? {
val parent = this?.parent ?: return null
if (parent is UClass) return parent
return parent.getContainingClass()
}
/**
* Returns the containing file of an element.
*
* @return the containing [UFile] element,
* or null if the receiver is null, or the element is not inside a [UFile] (it is abmormal).
*/
tailrec fun UElement?.getContainingFile(): UFile? {
val parent = this?.parent ?: return null
if (parent is UFile) return parent
@@ -33,20 +46,45 @@ tailrec fun UElement?.getContainingFile(): UFile? {
fun UElement?.getContainingClassOrEmpty() = getContainingClass() ?: UClassNotResolved
/**
* Returns the containing function of an element.
*
* @return the containing [UFunction] element,
* or null if the receiver is null, or the element is not inside a [UFunction].
*/
tailrec fun UElement?.getContainingFunction(): UFunction? {
val parent = this?.parent ?: return null
if (parent is UFunction) return parent
return parent.getContainingFunction()
}
/**
* Returns the containing declaration of an element.
*
* @return the containing [UDeclaration] element,
* or null if the receiver is null, or the element is a top-level declaration.
*/
tailrec fun UElement?.getContainingDeclaration(): UDeclaration? {
val parent = this?.parent ?: return null
if (parent is UDeclaration) return parent
return parent.getContainingDeclaration()
}
/**
* Checks if the element is a top-level declaration.
*
* @return true if the element is a top-level declaration, false otherwise.
*/
fun UDeclaration.isTopLevel() = parent is UFile
/**
* Builds the log message for the [UElement.logString] function.
*
* @param firstLine the message line (the interface name, some optional information).
* @param nested nested UElements. Could be `List<UElement>`, [UElement] or `null`.
* @throws IllegalStateException if the [nested] argument is invalid.
* @return the rendered log string.
*/
fun UElement.log(firstLine: String, vararg nested: Any?): String {
return (if (firstLine.isBlank()) "" else firstLine + "\n") + nested.joinToString("\n") {
when (it) {
@@ -74,12 +112,27 @@ fun UClass.findFunctions(name: String) = declarations.filter { it is UFunction &
fun UCallExpression.getReceiver(): UExpression? = (this.parent as? UQualifiedExpression)?.receiver
/**
* Resolves the receiver element if it implements [UResolvable].
*
* @return the resolved element, or null if the element was not resolved, or if the receiver element is not an [UResolvable].
*/
fun UElement.resolveIfCan(context: UastContext): UDeclaration? = (this as? UResolvable)?.resolve(context)
fun UElement.isThrow() = this is USpecialExpressionList && this.kind == UastSpecialExpressionKind.THROW
/**
* Find an annotation with the required qualified name.
*
* @param fqName the qualified name to search
* @return [UAnnotation] element if the annotation with the specified [fqName] was found, null otherwise.
*/
fun UAnnotated.findAnnotation(fqName: String) = annotations.firstOrNull { it.fqName == fqName }
/**
* Get all class declarations (including supertypes).
*
* @param context the Uast context
* @return the list of declarations for the receiver class
*/
fun UClass.getAllDeclarations(context: UastContext): List<UDeclaration> = mutableListOf<UDeclaration>().apply {
this += declarations
for (superType in superTypes) {
@@ -107,14 +160,6 @@ fun UCallExpression.getQualifiedCallElement(): UExpression {
return findParent(parent as? UExpression) ?: this
}
val UDeclaration.fqName: String
get() {
val containingFqName = this.getContainingDeclaration()?.fqName
?: this.getContainingFile()?.packageFqName
val containingFqNameWithDot = containingFqName?.let { it + "." } ?: ""
return containingFqNameWithDot + this.name
}
inline fun <reified T: UElement> UElement.getParentOfType(): T? = getParentOfType(T::class.java)
fun <T: UElement> UElement.getParentOfType(clazz: Class<T>): T? {
@@ -133,10 +178,9 @@ fun <T: UElement> UElement.getParentOfType(clazz: Class<T>): T? {
fun <T> UClass.findStaticMemberOfType(name: String, type: Class<out T>): T? {
for (companion in companions) {
val classKind = companion.kind as? UastClassKind.UastCompanionObject ?: continue
if (!classKind.default) continue
val member = companion.declarations.firstOrNull { it.name == name && type.isInstance(it) }
val member = companion.declarations.firstOrNull {
it.name == name && type.isInstance(it) && it is UModifierOwner && it.hasModifier(UastModifier.STATIC)
}
@Suppress("UNCHECKED_CAST")
if (member != null) return member as T
}
@@ -146,4 +190,52 @@ fun <T> UClass.findStaticMemberOfType(name: String, type: Class<out T>): T? {
it.name == name && it is UModifierOwner
&& it.hasModifier(UastModifier.STATIC) && type.isInstance(it)
} as T
}
fun UExpression.asQualifiedIdentifiers(): List<String>? {
var error = false
val list = mutableListOf<String>()
fun addIdentifiers(expr: UQualifiedExpression) {
val receiver = expr.receiver
val selector = expr.selector as? USimpleReferenceExpression ?: run { error = true; return }
when (receiver) {
is UQualifiedExpression -> addIdentifiers(receiver)
is USimpleReferenceExpression -> list += receiver.identifier
else -> {
error = true
return
}
}
list += selector.identifier
}
when (this) {
is UQualifiedExpression -> addIdentifiers(this)
is USimpleReferenceExpression -> listOf(identifier)
else -> return null
}
return if (error) null else list
}
fun UExpression.matchesQualified(fqName: String): Boolean {
val identifiers = this.asQualifiedIdentifiers() ?: return false
val passedIdentifiers = fqName.split('.')
return identifiers == passedIdentifiers
}
fun UExpression.startsWithQualified(fqName: String): Boolean {
val identifiers = this.asQualifiedIdentifiers() ?: return false
val passedIdentifiers = fqName.split('.')
identifiers.forEachIndexed { i, identifier ->
if (identifier != passedIdentifiers[i]) return false
}
return true
}
fun UExpression.endsWithQualified(fqName: String): Boolean {
val identifiers = this.asQualifiedIdentifiers() ?: return false
val passedIdentifiers = fqName.split('.')
identifiers.forEachIndexed { i, identifier ->
if (identifier != passedIdentifiers[i]) return false
}
return true
}
@@ -46,7 +46,6 @@ abstract class UastVisitor {
open fun visitPrefixExpression(node: UPrefixExpression) = visitElement(node)
open fun visitPostfixExpression(node: UPostfixExpression) = visitElement(node)
open fun visitSpecialExpressionList(node: USpecialExpressionList) = visitElement(node)
open fun visitExpressionList(node: UExpressionList) = visitElement(node)
open fun visitIfExpression(node: UIfExpression) = visitElement(node)
open fun visitSwitchExpression(node: USwitchExpression) = visitElement(node)
open fun visitSwitchClauseExpression(node: USwitchClauseExpression) = visitElement(node)
@@ -58,11 +57,66 @@ abstract class UastVisitor {
open fun visitLiteralExpression(node: ULiteralExpression) = visitElement(node)
open fun visitThisExpression(node: UThisExpression) = visitElement(node)
open fun visitSuperExpression(node: USuperExpression) = visitElement(node)
open fun visitReturnExpression(node: UReturnExpression) = visitElement(node)
open fun visitBreakExpression(node: UBreakExpression) = visitElement(node)
open fun visitContinueExpression(node: UContinueExpression) = visitElement(node)
open fun visitThrowExpression(node: UThrowExpression) = visitElement(node)
open fun visitArrayAccessExpression(node: UArrayAccessExpression) = visitElement(node)
open fun visitCallableReferenceExpression(node: UCallableReferenceExpression) = visitElement(node)
open fun visitClassLiteralExpression(node: UClassLiteralExpression) = visitElement(node)
open fun visitLambdaExpression(node: ULambdaExpression) = visitElement(node)
open fun visitObjectLiteralExpression(node: UObjectLiteralExpression) = visitElement(node)
// After
open fun afterVisitElement(node: UElement) {}
open fun afterVisitFile(node: UFile) {}
open fun afterVisitImportStatement(node: UImportStatement) {}
open fun afterVisitAnnotation(node: UAnnotation) {}
open fun afterVisitCatchClause(node: UCatchClause) {}
open fun afterVisitType(node: UType) {}
// Declarations
open fun afterVisitClass(node: UClass) {}
open fun afterVisitFunction(node: UFunction) {}
open fun afterVisitVariable(node: UVariable) {}
// Expressions
open fun afterVisitLabeledExpression(node: ULabeledExpression) {}
open fun afterVisitDeclarationsExpression(node: UDeclarationsExpression) {}
open fun afterVisitBlockExpression(node: UBlockExpression) {}
open fun afterVisitQualifiedExpression(node: UQualifiedExpression) {}
open fun afterVisitSimpleReferenceExpression(node: USimpleReferenceExpression) {}
open fun afterVisitCallExpression(node: UCallExpression) {}
open fun afterVisitBinaryExpression(node: UBinaryExpression) {}
open fun afterVisitBinaryExpressionWithType(node: UBinaryExpressionWithType) {}
open fun afterVisitParenthesizedExpression(node: UParenthesizedExpression) {}
open fun afterVisitUnaryExpression(node: UUnaryExpression) {}
open fun afterVisitPrefixExpression(node: UPrefixExpression) {}
open fun afterVisitPostfixExpression(node: UPostfixExpression) {}
open fun afterVisitSpecialExpressionList(node: USpecialExpressionList) {}
open fun afterVisitIfExpression(node: UIfExpression) {}
open fun afterVisitSwitchExpression(node: USwitchExpression) {}
open fun afterVisitSwitchClauseExpression(node: USwitchClauseExpression) {}
open fun afterVisitWhileExpression(node: UWhileExpression) {}
open fun afterVisitDoWhileExpression(node: UDoWhileExpression) {}
open fun afterVisitForExpression(node: UForExpression) {}
open fun afterVisitForEachExpression(node: UForEachExpression) {}
open fun afterVisitTryExpression(node: UTryExpression) {}
open fun afterVisitLiteralExpression(node: ULiteralExpression) {}
open fun afterVisitThisExpression(node: UThisExpression) {}
open fun afterVisitSuperExpression(node: USuperExpression) {}
open fun afterVisitReturnExpression(node: UReturnExpression) {}
open fun afterVisitBreakExpression(node: UBreakExpression) {}
open fun afterVisitContinueExpression(node: UContinueExpression) {}
open fun afterVisitThrowExpression(node: UThrowExpression) {}
open fun afterVisitArrayAccessExpression(node: UArrayAccessExpression) {}
open fun afterVisitCallableReferenceExpression(node: UCallableReferenceExpression) {}
open fun afterVisitClassLiteralExpression(node: UClassLiteralExpression) {}
open fun afterVisitLambdaExpression(node: ULambdaExpression) {}
open fun afterVisitObjectLiteralExpression(node: UObjectLiteralExpression) {}
}
object EmptyUastVisitor : UastVisitor()
@@ -17,24 +17,59 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
Represents an applied JVM annotation.
*/
interface UAnnotation : UElement, UNamed, UFqNamed {
/**
* Returns the list of named value arguments.
*/
val valueArguments: List<UNamedExpression>
/**
* Returns the class of this annotation.
*
* @return annotation class or null if the class was not resolved
*/
fun resolve(context: UastContext): UClass?
/**
* Returns the evaluated value of the argument with the specified name.
*
* @param name name of the annotation value parameter
* @return the argument value
*/
fun getValue(name: String) = valueArguments.firstOrNull { it.name == name }?.expression?.evaluate()
/**
* Returns the list of Pair(name, evaluatedValue) for all applied annotation arguments.
*
* @return the list of name-value pairs.
*/
fun getValues() = valueArguments.map { Pair(it.name, it.expression.evaluate()) }
override fun logString() = log("UAnnotation ($name)")
override fun renderString() = if (valueArguments.isEmpty()) "@$name" else "@$name(" +
valueArguments.joinToString { it.renderString() } + ")"
override fun renderString(): String {
return if (valueArguments.isEmpty())
"@$name"
else
"@$name(" + valueArguments.joinToString { it.renderString() } + ")"
}
override fun accept(visitor: UastVisitor) {
if (visitor.visitAnnotation(this)) return
valueArguments.acceptList(visitor)
visitor.afterVisitAnnotation(this)
}
}
/**
* Represents an annotated element.
*/
interface UAnnotated : UElement {
/**
* Returns the list of annotations applied to the current element.
*/
val annotations: List<UAnnotation>
}
@@ -17,39 +17,153 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* The common interface for all Uast elements.
*/
interface UElement {
/**
* Returns the element parent.
*/
val parent: UElement?
/**
* Returns true if this element is valid, false otherwise.
*/
open val isValid: Boolean
get() = true
/**
* Returns the log string.
*
* Output example (should be something like this):
* UWhileExpression
* UBinaryExpression (>)
* USimpleReferenceExpression (i)
* ULiteralExpression (5)
* UBlockExpression
* UCallExpression (println)
* ULiteralExpression (ABC)
* UPostfixExpression (--)
* USimpleReferenceExpression(i)
*
* @return the expression tree for this element.
* @see [UIfExpression] for example.
*/
fun logString(): String
/**
* Returns the string in pseudo-code.
*
* Output example (should be something like this):
* while (i > 5) {
* println("Hello, world")
* i--
* }
*
* @return the rendered text.
* @see [UIfExpression] for example.
*/
fun renderString(): String = logString()
/**
* Passes the element to the specified visitor.
*
* @param visitor the visitor to pass the element to.
*/
fun accept(visitor: UastVisitor) {
visitor.visitElement(this)
}
}
/**
* An interface for the [UElement] which has a name.
*/
interface UNamed {
/**
* Returns the name of this element.
*/
val name: String
/**
* Checks if the element name is equal to the passed name.
*
* @param name the name to check against
* @return true if the element name is equal to [name], false otherwise.
*/
fun matchesName(name: String) = this.name == name
}
/**
* An interface for the [UElement] which has a qualified name.
*/
interface UFqNamed : UNamed {
/**
* Returns the qualified name of this element, or null if the qualified name was not resolved.
*/
val fqName: String?
/**
* Checks if the element qualified name is equal to the passed name.
*
* @param fqName qualified name to check against
* @return true if the element name is not null and is equal to [name], false otherwise.
*/
fun matchesFqName(fqName: String) = this.fqName == fqName
}
/**
* An interface for the [UElement] which has Uast modifiers.
*/
interface UModifierOwner {
/**
* Checks if the element has the passed modifier.
*
* @param modifier modifier to check
* @return true if the element has [modifier], false otherwise.
*/
fun hasModifier(modifier: UastModifier): Boolean
}
/**
* An interface for the [UElement] which has a visibility (class, function, variable).
*/
interface UVisibilityOwner {
/**
* Returns the element visibility.
*/
val visibility: UastVisibility
}
/**
* An inteface for the [UElement] which can be resolved to the its declaration.
*/
interface UResolvable {
/**
* Returns the declaration element.
*
* @param context the Uast context
* @return the resolved declaration, or null if the declaration was not resolved.
*/
fun resolve(context: UastContext): UDeclaration?
/**
* Returns the declaration element or an empty Uast declaration element if the declaration was not resolved.
* An empty declaration element should be a singleton.
* [resolveOrEmpty] should not create new elements each time the declaration was not resolved.
*
* @param context the Uast context
* @return the resolved declaration, of an empty error Uast declaration element if the declaration was not resolved.
*
* @see [UDeclarationNotResolved]
*/
fun resolveOrEmpty(context: UastContext): UDeclaration = resolve(context) ?: UDeclarationNotResolved
/**
* Returns the declaration [UClass] element of null if the declaration was not resolved.
*
* @param context the Uast context
* @return the resolved [UClass] element, or null if the class was not resolved,
* or if the resolved [UElement] is not an instance of [UClass].
*/
fun resolveClass(context: UastContext): UClass? = resolve(context) as? UClass
}
@@ -24,6 +24,7 @@ interface UExpression : UElement {
override fun accept(visitor: UastVisitor) {
visitor.visitElement(this)
visitor.afterVisitElement(this)
}
}
@@ -36,6 +37,6 @@ interface NoModifiers : UModifierOwner {
override fun hasModifier(modifier: UastModifier) = false
}
class EmptyExpression(override val parent: UElement) : UExpression {
class EmptyUExpression(override val parent: UElement) : UExpression {
override fun logString() = "EmptyExpression"
}
@@ -17,11 +17,31 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a source file.
* File is the topmost element of the UElement hierarchy.
* Files should not be nested, thus the [parent] property should always return null.
*/
interface UFile: UElement {
/**
* Returns the qualified package name of this file.
* Could be an empty string if the package is "default", or null if the package directive is not present in this file.
*/
val packageFqName: String?
/**
* Returns the list of import statements.
*/
val importStatements: List<UImportStatement>
/**
* Returns the list of declarations in this file (classes, properties, functions, etc).
*/
val declarations: List<UDeclaration>
/**
* Returns list of classes containing in this file.
*/
val classes: List<UClass>
get() = declarations.filterIsInstance<UClass>()
@@ -32,12 +52,13 @@ interface UFile: UElement {
if (visitor.visitFile(this)) return
declarations.acceptList(visitor)
importStatements.acceptList(visitor)
visitor.afterVisitFile(this)
}
override fun logString() = "UFile (package = $packageFqName)\n" + declarations.joinToString("\n") { it.logString().withMargin }
override fun renderString() = buildString {
if (packageFqName != null) {
if (!packageFqName.isNullOrBlank()) {
appendln("package $packageFqName")
appendln()
}
@@ -17,13 +17,26 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represent a
*
* `do {
* // body
* } while (expr)`
*
* loop expression.
*/
interface UDoWhileExpression : ULoopExpression {
/**
* Returns the loop post-condition.
*/
val condition: UExpression
override fun accept(visitor: UastVisitor) {
if (visitor.visitDoWhileExpression(this)) return
condition.accept(visitor)
body.accept(visitor)
visitor.afterVisitDoWhileExpression(this)
}
override fun renderString() = buildString {
@@ -17,8 +17,24 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a
*
* `for (element : collectionOfElements) {
* // body
* }`
*
* loop expression.
*/
interface UForEachExpression : ULoopExpression {
/**
* Returns the loop variable.
*/
val variable: UVariable
/**
* Returns the iterated value (collection, sequence, iterable etc.)
*/
val iteratedValue: UExpression
override fun accept(visitor: UastVisitor) {
@@ -26,6 +42,7 @@ interface UForEachExpression : ULoopExpression {
variable.accept(visitor)
iteratedValue.accept(visitor)
body.accept(visitor)
visitor.afterVisitForEachExpression(this)
}
override fun renderString() = buildString {
@@ -17,9 +17,29 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a
*
* `for (initDeclarations; loopCondition; update) {
* // body
* }`
*
* loop expression.
*/
interface UForExpression : ULoopExpression {
/**
* Returns the [UExpression] containing variable declarations, or null if the are no variables declared.
*/
val declaration: UExpression?
/**
* Returns the loop condition, or null if the condition is empty.
*/
val condition: UExpression?
/**
* Returns the loop update expression(s).
*/
val update: UExpression?
override fun accept(visitor: UastVisitor) {
@@ -28,6 +48,7 @@ interface UForExpression : ULoopExpression {
condition?.accept(visitor)
update?.accept(visitor)
body.accept(visitor)
visitor.afterVisitForExpression(this)
}
override fun renderString() = buildString {
@@ -17,10 +17,40 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents
*
* `if (condition) {
* // do if true
* } else {
* // do if false
* }`
*
* and
*
* `condition : trueExpression ? falseExpression`
*
* condition expressions.
*/
interface UIfExpression : UExpression {
/**
* Returns the condition expression.
*/
val condition: UExpression
/**
* Returns the expression which is executed if the condition is true, or null if the expression is empty.
*/
val thenBranch: UExpression?
/**
* Returns the expression which is executed if the condition is false, or null if the expression is empty.
*/
val elseBranch: UExpression?
/**
* Returns true if the expression is ternary (condition ? trueExpression : falseExpression).
*/
val isTernary: Boolean
override fun accept(visitor: UastVisitor) {
@@ -28,9 +58,11 @@ interface UIfExpression : UExpression {
condition.accept(visitor)
thenBranch?.accept(visitor)
elseBranch?.accept(visitor)
visitor.afterVisitIfExpression(this)
}
override fun logString() = log("UIfExpression", condition, thenBranch, elseBranch)
override fun renderString() = buildString {
if (isTernary) {
append("(" + condition.renderString() + ")")
@@ -42,7 +74,7 @@ interface UIfExpression : UExpression {
append("if (${condition.renderString()}) ")
thenBranch?.let { append(it.renderString()) }
val elseBranch = elseBranch
if (elseBranch != null && elseBranch !is EmptyExpression) {
if (elseBranch != null && elseBranch !is EmptyUExpression) {
if (thenBranch !is UBlockExpression) append(" ")
append("else ")
append(elseBranch.renderString())
@@ -15,6 +15,12 @@
*/
package org.jetbrains.uast
/**
* Represents the loop expression.
*/
interface ULoopExpression : UExpression {
/**
* Returns the loop body [UExpression].
*/
val body: UExpression
}
@@ -17,14 +17,35 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a
*
* ` switch (expression) {
* case value1 -> expr1
* case value2 -> expr2
* ...
* else -> exprElse
* }
*
* conditional expression.
*/
interface USwitchExpression : UExpression {
/*
Returns the expression on which the `switch` expression is performed.
*/
val expression: UExpression?
/*
Returns the switch body.
Body should contain [USwitchClauseExpression] expressions.
*/
val body: UExpression
override fun accept(visitor: UastVisitor) {
if (visitor.visitSwitchExpression(this)) return
expression?.accept(visitor)
body.accept(visitor)
visitor.afterVisitSwitchExpression(this)
}
override fun logString() = log("USwitchExpression", expression, body)
@@ -35,23 +56,47 @@ interface USwitchExpression : UExpression {
}
}
interface USwitchClauseExpression : UExpression
interface UExpressionSwitchClauseExpression : USwitchClauseExpression {
val caseValue: UExpression
/**
* Represents a [USwitchExpression] clause.
* [USwitchClauseExpression] does not contain the clause body,
* and the actual body expression should be the next element in the parent expression list.
*/
interface USwitchClauseExpression : UExpression {
/**
* Returns the list of values for this clause, or null if the are no values for this close
* (for example, for the `else` clause).
*/
val caseValues: List<UExpression>?
override fun accept(visitor: UastVisitor) {
if (visitor.visitSwitchClauseExpression(this)) return
caseValue.accept(visitor)
caseValues?.acceptList(visitor)
visitor.afterVisitSwitchClauseExpression(this)
}
override fun renderString() = caseValue.renderString() + " -> "
override fun logString() = log("UExpressionSwitchClauseExpression", caseValue)
override fun renderString() = (caseValues?.joinToString { it.renderString() } ?: "else") + " -> "
override fun logString() = log("USwitchClauseExpression", caseValues)
}
interface UDefaultSwitchClauseExpression : USwitchClauseExpression {
override fun logString() = "UDefaultSwitchClause"
override fun renderString() = "else -> "
}
/**
* Represents a [USwitchExpression] clause with the body.
* [USwitchClauseExpressionWithBody], comparing with [USwitchClauseExpression], contains the body expression.
*
* Implementing this interface *is the right way* to support `switch` clauses in your language.
*/
interface USwitchClauseExpressionWithBody : USwitchClauseExpression {
/**
* Returns the body expression for this clause.
*/
val body: UExpression
class SimpleUDefaultSwitchClauseExpression(override val parent: UElement) : UDefaultSwitchClauseExpression
override fun accept(visitor: UastVisitor) {
if (visitor.visitSwitchClauseExpression(this)) return
caseValues?.acceptList(visitor)
body.accept(visitor)
visitor.afterVisitSwitchClauseExpression(this)
}
override fun renderString() = (caseValues?.joinToString { it.renderString() } ?: "else") + " -> " + body.renderString()
override fun logString() = log("USwitchClauseExpressionWithBody", caseValues, body)
}
@@ -17,10 +17,45 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents
*
* `try {
* // tryClause body
* } catch (e: Type1, Type2 ... TypeN) {
* // catchClause1 body
* } ... {
* finally {
* //finallyBody
* }`
*
* and
*
* `try (resource1, ..., resourceN) {
* // tryClause body
* }`
*
* expressions.
*/
interface UTryExpression : UExpression {
/**
* Returns the list of try resources, or null if this expression is not a `try-with-resources` expression.
*/
val resources: List<UElement>?
/**
* Returns the `try` clause expression.
*/
val tryClause: UExpression
/**
* Returns the `catch` clauses [UCatchClause] expression list.
*/
val catchClauses: List<UCatchClause>
/**
* Returns the `finally` clause expression, or null if the `finally` clause is absent.
*/
val finallyClause: UExpression?
override fun accept(visitor: UastVisitor) {
@@ -29,13 +64,14 @@ interface UTryExpression : UExpression {
tryClause.accept(visitor)
catchClauses.acceptList(visitor)
finallyClause?.accept(visitor)
visitor.afterVisitTryExpression(this)
}
override fun renderString() = buildString {
append("try ")
appendln(tryClause.renderString())
catchClauses.forEach { appendln(it.renderString()) }
finallyClause?.let { append("finally ").append(it.renderString()) }
appendln(tryClause.renderString().trim('\n'))
catchClauses.forEach { appendln(it.renderString().trim('\n')) }
finallyClause?.let { append("finally ").append(it.renderString().trim('\n')) }
}
override fun logString() = "UTryExpression\n" +
@@ -44,9 +80,23 @@ interface UTryExpression : UExpression {
(finallyClause?.let { it.logString().withMargin } ?: "<no finally clause>" )
}
/**
* Represents the `catch` clause in [UTryExpression].
*/
interface UCatchClause : UElement {
/**
* Returns the `catch` clause body expression.
*/
val body: UExpression
/**
* Returns the exception parameter variables for this `catch` clause.
*/
val parameters: List<UVariable>
/**
* Returns the exception types for this `catch` clause.
*/
val types: List<UType>
override fun accept(visitor: UastVisitor) {
@@ -54,6 +104,7 @@ interface UCatchClause : UElement {
body.accept(visitor)
parameters.acceptList(visitor)
types.acceptList(visitor)
visitor.afterVisitCatchClause(this)
}
override fun logString() = log("UCatchClause", body)
@@ -17,13 +17,26 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a
*
* `while (condition) {
* // body
* }`
*
* expression.
*/
interface UWhileExpression : ULoopExpression {
/**
* Returns the loop condition.
*/
val condition: UExpression
override fun accept(visitor: UastVisitor) {
if (visitor.visitWhileExpression(this)) return
condition.accept(visitor)
body.accept(visitor)
visitor.afterVisitWhileExpression(this)
}
override fun renderString() = buildString {
@@ -15,41 +15,94 @@
*/
package org.jetbrains.uast
import org.jetbrains.uast.kinds.UastClassKind
import org.jetbrains.uast.visitor.UastVisitor
interface UClass : UDeclaration, UFqNamed, UModifierOwner, UAnnotated {
/* The simple class name is only for the debug purposes. Do not check against it in the production code */
/**
* Represents a JVM class (ordinary class, interface, annotation, singleton, etc.).
*/
interface UClass : UDeclaration, UFqNamed, UModifierOwner, UVisibilityOwner, UAnnotated {
/**
* Returns the class simple (non-qualified) name.
* The simple class name is only for the debug purposes. Do not check against it in the production code.
*/
override val name: String
/**
* Returns true if the class is anonymous ([name] in this case should return a placeholder like "<anonymous>").
*/
val isAnonymous: Boolean
val visibility: UastVisibility
/**
* Returns the class kind (ordinary class, interface, annotation, etc.).
*/
val kind: UastClassKind
/**
* Returns the default type for this class.
*/
val defaultType: UType
/**
* Returns the class companions.
*/
val companions: List<UClass>
/**
* Returns the class JVM name, or null if the JVM name is unknown.
*/
open val internalName: String?
get() = null
/**
* Returns the all supertypes of this class.
*/
val superTypes: List<UType>
/**
* Returns class declarations (nested classes, constructors, functions, variables, etc.).
* An empty (default) constructor also should be present.
*/
val declarations: List<UDeclaration>
/**
* Returns nested classes declared in this class.
*/
val nestedClasses: List<UClass>
get() = declarations.filterIsInstance<UClass>()
/**
* Returns functions declared in this class.
*/
val functions: List<UFunction>
get() = declarations.filterIsInstance<UFunction>()
/**
* Returns properties declared in this class.
*/
val properties: List<UVariable>
get() = declarations.filterIsInstance<UVariable>()
/**
* Returns constructors declared in this class.
*/
@Suppress("UNCHECKED_CAST")
val constructors: List<UFunction>
get() = declarations.filter { it is UFunction && it.kind == UastFunctionKind.CONSTRUCTOR } as List<UFunction>
/**
* Checks if the class is subclass of another.
*
* @param fqName qualified name of the class to check against
* @return true if the class is the *subclass* of the class with the specified [fqName],
* false if the class qualified name is [fqName] or if the class is not a subclass of the class with the specified [fqName]
*/
fun isSubclassOf(fqName: String) : Boolean
/**
* Get the direct superclass of this class.
*
* @return null if the class has not a superclass (java.lang.Object).
*/
fun getSuperClass(context: UastContext): UClass?
override fun accept(visitor: UastVisitor) {
@@ -57,24 +110,24 @@ interface UClass : UDeclaration, UFqNamed, UModifierOwner, UAnnotated {
nameElement?.accept(visitor)
declarations.acceptList(visitor)
annotations.acceptList(visitor)
visitor.afterVisitClass(this)
}
override fun renderString(): String {
val modifiers = listOf(UastModifier.ABSTRACT, UastModifier.FINAL, UastModifier.STATIC)
.filter { hasModifier(it) }.joinToString(" ") { it.name }.let { if (it.isBlank()) it else "$it " }
val name = if (isAnonymous) "" else " $name"
override fun renderString() = buildString {
appendWithSpace(visibility.name)
appendWithSpace(renderModifiers())
appendWithSpace(kind.text)
appendWithSpace(name)
val declarations = if (declarations.isEmpty()) "" else buildString {
appendln("{")
append(declarations.joinToString("\n") { it.renderString() }.withMargin)
append(declarations.joinToString("\n\n") { it.renderString().trim('\n') }.withMargin)
append("\n}")
}
return "${visibility.name} " + modifiers + kind.text + name + " " + declarations
append(declarations)
}
override fun logString() = "UClass ($name, kind = ${kind.text})\n" + declarations.logString()
override fun logString() = log("UClass ($name, kind = ${kind.text})", declarations)
}
object UClassNotResolved : UClass {
@@ -88,7 +141,7 @@ object UClassNotResolved : UClass {
override val defaultType = UastErrorType
override val nameElement = null
override val parent = null
override val name = "<class not resolved>"
override val name = ERROR_NAME
override val fqName = null
override val internalName = null
@@ -17,16 +17,38 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a declaration.
*/
interface UDeclaration : UElement, UNamed {
/**
* Returns an element for the name node, or null if the node does not exist in the underlying AST (Psi).
*/
val nameElement: UElement?
/**
* Checks if the function name is [name], and the function containing class qualified name is [containingClassFqName].
*
* @param containingClassFqName the required containing class qualified name.
* @param name the function name to check against.
* @return true if the call is a function call, the function name is [name],
* and the qualified name of the function direct containing class is [containingClassFqName],
* false otherwise.
*/
open fun matchesNameWithContaining(containingClassFqName: String, name: String): Boolean {
if (!matchesName(name)) return false
val containingClass = parent as? UClass ?: return false
return containingClass.matchesFqName(containingClassFqName)
}
override fun accept(visitor: UastVisitor) {
visitor.visitElement(this)
visitor.afterVisitElement(this)
}
}
object UDeclarationNotResolved : UDeclaration {
override val name = "<declaration not resolved>"
override val name = ERROR_NAME
override val nameElement = null
override val parent = null
@@ -17,58 +17,109 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
interface UFunction : UDeclaration, UModifierOwner, UAnnotated {
/**
* Represents a function.
* Function could be a JVM method, a property accessor, a constructor, etc.
*/
interface UFunction : UDeclaration, UModifierOwner, UVisibilityOwner, UAnnotated {
/**
* Returns the function kind.
*/
val kind: UastFunctionKind
val valueParameters: List<UVariable>
val valueParameterCount: Int
val typeParameters: List<UTypeReference>
val typeParameterCount: Int
val returnType: UType?
val body: UExpression
val visibility: UastVisibility
/**
* Returns the function value parameters.
*/
val valueParameters: List<UVariable>
/**
* Returns the function value parameters count.
* Retrieving the parameter count could be faster than getting the [valueParameters.size],
* because there is no need to create actual [UVariable] instances.
*/
val valueParameterCount: Int
/**
* Returns the function type parameters.
*/
val typeParameters: List<UTypeReference>
/**
* Returns the function type parameter count.
*/
val typeParameterCount: Int
/**
* Returns the function return type, or null if the function does not have a return type
* (e.g. it is a constructor).
*/
val returnType: UType?
/**
* Returns the function body expression.
*/
val body: UExpression?
/**
* Returns the function JVM descriptor (for example, "(ILjava/lang/String;)[I"), or null if the descriptor is unknown.
*/
open val bytecodeDescriptor: String?
get() = null
/**
* Get the list of all super functions for this function.
*/
fun getSuperFunctions(context: UastContext): List<UFunction>
override fun accept(visitor: UastVisitor) {
if (visitor.visitFunction(this)) return
nameElement?.accept(visitor)
valueParameters.acceptList(visitor)
body.accept(visitor)
body?.accept(visitor)
annotations.acceptList(visitor)
typeParameters.acceptList(visitor)
returnType?.accept(visitor)
visitor.afterVisitFunction(this)
}
override fun renderString(): String {
val typeParameters = if (typeParameterCount == 0) "" else "<" + typeParameters.joinToString { it.renderString() } + "> "
val valueParameters = valueParameters.joinToString { it.renderString() }
val returnType = returnType?.let { ": " + it.renderString() } ?: ""
val body = when (body) {
override fun renderString(): String = buildString {
appendWithSpace(visibility.name)
appendWithSpace(renderModifiers())
append("fun ")
if (typeParameterCount > 0) {
append('<').append(typeParameters.joinToString { it.renderString() }).append("> ")
}
append(name)
append('(')
append(valueParameters.joinToString() { it.renderString() })
append(')')
returnType?.let { append(": " + it.renderString()) }
val body = body
val bodyRendered = when (body) {
null -> ""
is UBlockExpression -> " " + body.renderString()
else -> " = " + body.renderString()
}
return "${visibility.name} fun " + typeParameters + name + "(" + valueParameters + ")" + returnType + body
append(bodyRendered)
}
override fun logString() = "UFunction ($name, kind = ${kind.text}, " +
"paramCount = $valueParameterCount)\n" + body.logString().withMargin
override fun logString() = log("UFunction ($name, kind = ${kind.text}, paramCount = $valueParameterCount)", body)
}
object UFunctionNotResolved : UFunction {
override val kind = UastFunctionKind("<unknown>")
override val kind = UastFunctionKind(ERROR_NAME)
override val valueParameters = emptyList<UVariable>()
override val valueParameterCount = 0
override val typeParameters = emptyList<UTypeReference>()
override val typeParameterCount = 0
override val returnType = null
override val body = EmptyExpression(this)
override val body = null
override val visibility = UastVisibility.PRIVATE
override val nameElement = null
override val parent = null
override val name = "<function not resolved>"
override val name = ERROR_NAME
override fun hasModifier(modifier: UastModifier) = false
override fun getSuperFunctions(context: UastContext) = emptyList<UFunction>()
@@ -15,19 +15,33 @@
*/
package org.jetbrains.uast
import org.jetbrains.uast.kinds.UastImportKind
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents an import statement.
*/
interface UImportStatement : UElement {
/**
* Returns the qualified name to import.
*/
val fqNameToImport: String?
val isStarImport: Boolean
val kind: UastImportKind
/* Returns null if isStarImport = true */
/**
* Returns true is the import is a "star" import (on-demand, all-under).
*/
val isStarImport: Boolean
/**
* Resolve the import statement to the declaration.
*
* @param context the Uast context
* @return the declaration element, or null if the declaration was not resolved.
*/
fun resolve(context: UastContext): UDeclaration?
override fun accept(visitor: UastVisitor) {
visitor.visitImportStatement(this)
visitor.afterVisitImportStatement(this)
}
override fun logString() = "UImport ($fqNameToImport)"
@@ -17,50 +17,105 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents the type.
* The abstraction is quite simple. Intersection types, union types, platform types are yet to be supported.
*/
interface UType : UElement, UNamed, UFqNamed, UAnnotated, UResolvable {
override fun matchesName(name: String) = this.name == name || this.name.endsWith(".$name")
/* The simple type name is only for the debug purposes. Do not check against it in the production code */
/**
* Returns the simple (non-qualified) type name.
* The simple type name is only for the debug purposes. Do not check against it in the production code.
*/
override val name: String
/* Semantics: returns true if the type is either a boxed or an unboxed, false otherwise */
/**
* Returns true if the type is either a boxed or an unboxed [Integer], false otherwise.
*/
val isInt: Boolean
/**
* Returns true if the type is either a boxed or an unboxed [Short], false otherwise.
*/
val isShort: Boolean
/**
* Returns true if the type is either a boxed or an unboxed [Long], false otherwise.
*/
val isLong: Boolean
/**
* Returns true if the type is either a boxed or an unboxed [Float], false otherwise.
*/
val isFloat: Boolean
/**
* Returns true if the type is either a boxed or an unboxed [Double], false otherwise.
*/
val isDouble: Boolean
/**
* Returns true if the type is either a boxed or an unboxed [Character], false otherwise.
*/
val isChar: Boolean
/**
* Returns true if the type is either a boxed or an unboxed [Boolean], false otherwise.
*/
val isBoolean: Boolean
/**
* Returns true if the type is either a boxed or an unboxed [Byte], false otherwise.
*/
val isByte: Boolean
override fun logString() = "UType ($name)"
override fun renderString() = name
/**
* Returns the [UClass] declaration element for this type.
*
* @param context the Uast context
* @return the [UClass] declaration element, or null if the class was not resolved.
*/
override fun resolve(context: UastContext): UClass?
override fun resolveOrEmpty(context: UastContext) = resolve(context) ?: UClassNotResolved
override fun accept(visitor: UastVisitor) {
if (visitor.visitType(this)) return
annotations.acceptList(visitor)
visitor.afterVisitType(this)
}
}
/**
* Represents a type reference.
*/
interface UTypeReference : UDeclaration, UResolvable {
override fun renderString() = ""
override fun logString() = log("UTypeReference")
/**
* Returns the [UClass] declaration for this type reference.
*
* @param context the Uast context
* @return the [UClass] declaration element, or null if the class was not resolved.
*/
override fun resolve(context: UastContext): UClass?
override fun resolveOrEmpty(context: UastContext) = resolve(context) ?: UClassNotResolved
}
object UastErrorType : UType, NoAnnotations {
override val isInt = false
override val isLong = false
override val isShort = false
override val isFloat = false
override val isDouble = false
override val isChar = false
override val isByte = false
override val parent = null
override val name = "<error>"
override val name = ERROR_NAME
override val fqName = null
override val isBoolean = false
override fun resolve(context: UastContext) = null
@@ -17,16 +17,26 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
interface UVariable : UDeclaration, UModifierOwner, UAnnotated {
interface UVariable : UDeclaration, UModifierOwner, UVisibilityOwner, UAnnotated {
/**
* Return the variable initializer (or the default value for value parameter), or null if the variable is not initialized.
*/
val initializer: UExpression?
/**
* Return the variable kind.
*/
val kind: UastVariableKind
/**
* Return the variable type.
*/
val type: UType
val visibility: UastVisibility
open val getters: List<UFunction>?
get() = null
open val setters: List<UFunction>?
/**
* Return the list of accessors if the variable is a property, or null otherwise.
*/
open val accessors: List<UFunction>?
get() = null
override fun accept(visitor: UastVisitor) {
@@ -35,13 +45,28 @@ interface UVariable : UDeclaration, UModifierOwner, UAnnotated {
initializer?.accept(visitor)
annotations.acceptList(visitor)
type.accept(visitor)
visitor.afterVisitVariable(this)
}
override fun renderString(): String {
val initializer = if (initializer != null && initializer !is EmptyExpression) " = ${initializer!!.renderString()}" else ""
val prefix = if (kind == UastVariableKind.VALUE_PARAMETER) "" else "var "
val emptyLine = if (kind == UastVariableKind.MEMBER) "\n" else ""
return "$prefix$name: " + type.name + initializer + emptyLine
override fun renderString(): String = buildString {
if (kind != UastVariableKind.VALUE_PARAMETER) appendWithSpace(visibility.name)
appendWithSpace(renderModifiers())
if (kind != UastVariableKind.VALUE_PARAMETER) append("var ")
append(name)
append(": ")
append(type.name)
if (initializer != null && initializer !is EmptyUExpression) {
append(" = ")
append(initializer!!.renderString())
}
accessors?.let {
appendln()
it.forEachIndexed { i, accessor ->
this@buildString.append(accessor.renderString().withMargin)
if ((i + 1) < it.size) appendln()
}
}
}
override fun logString() = "UVariable ($name, kind = ${kind.name})\n" +
@@ -50,11 +75,11 @@ interface UVariable : UDeclaration, UModifierOwner, UAnnotated {
object UVariableNotResolved : UVariable {
override val initializer = null
override val kind = UastVariableKind.MEMBER
override val kind = UastVariableKind(ERROR_NAME)
override val type = UastErrorType
override val nameElement = null
override val parent = null
override val name = "<variable not resolved>"
override val name = ERROR_NAME
override val visibility = UastVisibility.LOCAL
override fun hasModifier(modifier: UastModifier) = false
@@ -17,14 +17,25 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents `receiver[index0, ..., indexN]` expression.
*/
interface UArrayAccessExpression : UExpression {
/**
* Returns the receiver expression.
*/
val receiver: UExpression
/**
* Returns the list of index expressions.
*/
val indices: List<UExpression>
override fun accept(visitor: UastVisitor) {
if (visitor.visitArrayAccessExpression(this)) return
receiver.accept(visitor)
indices.acceptList(visitor)
visitor.afterVisitArrayAccessExpression(this)
}
override fun logString() = log("UArrayAccessExpression", receiver, indices)
@@ -17,15 +17,30 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a binary expression (value1 op value2), eg. `2 + "A"`.
*/
interface UBinaryExpression : UExpression {
/**
* Returns the left operand.
*/
val leftOperand: UExpression
/**
* Returns the binary operator.
*/
val operator: UastBinaryOperator
/**
* Returns the right operand.
*/
val rightOperand: UExpression
override fun accept(visitor: UastVisitor) {
if (visitor.visitBinaryExpression(this)) return
leftOperand.accept(visitor)
rightOperand.accept(visitor)
visitor.afterVisitBinaryExpression(this)
}
override fun logString() =
@@ -17,17 +17,32 @@ package org.jetbrains.uast
import org.jetbrains.uast.visitor.UastVisitor
/**
* Represents a binary expression with type (value op type), e.g. ("A" instanceof String).
*/
interface UBinaryExpressionWithType : UExpression {
/**
* Returns the operand expression.
*/
val operand: UExpression
/**
* Returns the operation kind.
*/
val operationKind: UastBinaryExpressionWithTypeKind
/**
* Returns the type.
*/
val type: UType
override fun logString() = log("UBinaryExpressionWithType (${getExpressionType()?.name}, ${operationKind.name})", operand)
override fun renderString() = "(${operand.renderString()}) ${operationKind.name} ${getExpressionType()?.name}"
override fun renderString() = "${operand.renderString()} ${operationKind.name} ${type.name}"
override fun accept(visitor: UastVisitor) {
if (visitor.visitBinaryExpressionWithType(this)) return
operand.accept(visitor)
type.accept(visitor)
visitor.afterVisitBinaryExpressionWithType(this)
}
}

Some files were not shown because too many files have changed in this diff Show More