Other code review changes
This commit is contained in:
@@ -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 <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 */
|
||||
|
||||
+7
-6
@@ -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);
|
||||
|
||||
|
||||
+5
-4
@@ -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))
|
||||
}
|
||||
}
|
||||
+9
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-109
@@ -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);
|
||||
|
||||
+12
-11
@@ -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;
|
||||
}
|
||||
|
||||
+4
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-234
@@ -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
|
||||
};
|
||||
}
|
||||
+3
-4
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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:
|
||||
|
||||
+9
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -427,7 +427,7 @@ public class LayoutConsistencyDetector extends LayoutDetector implements UastSca
|
||||
return locations.get(0);
|
||||
}
|
||||
|
||||
// ---- Implements JavaScanner ----
|
||||
// ---- Implements UastScanner ----
|
||||
|
||||
@Override
|
||||
public boolean appliesToResourceRefs() {
|
||||
|
||||
+5
-6
@@ -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);
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-8
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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)) {
|
||||
|
||||
-263
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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");
|
||||
|
||||
+3
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -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.");
|
||||
}
|
||||
|
||||
+19
-18
@@ -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;
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+16
-55
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -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*/);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-28
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-3
@@ -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");
|
||||
|
||||
-16
@@ -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;
|
||||
|
||||
+12
-29
@@ -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() {
|
||||
|
||||
-16
@@ -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;
|
||||
|
||||
+4
-27
@@ -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>
|
||||
|
||||
-46
@@ -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);
|
||||
|
||||
+5
-21
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
-16
@@ -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;
|
||||
|
||||
-32
@@ -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
|
||||
|
||||
-16
@@ -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;
|
||||
|
||||
+8
-8
@@ -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);
|
||||
|
||||
+73
-82
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+3
-7
@@ -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
|
||||
|
||||
|
||||
+4
-4
@@ -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();
|
||||
}
|
||||
|
||||
+16
-237
@@ -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);
|
||||
}
|
||||
|
||||
-125
@@ -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));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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;
|
||||
|
||||
-276
@@ -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() =
|
||||
|
||||
+16
-1
@@ -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
Reference in New Issue
Block a user