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="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="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="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="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="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"/>
|
<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="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="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="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="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="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"/>
|
<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="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="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="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="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="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="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="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="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"/>
|
<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="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="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="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="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="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"/>
|
<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) {
|
for (File file : files) {
|
||||||
Context context = new Context(this, project, main, file);
|
Context context = new Context(this, project, main, file);
|
||||||
fireEvent(EventType.SCANNING_FILE, context);
|
fireEvent(LintListener.EventType.SCANNING_FILE, context);
|
||||||
for (Detector detector : detectors) {
|
for (Detector detector : detectors) {
|
||||||
if (detector.appliesTo(context, file)) {
|
if (detector.appliesTo(context, file)) {
|
||||||
detector.beforeCheckFile(context);
|
detector.beforeCheckFile(context);
|
||||||
@@ -1437,7 +1437,7 @@ public class LintDriver {
|
|||||||
if (!sources.isEmpty()) {
|
if (!sources.isEmpty()) {
|
||||||
List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(sources.size());
|
List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(sources.size());
|
||||||
for (File file : sources) {
|
for (File file : sources) {
|
||||||
JavaContext context = new JavaContext(this, project, main, file, javaParser);
|
JavaContext context = new JavaContext(this, project, main, file);
|
||||||
contexts.add(context);
|
contexts.add(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2377,7 +2377,7 @@ public class LintDriver {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (valueNode instanceof UCallExpression
|
} else if (valueNode instanceof UCallExpression
|
||||||
&& ((UCallExpression)valueNode).getKind() == JavaUastCallKinds.ARRAY_INITIALIZER) {
|
&& ((UCallExpression)valueNode).getKind() == UastCallKind.ARRAY_INITIALIZER) {
|
||||||
UCallExpression array = (UCallExpression) valueNode;
|
UCallExpression array = (UCallExpression) valueNode;
|
||||||
List<UExpression> expressions = array.getValueArguments();
|
List<UExpression> expressions = array.getValueArguments();
|
||||||
for (UExpression arrayElement : expressions) {
|
for (UExpression arrayElement : expressions) {
|
||||||
|
|||||||
@@ -510,7 +510,7 @@ public abstract class Detector {
|
|||||||
return null;
|
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 */
|
/** Scope-set used for detectors which scan all resources */
|
||||||
public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
|
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 */
|
/** 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 */
|
/** 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);
|
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 */
|
/** 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.Severity;
|
||||||
import com.android.tools.klint.detector.api.Speed;
|
import com.android.tools.klint.detector.api.Speed;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
@@ -54,21 +53,21 @@ public class AddJavascriptInterfaceDetector extends Detector implements UastScan
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
AddJavascriptInterfaceDetector.class,
|
AddJavascriptInterfaceDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE)).
|
Scope.SOURCE_FILE_SCOPE)).
|
||||||
addMoreInfo(
|
addMoreInfo(
|
||||||
"https://labs.mwrinfosecurity.com/blog/2013/09/24/webview-addjavascriptinterface-remote-code-execution/");
|
"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 WEB_VIEW = "android.webkit.WebView"; //$NON-NLS-1$
|
||||||
private static final String ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface"; //$NON-NLS-1$
|
private static final String ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface"; //$NON-NLS-1$
|
||||||
|
|
||||||
// ---- Implements UastScanner ----
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Speed getSpeed() {
|
public Speed getSpeed() {
|
||||||
return Speed.FAST;
|
return Speed.FAST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Implements UastScanner ----
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public List<String> getApplicableFunctionNames() {
|
public List<String> getApplicableFunctionNames() {
|
||||||
@@ -76,11 +75,13 @@ public class AddJavascriptInterfaceDetector extends Detector implements UastScan
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
if (context.getLintContext().getMainProject().getMinSdk() >= 17) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore if the method doesn't fit our description.
|
||||||
UFunction resolvedFunction = node.resolveOrEmpty(context);
|
UFunction resolvedFunction = node.resolveOrEmpty(context);
|
||||||
UClass containingClass = UastUtils.getContainingClassOrEmpty(resolvedFunction);
|
UClass containingClass = UastUtils.getContainingClassOrEmpty(resolvedFunction);
|
||||||
if (!containingClass.isSubclassOf(WEB_VIEW)) {
|
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: " +
|
String message = "`WebView.addJavascriptInterface` should not be called with minSdkVersion < 17 for security reasons: " +
|
||||||
"JavaScript can use reflection to manipulate application";
|
"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(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
AlarmDetector.class,
|
AlarmDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Alarm set too soon/frequently */
|
/** Alarm set too soon/frequently */
|
||||||
public static final Issue ISSUE = Issue.create(
|
public static final Issue ISSUE = Issue.create(
|
||||||
@@ -84,7 +84,7 @@ public class AlarmDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
UFunction setRepeatingFunction = node.resolve(context);
|
UFunction setRepeatingFunction = node.resolve(context);
|
||||||
UClass containingClass = UastUtils.getContainingClassOrEmpty(setRepeatingFunction);
|
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.UElement;
|
||||||
import org.jetbrains.uast.UQualifiedExpression;
|
import org.jetbrains.uast.UQualifiedExpression;
|
||||||
import org.jetbrains.uast.USimpleReferenceExpression;
|
import org.jetbrains.uast.USimpleReferenceExpression;
|
||||||
|
import org.jetbrains.uast.UastUtils;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
import org.jetbrains.uast.check.UastAndroidUtils;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
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 isIfRoom = description.equals("SHOW_AS_ACTION_IF_ROOM"); //$NON-NLS-1$
|
||||||
boolean isAlways = description.equals("SHOW_AS_ACTION_ALWAYS"); //$NON-NLS-1$
|
boolean isAlways = description.equals("SHOW_AS_ACTION_ALWAYS"); //$NON-NLS-1$
|
||||||
if ((isIfRoom || isAlways)
|
if ((isIfRoom || isAlways)
|
||||||
&& node.getReceiver().renderString().equals("MenuItem")) { //$NON-NLS-1$
|
&& UastUtils.endsWithQualified(node.getReceiver(), "MenuItem")) { //$NON-NLS-1$
|
||||||
if (isAlways) {
|
if (isAlways) {
|
||||||
JavaContext lintContext = mContext.getLintContext();
|
JavaContext lintContext = mContext.getLintContext();
|
||||||
if (lintContext.getDriver().isSuppressed(lintContext, ISSUE, node)) {
|
if (lintContext.getDriver().isSuppressed(lintContext, ISSUE, node)) {
|
||||||
return false;
|
return super.visitQualifiedExpression(node);
|
||||||
}
|
}
|
||||||
if (mAlwaysFields == null) {
|
if (mAlwaysFields == null) {
|
||||||
mAlwaysFields = new ArrayList<Location>();
|
mAlwaysFields = new ArrayList<Location>();
|
||||||
}
|
}
|
||||||
mAlwaysFields.add(UastAndroidUtils.getLocation(node));
|
mAlwaysFields.add(mContext.getLocation(node));
|
||||||
} else {
|
} else {
|
||||||
mHasIfRoomRefs = true;
|
mHasIfRoomRefs = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return super.visitQualifiedExpression(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import java.io.File;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.java.JavaUastCallKinds;
|
import org.jetbrains.uast.java.JavaUastCallKinds;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
@@ -62,7 +61,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
|
|||||||
Severity.ERROR,
|
Severity.ERROR,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
AnnotationDetector.class,
|
AnnotationDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
/** Constructs a new {@link AnnotationDetector} check */
|
/** Constructs a new {@link AnnotationDetector} check */
|
||||||
public AnnotationDetector() {
|
public AnnotationDetector() {
|
||||||
@@ -108,7 +107,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
|
|||||||
return super.visitAnnotation(node);
|
return super.visitAnnotation(node);
|
||||||
}
|
}
|
||||||
} else if (valueNode instanceof UCallExpression &&
|
} else if (valueNode instanceof UCallExpression &&
|
||||||
((UCallExpression) valueNode).getKind() == JavaUastCallKinds.ARRAY_INITIALIZER) {
|
((UCallExpression) valueNode).getKind() == UastCallKind.ARRAY_INITIALIZER) {
|
||||||
UCallExpression array = (UCallExpression) valueNode;
|
UCallExpression array = (UCallExpression) valueNode;
|
||||||
if (array.getValueArgumentCount() == 0) {
|
if (array.getValueArgumentCount() == 0) {
|
||||||
continue;
|
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) {
|
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) {
|
if (parent instanceof UFunction || parent instanceof UBlockExpression) {
|
||||||
break;
|
break;
|
||||||
} else if (issue == ApiDetector.UNSUPPORTED && parent instanceof UDeclarationsExpression) {
|
} else if (issue == ApiDetector.UNSUPPORTED && parent instanceof UDeclarationsExpression) {
|
||||||
UDeclarationsExpression declarations = (UDeclarationsExpression)parent;
|
UDeclarationsExpression declarations = (UDeclarationsExpression) parent;
|
||||||
for (UVariable var : declarations.getVariables()) {
|
for (UVariable var : declarations.getVariables()) {
|
||||||
if (var.getKind() != UastVariableKind.MEMBER && var.getInitializer() instanceof UQualifiedExpression) {
|
if (var.getKind() != UastVariableKind.MEMBER && var.getInitializer() instanceof UQualifiedExpression) {
|
||||||
return true;
|
return true;
|
||||||
@@ -158,7 +157,7 @@ public class AnnotationDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
// This issue doesn't have AST access: annotations are not
|
// This issue doesn't have AST access: annotations are not
|
||||||
// available for local variables or parameters
|
// 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 " +
|
"The `@SuppressLint` annotation cannot be used on a local " +
|
||||||
"variable with the lint check '%1$s': move out to the " +
|
"variable with the lint check '%1$s': move out to the " +
|
||||||
"surrounding method", id));
|
"surrounding method", id));
|
||||||
|
|||||||
@@ -199,7 +199,8 @@ open class ApiDetector : Detector(), UastScanner {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null
|
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 {
|
tailrec fun getLocalMinSdk(scope: UElement?): Int {
|
||||||
if (scope == null) return -1
|
if (scope == null) return -1
|
||||||
@@ -223,7 +224,7 @@ open class ApiDetector : Detector(), UastScanner {
|
|||||||
val value = (valueNode as ULiteralExpression).value as String
|
val value = (valueNode as ULiteralExpression).value as String
|
||||||
return SdkVersionInfo.getApiByBuildCode(value, true)
|
return SdkVersionInfo.getApiByBuildCode(value, true)
|
||||||
} else if (valueNode is UQualifiedExpression) {
|
} else if (valueNode is UQualifiedExpression) {
|
||||||
val codename = valueNode.selector.renderString()
|
val codename = valueNode.getSelectorAsIdentifier();
|
||||||
return SdkVersionInfo.getApiByBuildCode(codename, true)
|
return SdkVersionInfo.getApiByBuildCode(codename, true)
|
||||||
} else if (valueNode is USimpleReferenceExpression) {
|
} else if (valueNode is USimpleReferenceExpression) {
|
||||||
val codename = valueNode.identifier;
|
val codename = valueNode.identifier;
|
||||||
@@ -239,8 +240,8 @@ open class ApiDetector : Detector(), UastScanner {
|
|||||||
fun isCheckedExplicitly(context: UastAndroidContext, requiredVersion: Int, node: UElement): Boolean {
|
fun isCheckedExplicitly(context: UastAndroidContext, requiredVersion: Int, node: UElement): Boolean {
|
||||||
tailrec fun UExpression.isSdkIntReference(): Boolean = when (this) {
|
tailrec fun UExpression.isSdkIntReference(): Boolean = when (this) {
|
||||||
is UParenthesizedExpression -> expression.isSdkIntReference()
|
is UParenthesizedExpression -> expression.isSdkIntReference()
|
||||||
is USimpleReferenceExpression -> resolve(context)?.fqName == SDK_INT_FQNAME
|
is USimpleReferenceExpression -> resolve(context)?.matchesNameWithContaining(SDK_INT_CONTAINING, SDK_INT) ?: false
|
||||||
is UQualifiedExpression -> resolve(context)?.fqName == SDK_INT_FQNAME
|
is UQualifiedExpression -> resolve(context)?.matchesNameWithContaining(SDK_INT_CONTAINING, SDK_INT) ?: false
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,8 +306,8 @@ open class ApiDetector : Detector(), UastScanner {
|
|||||||
else
|
else
|
||||||
check(node.parent, node, context)
|
check(node.parent, node, context)
|
||||||
}
|
}
|
||||||
is UExpressionSwitchClauseExpression -> {
|
is USwitchClauseExpression -> {
|
||||||
if (checkCondition(node.caseValue) == true)
|
if (node.caseValues?.any { checkCondition(it) == true } ?: false)
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
check(node.parent, node, context)
|
check(node.parent, node, context)
|
||||||
@@ -400,7 +401,7 @@ open class ApiDetector : Detector(), UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
Implementation(
|
Implementation(
|
||||||
ApiDetector::class.java,
|
ApiDetector::class.java,
|
||||||
Scope.JAVA_FILE_SCOPE))
|
Scope.SOURCE_FILE_SCOPE))
|
||||||
|
|
||||||
/** Accessing an unsupported API */
|
/** Accessing an unsupported API */
|
||||||
@JvmField
|
@JvmField
|
||||||
@@ -429,6 +430,6 @@ open class ApiDetector : Detector(), UastScanner {
|
|||||||
Severity.ERROR,
|
Severity.ERROR,
|
||||||
Implementation(
|
Implementation(
|
||||||
ApiDetector::class.java,
|
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.UFunction;
|
||||||
import org.jetbrains.uast.UastUtils;
|
import org.jetbrains.uast.UastUtils;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
public class AppCompatCallDetector extends Detector implements UastScanner {
|
public class AppCompatCallDetector extends Detector implements UastScanner {
|
||||||
@@ -53,7 +52,7 @@ public class AppCompatCallDetector extends Detector implements UastScanner {
|
|||||||
Category.CORRECTNESS, 6, Severity.WARNING,
|
Category.CORRECTNESS, 6, Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
AppCompatCallDetector.class,
|
AppCompatCallDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE)).
|
Scope.SOURCE_FILE_SCOPE)).
|
||||||
addMoreInfo("http://developer.android.com/tools/support-library/index.html");
|
addMoreInfo("http://developer.android.com/tools/support-library/index.html");
|
||||||
|
|
||||||
private static final String GET_ACTION_BAR = "getActionBar";
|
private static final String GET_ACTION_BAR = "getActionBar";
|
||||||
@@ -94,26 +93,26 @@ public class AppCompatCallDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
if (mDependsOnAppCompat && isAppBarActivityCall(context, node)) {
|
if (mDependsOnAppCompat && isAppBarActivityCall(context, node)) {
|
||||||
String replace = null;
|
String replace = null;
|
||||||
if (node.functionNameMatches(GET_ACTION_BAR)) {
|
if (node.matchesFunctionName(GET_ACTION_BAR)) {
|
||||||
replace = "getSupportActionBar";
|
replace = "getSupportActionBar";
|
||||||
} else if (node.functionNameMatches(START_ACTION_MODE)) {
|
} else if (node.matchesFunctionName(START_ACTION_MODE)) {
|
||||||
replace = "startSupportActionMode";
|
replace = "startSupportActionMode";
|
||||||
} else if (node.functionNameMatches(SET_PROGRESS_BAR_VIS)) {
|
} else if (node.matchesFunctionName(SET_PROGRESS_BAR_VIS)) {
|
||||||
replace = "setSupportProgressBarVisibility";
|
replace = "setSupportProgressBarVisibility";
|
||||||
} else if (node.functionNameMatches(SET_PROGRESS_BAR_IN_VIS)) {
|
} else if (node.matchesFunctionName(SET_PROGRESS_BAR_IN_VIS)) {
|
||||||
replace = "setSupportProgressBarIndeterminateVisibility";
|
replace = "setSupportProgressBarIndeterminateVisibility";
|
||||||
} else if (node.functionNameMatches(SET_PROGRESS_BAR_INDETERMINATE)) {
|
} else if (node.matchesFunctionName(SET_PROGRESS_BAR_INDETERMINATE)) {
|
||||||
replace = "setSupportProgressBarIndeterminate";
|
replace = "setSupportProgressBarIndeterminate";
|
||||||
} else if (node.functionNameMatches(REQUEST_WINDOW_FEATURE)) {
|
} else if (node.matchesFunctionName(REQUEST_WINDOW_FEATURE)) {
|
||||||
replace = "supportRequestWindowFeature";
|
replace = "supportRequestWindowFeature";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replace != null) {
|
if (replace != null) {
|
||||||
String message = String.format(ERROR_MESSAGE_FORMAT, replace, node.getFunctionName());
|
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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
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 org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
import static org.jetbrains.uast.UastLiteralUtils.isNullLiteral;
|
import static org.jetbrains.uast.UastLiteralUtils.isNullLiteral;
|
||||||
@@ -65,7 +64,7 @@ public class AssertDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
AssertDetector.class,
|
AssertDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE))
|
Scope.SOURCE_FILE_SCOPE))
|
||||||
.addMoreInfo(
|
.addMoreInfo(
|
||||||
"https://code.google.com/p/android/issues/detail?id=65183"); //$NON-NLS-1$
|
"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) {
|
public UastVisitor createUastVisitor(final UastAndroidContext context) {
|
||||||
return new UastVisitor() {
|
return new UastVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
|
public boolean visitCallExpression(@NotNull UCallExpression node) {
|
||||||
if (node.getKind() != JavaSpecialExpressionKinds.ASSERT) {
|
if (!(node instanceof JavaUAssertExpression)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.getValueArgumentCount() < 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@ public class AssertDetector extends Detector implements UastScanner {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UExpression assertion = node.firstOrNull();
|
UExpression assertion = node.getValueArguments().get(0);
|
||||||
// Allow "assert true"; it's basically a no-op
|
// Allow "assert true"; it's basically a no-op
|
||||||
if (assertion instanceof ULiteralExpression) {
|
if (assertion instanceof ULiteralExpression) {
|
||||||
ULiteralExpression literal = (ULiteralExpression) assertion;
|
ULiteralExpression literal = (ULiteralExpression) assertion;
|
||||||
@@ -114,8 +117,8 @@ public class AssertDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String message
|
String message
|
||||||
= "Assertions are unreliable. Use `BuildConfig.DEBUG` conditional checks instead.";
|
= "Assertions are unreliable. Use `BuildConfig.DEBUG` conditional checks instead.";
|
||||||
context.report(ISSUE, node, UastAndroidUtils.getLocation(node), message);
|
context.report(ISSUE, node, context.getLocation(node), message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
|
|||||||
issues.add(ApiDetector.OVERRIDE);
|
issues.add(ApiDetector.OVERRIDE);
|
||||||
issues.add(ApiDetector.UNSUPPORTED);
|
issues.add(ApiDetector.UNSUPPORTED);
|
||||||
issues.add(AppCompatCallDetector.ISSUE);
|
issues.add(AppCompatCallDetector.ISSUE);
|
||||||
issues.add(AppCompatResourceDetector.ISSUE);
|
|
||||||
issues.add(AssertDetector.ISSUE);
|
issues.add(AssertDetector.ISSUE);
|
||||||
issues.add(CallSuperDetector.ISSUE);
|
issues.add(CallSuperDetector.ISSUE);
|
||||||
issues.add(CipherGetInstanceDetector.ISSUE);
|
issues.add(CipherGetInstanceDetector.ISSUE);
|
||||||
@@ -54,7 +53,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
|
|||||||
issues.add(CustomViewDetector.ISSUE);
|
issues.add(CustomViewDetector.ISSUE);
|
||||||
issues.add(CutPasteDetector.ISSUE);
|
issues.add(CutPasteDetector.ISSUE);
|
||||||
issues.add(DateFormatDetector.DATE_FORMAT);
|
issues.add(DateFormatDetector.DATE_FORMAT);
|
||||||
issues.add(FullBackupContentDetector.ISSUE);
|
|
||||||
issues.add(FragmentDetector.ISSUE);
|
issues.add(FragmentDetector.ISSUE);
|
||||||
issues.add(GetSignaturesDetector.ISSUE);
|
issues.add(GetSignaturesDetector.ISSUE);
|
||||||
issues.add(HandlerDetector.ISSUE);
|
issues.add(HandlerDetector.ISSUE);
|
||||||
@@ -82,7 +80,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
|
|||||||
issues.add(LogDetector.LONG_TAG);
|
issues.add(LogDetector.LONG_TAG);
|
||||||
issues.add(LogDetector.WRONG_TAG);
|
issues.add(LogDetector.WRONG_TAG);
|
||||||
issues.add(MergeRootFrameLayoutDetector.ISSUE);
|
issues.add(MergeRootFrameLayoutDetector.ISSUE);
|
||||||
issues.add(NfcTechListDetector.ISSUE);
|
|
||||||
issues.add(NonInternationalizedSmsDetector.ISSUE);
|
issues.add(NonInternationalizedSmsDetector.ISSUE);
|
||||||
issues.add(OverdrawDetector.ISSUE);
|
issues.add(OverdrawDetector.ISSUE);
|
||||||
issues.add(OverrideConcreteDetector.ISSUE);
|
issues.add(OverrideConcreteDetector.ISSUE);
|
||||||
@@ -117,7 +114,6 @@ public class BuiltinIssueRegistry extends IssueRegistry {
|
|||||||
issues.add(SupportAnnotationDetector.RESOURCE_TYPE);
|
issues.add(SupportAnnotationDetector.RESOURCE_TYPE);
|
||||||
issues.add(SupportAnnotationDetector.THREAD);
|
issues.add(SupportAnnotationDetector.THREAD);
|
||||||
issues.add(SupportAnnotationDetector.TYPE_DEF);
|
issues.add(SupportAnnotationDetector.TYPE_DEF);
|
||||||
issues.add(TitleDetector.ISSUE);
|
|
||||||
issues.add(ToastDetector.ISSUE);
|
issues.add(ToastDetector.ISSUE);
|
||||||
issues.add(UnusedResourceDetector.ISSUE);
|
issues.add(UnusedResourceDetector.ISSUE);
|
||||||
issues.add(UnusedResourceDetector.ISSUE_IDS);
|
issues.add(UnusedResourceDetector.ISSUE_IDS);
|
||||||
|
|||||||
+12
-11
@@ -37,7 +37,6 @@ import java.util.List;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ public class CallSuperDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
CallSuperDetector.class,
|
CallSuperDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Missing call to super */
|
/** Missing call to super */
|
||||||
public static final Issue ISSUE = Issue.create(
|
public static final Issue ISSUE = Issue.create(
|
||||||
@@ -103,7 +102,7 @@ public class CallSuperDetector extends Detector implements UastScanner {
|
|||||||
String methodName = declaration.getName();
|
String methodName = declaration.getName();
|
||||||
String message = "Overriding method should call `super."
|
String message = "Overriding method should call `super."
|
||||||
+ methodName + "`";
|
+ methodName + "`";
|
||||||
Location location = UastAndroidUtils.getLocation(declaration.getNameElement());
|
Location location = context.getLocation(declaration.getNameElement());
|
||||||
context.report(ISSUE, declaration, location, message);
|
context.report(ISSUE, declaration, location, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,12 +147,12 @@ public class CallSuperDetector extends Detector implements UastScanner {
|
|||||||
UFunction superMethod = directSuper;
|
UFunction superMethod = directSuper;
|
||||||
while (superMethod != null) {
|
while (superMethod != null) {
|
||||||
for (UAnnotation annotation : superMethod.getAnnotations()) {
|
for (UAnnotation annotation : superMethod.getAnnotations()) {
|
||||||
annotation = SupportAnnotationDetector.getRelevantAnnotation(context, annotation);
|
annotation = SupportAnnotationDetector.getRelevantAnnotation(annotation, context);
|
||||||
if (annotation != null) {
|
if (annotation != null) {
|
||||||
String signature = annotation.getFqName();
|
String fqName = annotation.getFqName();
|
||||||
if (CALL_SUPER_ANNOTATION.equals(signature)) {
|
if (CALL_SUPER_ANNOTATION.equals(fqName)) {
|
||||||
return directSuper;
|
return directSuper;
|
||||||
} else if (signature != null && signature.endsWith(".OverrideMustInvoke")) {
|
} else if (fqName != null && fqName.endsWith(".OverrideMustInvoke")) {
|
||||||
// Handle findbugs annotation on the fly too
|
// Handle findbugs annotation on the fly too
|
||||||
return directSuper;
|
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 */
|
/** Visits a method and determines whether the method calls its super method */
|
||||||
private static class SuperCallVisitor extends UastVisitor {
|
private static class SuperCallVisitor extends UastVisitor {
|
||||||
private final UastAndroidContext mContext;
|
private final UastAndroidContext mContext;
|
||||||
private final String mMethodFqName;
|
private final String mMethodContainingClassFqName;
|
||||||
|
private final String mMethodName;
|
||||||
private boolean mCallsSuper;
|
private boolean mCallsSuper;
|
||||||
|
|
||||||
public static boolean callsSuper(
|
public static boolean callsSuper(
|
||||||
@@ -181,9 +181,10 @@ public class CallSuperDetector extends Detector implements UastScanner {
|
|||||||
return visitor.mCallsSuper;
|
return visitor.mCallsSuper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SuperCallVisitor(@NonNull UastAndroidContext context, @NonNull UFunction method) {
|
private SuperCallVisitor(@NonNull UastAndroidContext context, @NonNull UFunction function) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mMethodFqName = UastUtils.getFqName(method);
|
mMethodContainingClassFqName = UastUtils.getContainingClassOrEmpty(function).getFqName();
|
||||||
|
mMethodName = function.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -193,7 +194,7 @@ public class CallSuperDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
if (receiver instanceof USuperExpression && selector instanceof UCallExpression) {
|
if (receiver instanceof USuperExpression && selector instanceof UCallExpression) {
|
||||||
UFunction resolvedFunction = ((UCallExpression) selector).resolve(mContext);
|
UFunction resolvedFunction = ((UCallExpression) selector).resolve(mContext);
|
||||||
if (mMethodFqName.equals(UastUtils.getFqName(resolvedFunction))) {
|
if (resolvedFunction != null && resolvedFunction.matchesNameWithContaining(mMethodContainingClassFqName, mMethodName)) {
|
||||||
mCallsSuper = true;
|
mCallsSuper = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -32,7 +32,6 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +49,7 @@ public class CipherGetInstanceDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
CipherGetInstanceDetector.class,
|
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 CIPHER = "javax.crypto.Cipher"; //$NON-NLS-1$
|
||||||
private static final String GET_INSTANCE = "getInstance"; //$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
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
UClass containingClass = UastUtils.getContainingClass(node);
|
UClass containingClass = UastUtils.getContainingClass(node);
|
||||||
if (containingClass == null || !containingClass.isSubclassOf(CIPHER)) {
|
if (containingClass == null || !containingClass.isSubclassOf(CIPHER)) {
|
||||||
return;
|
return;
|
||||||
@@ -107,13 +106,13 @@ public class CipherGetInstanceDetector extends Detector implements UastScanner {
|
|||||||
if (ALGORITHM_ONLY.contains(value)) {
|
if (ALGORITHM_ONLY.contains(value)) {
|
||||||
String message = "`Cipher.getInstance` should not be called without setting the"
|
String message = "`Cipher.getInstance` should not be called without setting the"
|
||||||
+ " encryption mode and padding";
|
+ " 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)) {
|
} else if (value.contains(ECB)) {
|
||||||
String message = "ECB encryption mode should not be used";
|
String message = "ECB encryption mode should not be used";
|
||||||
if (includeValue) {
|
if (includeValue) {
|
||||||
message += " (was \"" + value + "\")";
|
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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ public class CleanupDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
CleanupDetector.class,
|
CleanupDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Problems with missing recycle calls */
|
/** Problems with missing recycle calls */
|
||||||
public static final Issue RECYCLE_RESOURCE = Issue.create(
|
public static final Issue RECYCLE_RESOURCE = Issue.create(
|
||||||
@@ -156,8 +155,8 @@ public class CleanupDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
if (node.functionNameMatches(BEGIN_TRANSACTION)) {
|
if (node.matchesFunctionName(BEGIN_TRANSACTION)) {
|
||||||
checkTransactionCommits(context, node);
|
checkTransactionCommits(context, node);
|
||||||
} else {
|
} else {
|
||||||
checkResourceRecycled(context, node, node.getFunctionName());
|
checkResourceRecycled(context, node, node.getFunctionName());
|
||||||
@@ -245,7 +244,7 @@ public class CleanupDetector extends Detector implements UastScanner {
|
|||||||
FinishVisitor visitor = new FinishVisitor(context, boundVariable) {
|
FinishVisitor visitor = new FinishVisitor(context, boundVariable) {
|
||||||
@Override
|
@Override
|
||||||
protected boolean isCleanupCall(@NonNull UCallExpression call) {
|
protected boolean isCleanupCall(@NonNull UCallExpression call) {
|
||||||
if (!call.functionNameMatches(recycleName)) {
|
if (!call.matchesFunctionName(recycleName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
UDeclaration resolved = call.resolve(mContext);
|
UDeclaration resolved = call.resolve(mContext);
|
||||||
@@ -286,7 +285,7 @@ public class CleanupDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
UElement locationNode = node instanceof UCallExpression ?
|
UElement locationNode = node instanceof UCallExpression ?
|
||||||
((UCallExpression) node).getFunctionNameElement() : node;
|
((UCallExpression) node).getFunctionNameElement() : node;
|
||||||
Location location = UastAndroidUtils.getLocation(locationNode);
|
Location location = context.getLocation(locationNode);
|
||||||
context.report(RECYCLE_RESOURCE, node, location, message);
|
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";
|
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);
|
message);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -383,15 +382,15 @@ public class CleanupDetector extends Detector implements UastScanner {
|
|||||||
private static boolean isTransactionCommitMethodCall(@NonNull UastAndroidContext context,
|
private static boolean isTransactionCommitMethodCall(@NonNull UastAndroidContext context,
|
||||||
@NonNull UCallExpression call) {
|
@NonNull UCallExpression call) {
|
||||||
|
|
||||||
return (call.functionNameMatches(COMMIT) || call.functionNameMatches(COMMIT_ALLOWING_LOSS)) &&
|
return (call.matchesFunctionName(COMMIT) || call.matchesFunctionName(COMMIT_ALLOWING_LOSS)) &&
|
||||||
isMethodOnFragmentClass(context, call,
|
isMethodOnFragmentClass(context, call,
|
||||||
FRAGMENT_TRANSACTION_CLS,
|
FRAGMENT_TRANSACTION_CLS,
|
||||||
FRAGMENT_TRANSACTION_V4_CLS);
|
FRAGMENT_TRANSACTION_V4_CLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isShowFragmentMethodCall(@NonNull UastAndroidContext context,
|
private static boolean isShowFragmentMethodCall(@NonNull UastAndroidContext context,
|
||||||
@NonNull UCallExpression call) {
|
@NonNull UCallExpression call) {
|
||||||
return call.functionNameMatches(SHOW)
|
return call.matchesFunctionName(SHOW)
|
||||||
&& isMethodOnFragmentClass(context, call,
|
&& isMethodOnFragmentClass(context, call,
|
||||||
DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT);
|
DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT);
|
||||||
}
|
}
|
||||||
@@ -435,8 +434,8 @@ public class CleanupDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
private static boolean isBeginTransaction(@NonNull UastAndroidContext context,
|
private static boolean isBeginTransaction(@NonNull UastAndroidContext context,
|
||||||
@NonNull UCallExpression node) {
|
@NonNull UCallExpression node) {
|
||||||
assert node.functionNameMatches(BEGIN_TRANSACTION) : node.renderString();
|
assert node.matchesFunctionName(BEGIN_TRANSACTION) : node.renderString();
|
||||||
if (node.functionNameMatches(BEGIN_TRANSACTION)) {
|
if (node.matchesFunctionName(BEGIN_TRANSACTION)) {
|
||||||
UFunction method = node.resolve(context);
|
UFunction method = node.resolve(context);
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
UClass containingClass = UastUtils.getContainingClassOrEmpty(method);
|
UClass containingClass = UastUtils.getContainingClassOrEmpty(method);
|
||||||
@@ -565,15 +564,13 @@ public class CleanupDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
|
public boolean visitReturnExpression(@NotNull UReturnExpression node) {
|
||||||
if (node.getKind() == UastSpecialExpressionKind.RETURN) {
|
UExpression value = node.getReturnExpression();
|
||||||
UExpression value = node.firstOrNull();
|
if (value instanceof USimpleReferenceExpression) {
|
||||||
if (value instanceof USimpleReferenceExpression) {
|
UDeclaration resolved = ((USimpleReferenceExpression) value).resolve(mContext);
|
||||||
UDeclaration resolved = ((USimpleReferenceExpression) value).resolve(mContext);
|
//noinspection SuspiciousMethodCalls
|
||||||
//noinspection SuspiciousMethodCalls
|
if (resolved != null && mVariables.contains(resolved)) {
|
||||||
if (resolved != null && mVariables.contains(resolved)) {
|
mEscapes = true;
|
||||||
mEscapes = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class CommentDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
CommentDetector.class,
|
CommentDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Looks for hidden code */
|
/** Looks for hidden code */
|
||||||
public static final Issue EASTER_EGG = Issue.create(
|
public static final Issue EASTER_EGG = Issue.create(
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ public class CustomViewDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
CustomViewDetector.class,
|
CustomViewDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Mismatched style and class names */
|
/** Mismatched style and class names */
|
||||||
public static final Issue ISSUE = Issue.create(
|
public static final Issue ISSUE = Issue.create(
|
||||||
@@ -92,7 +91,7 @@ public class CustomViewDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
if (node.getParent() instanceof UExpression) {
|
if (node.getParent() instanceof UExpression) {
|
||||||
if (!context.getLintContext().isContextMethod(node)) {
|
if (!context.getLintContext().isContextMethod(node)) {
|
||||||
return;
|
return;
|
||||||
@@ -137,7 +136,7 @@ public class CustomViewDetector extends Detector implements UastScanner {
|
|||||||
+ "should have the same name (various editor features rely on "
|
+ "should have the same name (various editor features rely on "
|
||||||
+ "this convention)",
|
+ "this convention)",
|
||||||
className, styleableName);
|
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)) {
|
} else if (resolvedClass.isSubclassOf(CLASS_VIEWGROUP + DOT_LAYOUT_PARAMS)) {
|
||||||
UClass outer = UastUtils.getContainingClass(resolvedClass);
|
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`. "
|
+ "class (`%3$s`) plus \"`_Layout`\", e.g. `%4$s`. "
|
||||||
+ "(Various editor features rely on this convention.)",
|
+ "(Various editor features rely on this convention.)",
|
||||||
styleableName, className, layoutClassName, expectedName);
|
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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ public class CutPasteDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
CutPasteDetector.class,
|
CutPasteDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
private UFunction mLastMethod;
|
private UFunction mLastMethod;
|
||||||
private Map<String, UCallExpression> mIds;
|
private Map<String, UCallExpression> mIds;
|
||||||
@@ -88,7 +87,7 @@ public class CutPasteDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
String lhs = getLhs(node);
|
String lhs = getLhs(node);
|
||||||
if (lhs == null) {
|
if (lhs == null) {
|
||||||
return;
|
return;
|
||||||
@@ -129,8 +128,8 @@ public class CutPasteDetector extends Detector implements UastScanner {
|
|||||||
if (!isReachableFrom(method, earlierCall, node)) {
|
if (!isReachableFrom(method, earlierCall, node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Location location = UastAndroidUtils.getLocation(node);
|
Location location = context.getLocation(node);
|
||||||
Location secondary = UastAndroidUtils.getLocation(earlierCall);
|
Location secondary = context.getLocation(earlierCall);
|
||||||
if (location != null && secondary != null) {
|
if (location != null && secondary != null) {
|
||||||
secondary.setMessage("First usage here");
|
secondary.setMessage("First usage here");
|
||||||
location.setSecondary(secondary);
|
location.setSecondary(secondary);
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import org.jetbrains.uast.UFunction;
|
|||||||
import org.jetbrains.uast.UCallExpression;
|
import org.jetbrains.uast.UCallExpression;
|
||||||
import org.jetbrains.uast.UType;
|
import org.jetbrains.uast.UType;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +43,7 @@ public class DateFormatDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
DateFormatDetector.class,
|
DateFormatDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Constructing SimpleDateFormat without an explicit locale */
|
/** Constructing SimpleDateFormat without an explicit locale */
|
||||||
public static final Issue DATE_FORMAT = Issue.create(
|
public static final Issue DATE_FORMAT = Issue.create(
|
||||||
@@ -93,7 +92,7 @@ public class DateFormatDetector extends Detector implements UastScanner {
|
|||||||
@Override
|
@Override
|
||||||
public void visitConstructor(UastAndroidContext context, UCallExpression functionCall, UFunction constructor) {
|
public void visitConstructor(UastAndroidContext context, UCallExpression functionCall, UFunction constructor) {
|
||||||
if (!specifiesLocale(constructor)) {
|
if (!specifiesLocale(constructor)) {
|
||||||
Location location = UastAndroidUtils.getLocation(functionCall);
|
Location location = context.getLocation(functionCall);
|
||||||
String message =
|
String message =
|
||||||
"To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, " +
|
"To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, " +
|
||||||
"or `getTimeInstance()`, or use `new SimpleDateFormat(String template, " +
|
"or `getTimeInstance()`, or use `new SimpleDateFormat(String template, " +
|
||||||
|
|||||||
@@ -33,10 +33,8 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.kinds.UastClassKind;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that Fragment subclasses can be instantiated via
|
* Checks that Fragment subclasses can be instantiated via
|
||||||
@@ -66,7 +64,7 @@ public class FragmentDetector extends Detector implements UastScanner {
|
|||||||
Severity.FATAL,
|
Severity.FATAL,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
FragmentDetector.class,
|
FragmentDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE)
|
Scope.SOURCE_FILE_SCOPE)
|
||||||
).addMoreInfo(
|
).addMoreInfo(
|
||||||
"http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$
|
"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);
|
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
|
@Override
|
||||||
public void visitClass(UastAndroidContext context, UClass cls) {
|
public void visitClass(UastAndroidContext context, UClass cls) {
|
||||||
if (cls.hasModifier(UastModifier.ABSTRACT) || cls.getKind() != UastClassKind.CLASS) {
|
if (cls.hasModifier(UastModifier.ABSTRACT) || cls.getKind() != UastClassKind.CLASS) {
|
||||||
@@ -98,14 +106,14 @@ public class FragmentDetector extends Detector implements UastScanner {
|
|||||||
if (!cls.getVisibility().isPublic()) {
|
if (!cls.getVisibility().isPublic()) {
|
||||||
String message = String.format("This fragment class should be public (%1$s)",
|
String message = String.format("This fragment class should be public (%1$s)",
|
||||||
cls.getFqName());
|
cls.getFqName());
|
||||||
context.report(ISSUE, cls, UastAndroidUtils.getLocation(cls.getNameElement()), message);
|
context.report(ISSUE, cls, context.getLocation(cls.getNameElement()), message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UastUtils.getContainingClass(cls) != null && !cls.hasModifier(UastModifier.STATIC)) {
|
if (UastUtils.getContainingClass(cls) != null && !cls.hasModifier(UastModifier.STATIC)) {
|
||||||
String message = String.format(
|
String message = String.format(
|
||||||
"This fragment inner class should be static (%1$s)", cls.getName());
|
"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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,11 +122,11 @@ public class FragmentDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
for (UFunction constructor : cls.getConstructors()) {
|
for (UFunction constructor : cls.getConstructors()) {
|
||||||
hasConstructor = true;
|
hasConstructor = true;
|
||||||
if (constructor.getValueParameterCount() == 0) {
|
if (constructor.getValueParameterCount() == 0 || isAllParametersWithDefaultValue(constructor)) {
|
||||||
if (constructor.getVisibility().isPublic()) {
|
if (constructor.getVisibility().isPublic()) {
|
||||||
hasDefaultConstructor = true;
|
hasDefaultConstructor = true;
|
||||||
} else {
|
} else {
|
||||||
Location location = UastAndroidUtils.getLocation(constructor.getNameElement());
|
Location location = context.getLocation(constructor.getNameElement());
|
||||||
context.report(ISSUE, constructor, location,
|
context.report(ISSUE, constructor, location,
|
||||||
"The default constructor must be public");
|
"The default constructor must be public");
|
||||||
// Also mark that we have a constructor so we don't complain again
|
// 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;
|
hasDefaultConstructor = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Location location = UastAndroidUtils.getLocation(constructor.getNameElement());
|
Location location = context.getLocation(constructor.getNameElement());
|
||||||
// TODO: Use separate issue for this which isn't an error
|
// TODO: Use separate issue for this which isn't an error
|
||||||
String message = "Avoid non-default constructors in fragments: "
|
String message = "Avoid non-default constructors in fragments: "
|
||||||
+ "use a default constructor plus "
|
+ "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 " +
|
"This fragment should provide a default constructor (a public " +
|
||||||
"constructor with no arguments) (`%1$s`)",
|
"constructor with no arguments) (`%1$s`)",
|
||||||
cls.getName());
|
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.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
public class GetSignaturesDetector extends Detector implements UastScanner {
|
public class GetSignaturesDetector extends Detector implements UastScanner {
|
||||||
@@ -47,7 +46,7 @@ public class GetSignaturesDetector extends Detector implements UastScanner {
|
|||||||
Severity.INFORMATIONAL,
|
Severity.INFORMATIONAL,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
GetSignaturesDetector.class,
|
GetSignaturesDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE))
|
Scope.SOURCE_FILE_SCOPE))
|
||||||
.addMoreInfo("https://bluebox.com/technical/android-fake-id-vulnerability/");
|
.addMoreInfo("https://bluebox.com/technical/android-fake-id-vulnerability/");
|
||||||
|
|
||||||
private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager"; //$NON-NLS-1$
|
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
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
UFunction resolved = node.resolve(context);
|
UFunction resolved = node.resolve(context);
|
||||||
|
|
||||||
if (resolved == null ||
|
if (resolved == null ||
|
||||||
@@ -86,7 +85,7 @@ public class GetSignaturesDetector extends Detector implements UastScanner {
|
|||||||
private static void maybeReportIssue(
|
private static void maybeReportIssue(
|
||||||
int flagValue, UastAndroidContext context, UCallExpression node) {
|
int flagValue, UastAndroidContext context, UCallExpression node) {
|
||||||
if ((flagValue & GET_SIGNATURES_FLAG) != 0) {
|
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 "
|
"Reading app signatures from getPackageInfo: The app signatures "
|
||||||
+ "could be exploited if not validated properly; "
|
+ "could be exploited if not validated properly; "
|
||||||
+ "see issue explanation for details.");
|
+ "see issue explanation for details.");
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.kinds.UastClassKind;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that Handler implementations are top level classes or static.
|
* Checks that Handler implementations are top level classes or static.
|
||||||
@@ -60,7 +58,7 @@ public class HandlerDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
HandlerDetector.class,
|
HandlerDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
private static final String LOOPER_CLS = "android.os.Looper";
|
private static final String LOOPER_CLS = "android.os.Looper";
|
||||||
private static final String HANDLER_CLS = "android.os.Handler";
|
private static final String HANDLER_CLS = "android.os.Handler";
|
||||||
@@ -99,7 +97,7 @@ public class HandlerDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UElement locationNode = node.getNameElement();
|
UElement locationNode = node.getNameElement();
|
||||||
Location location = UastAndroidUtils.getLocation(locationNode);
|
Location location = context.getLocation(locationNode);
|
||||||
context.report(ISSUE, node, location, String.format(
|
context.report(ISSUE, node, location, String.format(
|
||||||
"This Handler class should be static or leaks might occur (%1$s)",
|
"This Handler class should be static or leaks might occur (%1$s)",
|
||||||
node.getName()));
|
node.getName()));
|
||||||
|
|||||||
@@ -1958,6 +1958,7 @@ public class IconDetector extends ResourceXmlDetector implements UastScanner {
|
|||||||
// ---- Implements UastScanner ----
|
// ---- Implements UastScanner ----
|
||||||
|
|
||||||
private static final String NOTIFICATION_CLASS = "Notification"; //$NON-NLS-1$
|
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 =
|
private static final String NOTIFICATION_CLASS_FQNAME =
|
||||||
"android.app.Notification"; //$NON-NLS-1$
|
"android.app.Notification"; //$NON-NLS-1$
|
||||||
@@ -2006,6 +2007,8 @@ public class IconDetector extends ResourceXmlDetector implements UastScanner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> parts = UastUtils.asQualifiedIdentifiers(UastUtils.getQualifiedCallElement(node));
|
||||||
|
|
||||||
String typeName = reference.getIdentifier();
|
String typeName = reference.getIdentifier();
|
||||||
if (NOTIFICATION_CLASS.equals(typeName)) {
|
if (NOTIFICATION_CLASS.equals(typeName)) {
|
||||||
List<UExpression> args = node.getValueArguments();
|
List<UExpression> args = node.getValueArguments();
|
||||||
@@ -2028,10 +2031,16 @@ public class IconDetector extends ResourceXmlDetector implements UastScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (BUILDER_CLASS.equals(typeName)) {
|
} else if (BUILDER_CLASS.equals(typeName)) {
|
||||||
UClass resolvedClass = reference.resolveClass(mContext);
|
boolean isBuilder = false;
|
||||||
boolean isBuilder = resolvedClass != null
|
if (parts != null && parts.size() == 1) {
|
||||||
&& (resolvedClass.matchesFqName(NOTIFICATION_CLASS_FQNAME)
|
isBuilder = true;
|
||||||
|| resolvedClass.matchesFqName(NOTIFICATION_COMPAT_CLASS_FQNAME));
|
} 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) {
|
if (isBuilder) {
|
||||||
UFunction method = UastUtils.getContainingFunction(node);
|
UFunction method = UastUtils.getContainingFunction(node);
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
|
|||||||
+2
-2
@@ -56,7 +56,7 @@ public class JavaPerformanceDetector extends Detector implements UastScanner {
|
|||||||
private static final Implementation
|
private static final Implementation
|
||||||
IMPLEMENTATION = new Implementation(
|
IMPLEMENTATION = new Implementation(
|
||||||
JavaPerformanceDetector.class,
|
JavaPerformanceDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Allocating objects during a paint method */
|
/** Allocating objects during a paint method */
|
||||||
public static final Issue PAINT_ALLOC = Issue.create(
|
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
|
// 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
|
// mInDraw as true, in case we've left it and we're in a static
|
||||||
// block or something:
|
// block or something:
|
||||||
|
|||||||
+9
-25
@@ -34,7 +34,6 @@ import java.util.Map;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
|
|||||||
Severity.ERROR,
|
Severity.ERROR,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
JavaScriptInterfaceDetector.class,
|
JavaScriptInterfaceDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE))
|
Scope.SOURCE_FILE_SCOPE))
|
||||||
.addMoreInfo(
|
.addMoreInfo(
|
||||||
"http://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)"); //$NON-NLS-1$
|
"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
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
if (context.getLintContext().getMainProject().getTargetSdk() < 17) {
|
if (context.getLintContext().getMainProject().getTargetSdk() < 17) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -146,7 +145,7 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Location location = UastAndroidUtils.getLocation(node.getFunctionNameElement());
|
Location location = context.getLocation(node.getFunctionNameElement());
|
||||||
String message = String.format(
|
String message = String.format(
|
||||||
"None of the methods in the added interface (%1$s) have been annotated " +
|
"None of the methods in the added interface (%1$s) have been annotated " +
|
||||||
"with `@android.webkit.JavascriptInterface`; they will not " +
|
"with `@android.webkit.JavascriptInterface`; they will not " +
|
||||||
@@ -216,29 +215,19 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitCallExpression(@NotNull UCallExpression node) {
|
public void afterVisitCallExpression(@NotNull UCallExpression node) {
|
||||||
if (node.getKind() == UastCallKind.FUNCTION_CALL && node == mTargetCall) {
|
if (node.getKind() == UastCallKind.FUNCTION_CALL && node == mTargetCall) {
|
||||||
mFoundCall = true;
|
mFoundCall = true;
|
||||||
} else if (node.getKind() == UastCallKind.CONSTRUCTOR_CALL) {
|
} else if (node.getKind() == UastCallKind.CONSTRUCTOR_CALL) {
|
||||||
UFunction resolved = node.resolve(mContext);
|
UFunction resolved = node.resolve(mContext);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
mTypes.put(node, UastUtils.getContainingClass(resolved));
|
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
|
@Override
|
||||||
public boolean visitSimpleReferenceExpression(@NotNull USimpleReferenceExpression node) {
|
public void afterVisitSimpleReferenceExpression(@NotNull USimpleReferenceExpression node) {
|
||||||
if (mTypes.get(node) == null) {
|
if (mTypes.get(node) == null) {
|
||||||
UElement resolved = node.resolve(mContext);
|
UElement resolved = node.resolve(mContext);
|
||||||
if (resolved instanceof UVariable) {
|
if (resolved instanceof UVariable) {
|
||||||
@@ -248,11 +237,10 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitBinaryExpression(@NotNull UBinaryExpression node) {
|
public void afterVisitBinaryExpression(@NotNull UBinaryExpression node) {
|
||||||
if (node.getOperator() == UastBinaryOperator.ASSIGN) {
|
if (node.getOperator() == UastBinaryOperator.ASSIGN) {
|
||||||
UExpression rhs = node.getRightOperand();
|
UExpression rhs = node.getRightOperand();
|
||||||
UClass resolvedClass = mTypes.get(rhs);
|
UClass resolvedClass = mTypes.get(rhs);
|
||||||
@@ -267,11 +255,10 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitIfExpression(@NotNull UIfExpression node) {
|
public void afterVisitIfExpression(@NotNull UIfExpression node) {
|
||||||
if (node.isTernary()) {
|
if (node.isTernary()) {
|
||||||
UClass resolvedClass = mTypes.get(node.getThenBranch());
|
UClass resolvedClass = mTypes.get(node.getThenBranch());
|
||||||
if (resolvedClass == null) {
|
if (resolvedClass == null) {
|
||||||
@@ -281,11 +268,10 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
|
|||||||
mTypes.put(node, resolvedClass);
|
mTypes.put(node, resolvedClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitVariable(@NotNull UVariable node) {
|
public void afterVisitVariable(@NotNull UVariable node) {
|
||||||
UExpression initializer = node.getInitializer();
|
UExpression initializer = node.getInitializer();
|
||||||
if (initializer != null) {
|
if (initializer != null) {
|
||||||
UClass resolvedClass = mTypes.get(initializer);
|
UClass resolvedClass = mTypes.get(initializer);
|
||||||
@@ -294,16 +280,14 @@ public class JavaScriptInterfaceDetector extends Detector implements UastScanner
|
|||||||
mVariableTypes.put(node, resolvedClass);
|
mVariableTypes.put(node, resolvedClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitBinaryExpressionWithType(@NotNull UBinaryExpressionWithType node) {
|
public void afterVisitBinaryExpressionWithType(@NotNull UBinaryExpressionWithType node) {
|
||||||
UClass resolvedClass = mTypes.get(node);
|
UClass resolvedClass = mTypes.get(node);
|
||||||
if (resolvedClass != null) {
|
if (resolvedClass != null) {
|
||||||
mTypes.put(node, resolvedClass);
|
mTypes.put(node, resolvedClass);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -427,7 +427,7 @@ public class LayoutConsistencyDetector extends LayoutDetector implements UastSca
|
|||||||
return locations.get(0);
|
return locations.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Implements JavaScanner ----
|
// ---- Implements UastScanner ----
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean appliesToResourceRefs() {
|
public boolean appliesToResourceRefs() {
|
||||||
|
|||||||
+5
-6
@@ -46,7 +46,6 @@ import com.google.common.collect.Lists;
|
|||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.kxml2.io.KXmlParser;
|
import org.kxml2.io.KXmlParser;
|
||||||
@@ -75,7 +74,7 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
|
|||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
LayoutInflationDetector.class,
|
LayoutInflationDetector.class,
|
||||||
Scope.SOURCE_AND_RESOURCE_FILES,
|
Scope.SOURCE_AND_RESOURCE_FILES,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Passing in a null parent to a layout inflater */
|
/** Passing in a null parent to a layout inflater */
|
||||||
public static final Issue ISSUE = Issue.create(
|
public static final Issue ISSUE = Issue.create(
|
||||||
@@ -153,7 +152,7 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
assert ViewHolderDetector.INFLATE.equals(node.getFunctionName());
|
assert ViewHolderDetector.INFLATE.equals(node.getFunctionName());
|
||||||
if (node instanceof USimpleReferenceExpression) {
|
if (node instanceof USimpleReferenceExpression) {
|
||||||
return;
|
return;
|
||||||
@@ -169,8 +168,8 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UQualifiedExpression select = (UQualifiedExpression) first;
|
UQualifiedExpression select = (UQualifiedExpression) first;
|
||||||
UExpression selector = select.getSelector();
|
|
||||||
UExpression receiver = select.getReceiver();
|
UExpression receiver = select.getReceiver();
|
||||||
|
UExpression selector = select.getSelector();
|
||||||
if (receiver instanceof UQualifiedExpression && selector instanceof USimpleReferenceExpression) {
|
if (receiver instanceof UQualifiedExpression && selector instanceof USimpleReferenceExpression) {
|
||||||
UQualifiedExpression rLayout = (UQualifiedExpression) receiver;
|
UQualifiedExpression rLayout = (UQualifiedExpression) receiver;
|
||||||
if (rLayout.selectorMatches(ResourceType.LAYOUT.getName()) &&
|
if (rLayout.selectorMatches(ResourceType.LAYOUT.getName()) &&
|
||||||
@@ -186,11 +185,11 @@ public class LayoutInflationDetector extends LayoutDetector implements UastScann
|
|||||||
if (mPendingErrors == null) {
|
if (mPendingErrors == null) {
|
||||||
mPendingErrors = Lists.newArrayList();
|
mPendingErrors = Lists.newArrayList();
|
||||||
}
|
}
|
||||||
Location location = UastAndroidUtils.getLocation(second);
|
Location location = context.getLocation(second);
|
||||||
mPendingErrors.add(Pair.of(layoutName, location));
|
mPendingErrors.add(Pair.of(layoutName, location));
|
||||||
}
|
}
|
||||||
} else if (hasLayoutParams(lintContext, layoutName)) {
|
} 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.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +42,7 @@ import org.jetbrains.uast.check.UastScanner;
|
|||||||
*/
|
*/
|
||||||
public class LogDetector extends Detector implements UastScanner {
|
public class LogDetector extends Detector implements UastScanner {
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
LogDetector.class, Scope.JAVA_FILE_SCOPE);
|
LogDetector.class, Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
|
|
||||||
/** Log call missing surrounding if */
|
/** Log call missing surrounding if */
|
||||||
@@ -113,7 +112,7 @@ public class LogDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
UFunction method = node.resolve(context);
|
UFunction method = node.resolve(context);
|
||||||
if (method == null) {
|
if (method == null) {
|
||||||
return;
|
return;
|
||||||
@@ -137,7 +136,7 @@ public class LogDetector extends Detector implements UastScanner {
|
|||||||
"conditional: surround with `if (Log.isLoggable(...))` or " +
|
"conditional: surround with `if (Log.isLoggable(...))` or " +
|
||||||
"`if (BuildConfig.DEBUG) { ... }`",
|
"`if (BuildConfig.DEBUG) { ... }`",
|
||||||
node.getFunctionName());
|
node.getFunctionName());
|
||||||
context.report(CONDITIONAL, node, UastAndroidUtils.getLocation(node), message);
|
context.report(CONDITIONAL, node, context.getLocation(node), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check tag length
|
// Check tag length
|
||||||
@@ -156,7 +155,7 @@ public class LogDetector extends Detector implements UastScanner {
|
|||||||
String message = String.format(
|
String message = String.format(
|
||||||
"The logging tag can be at most 23 characters, was %1$d (%2$s)",
|
"The logging tag can be at most 23 characters, was %1$d (%2$s)",
|
||||||
tag.length(), tag);
|
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) {
|
if (!isOk) {
|
||||||
Location location = UastAndroidUtils.getLocation(logTag);
|
Location location = context.getLocation(logTag);
|
||||||
Location alternate = UastAndroidUtils.getLocation(isLoggableTag);
|
Location alternate = context.getLocation(isLoggableTag);
|
||||||
if (location != null && alternate != null) {
|
if (location != null && alternate != null) {
|
||||||
alternate.setMessage("Conflicting tag");
|
alternate.setMessage("Conflicting tag");
|
||||||
location.setSecondary(alternate);
|
location.setSecondary(alternate);
|
||||||
@@ -305,7 +304,10 @@ public class LogDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
String levelString = isLoggableLevel.toString();
|
String levelString = isLoggableLevel.toString();
|
||||||
if (isLoggableLevel instanceof UQualifiedExpression) {
|
if (isLoggableLevel instanceof UQualifiedExpression) {
|
||||||
levelString = ((UQualifiedExpression)isLoggableLevel).getSelector().renderString();
|
String identifier = ((UQualifiedExpression)isLoggableLevel).getSelectorAsIdentifier();
|
||||||
|
if (identifier != null) {
|
||||||
|
levelString = identifier;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (levelString.isEmpty()) {
|
if (levelString.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -331,8 +333,8 @@ public class LogDetector extends Detector implements UastScanner {
|
|||||||
"Mismatched logging levels: when checking `isLoggable` level `%1$s`, the " +
|
"Mismatched logging levels: when checking `isLoggable` level `%1$s`, the " +
|
||||||
"corresponding log call should be `Log.%2$s`, not `Log.%3$s`",
|
"corresponding log call should be `Log.%2$s`, not `Log.%3$s`",
|
||||||
levelString, expectedCall, logCallName);
|
levelString, expectedCall, logCallName);
|
||||||
Location location = UastAndroidUtils.getLocation(logCall.getFunctionNameElement());
|
Location location = context.getLocation(logCall.getFunctionNameElement());
|
||||||
Location alternate = UastAndroidUtils.getLocation(isLoggableLevel);
|
Location alternate = context.getLocation(isLoggableLevel);
|
||||||
if (location != null && alternate != null) {
|
if (location != null && alternate != null) {
|
||||||
alternate.setMessage("Conflicting tag");
|
alternate.setMessage("Conflicting tag");
|
||||||
location.setSecondary(alternate);
|
location.setSecondary(alternate);
|
||||||
|
|||||||
+2
-1
@@ -45,6 +45,7 @@ import com.android.utils.Pair;
|
|||||||
import org.jetbrains.uast.UExpression;
|
import org.jetbrains.uast.UExpression;
|
||||||
import org.jetbrains.uast.UCallExpression;
|
import org.jetbrains.uast.UCallExpression;
|
||||||
import org.jetbrains.uast.UQualifiedExpression;
|
import org.jetbrains.uast.UQualifiedExpression;
|
||||||
|
import org.jetbrains.uast.UastUtils;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@@ -196,7 +197,7 @@ public class MergeRootFrameLayoutDetector extends LayoutDetector implements Uast
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
if (node.getValueArgumentCount() != 1) {
|
if (node.getValueArgumentCount() != 1) {
|
||||||
return;
|
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.UCallExpression;
|
||||||
import org.jetbrains.uast.ULiteralExpression;
|
import org.jetbrains.uast.ULiteralExpression;
|
||||||
import org.jetbrains.uast.USimpleReferenceExpression;
|
import org.jetbrains.uast.USimpleReferenceExpression;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ public class NonInternationalizedSmsDetector extends Detector implements UastSca
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
NonInternationalizedSmsDetector.class,
|
NonInternationalizedSmsDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
|
|
||||||
/** Constructs a new {@link NonInternationalizedSmsDetector} check */
|
/** Constructs a new {@link NonInternationalizedSmsDetector} check */
|
||||||
@@ -77,15 +76,11 @@ public class NonInternationalizedSmsDetector extends Detector implements UastSca
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
String functionName = node.getFunctionName();
|
String functionName = node.getFunctionName();
|
||||||
|
|
||||||
assert "sendTextMessage".equals(functionName) || //$NON-NLS-1$
|
assert "sendTextMessage".equals(functionName) || //$NON-NLS-1$
|
||||||
"sendMultipartTextMessage".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();
|
List<UExpression> args = node.getValueArguments();
|
||||||
if (args.size() == 5) {
|
if (args.size() == 5) {
|
||||||
@@ -95,7 +90,7 @@ public class NonInternationalizedSmsDetector extends Detector implements UastSca
|
|||||||
String number = (String) ((ULiteralExpression) destinationAddress).getValue();
|
String number = (String) ((ULiteralExpression) destinationAddress).getValue();
|
||||||
|
|
||||||
if (number != null && !number.startsWith("+")) { //$NON-NLS-1$
|
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 " +
|
"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 " +
|
"with a + and a country code or restrict the code invocation to people in the country " +
|
||||||
"you are targeting.");
|
"you are targeting.");
|
||||||
|
|||||||
@@ -486,16 +486,7 @@ public class OverdrawDetector extends LayoutDetector implements UastScanner {
|
|||||||
String name = node.getName();
|
String name = node.getName();
|
||||||
if (mActivities != null && mActivities.contains(mClassFqn) || name.endsWith(ACTIVITY)
|
if (mActivities != null && mActivities.contains(mClassFqn) || name.endsWith(ACTIVITY)
|
||||||
|| node.isSubclassOf(CLASS_ACTIVITY)) {
|
|| node.isSubclassOf(CLASS_ACTIVITY)) {
|
||||||
String packageName = "";
|
mClassFqn = node.getFqName();
|
||||||
if (node.getParent() instanceof UFile) {
|
|
||||||
UFile file = (UFile) node.getParent();
|
|
||||||
if (file.getPackageFqName() == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
packageName = file.getPackageFqName();
|
|
||||||
}
|
|
||||||
mClassFqn = (!packageName.isEmpty() ? (packageName + '.') : "") + name;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ public class OverrideConcreteDetector extends Detector implements UastScanner {
|
|||||||
Severity.FATAL,
|
Severity.FATAL,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
OverrideConcreteDetector.class,
|
OverrideConcreteDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE)
|
Scope.SOURCE_FILE_SCOPE)
|
||||||
);
|
);
|
||||||
|
|
||||||
// This check is currently hardcoded for the specific case of the
|
// 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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.kinds.UastClassKind;
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +51,7 @@ public class ParcelDetector extends Detector implements UastScanner {
|
|||||||
Severity.ERROR,
|
Severity.ERROR,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
ParcelDetector.class,
|
ParcelDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE))
|
Scope.SOURCE_FILE_SCOPE))
|
||||||
.addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html");
|
.addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html");
|
||||||
|
|
||||||
/** Constructs a new {@link ParcelDetector} check */
|
/** Constructs a new {@link ParcelDetector} check */
|
||||||
@@ -100,7 +98,7 @@ public class ParcelDetector extends Detector implements UastScanner {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Location location = UastAndroidUtils.getLocation(node.getNameElement());
|
Location location = mContext.getLocation(node.getNameElement());
|
||||||
mContext.report(ISSUE, node, location,
|
mContext.report(ISSUE, node, location,
|
||||||
"This class implements `Parcelable` but does not "
|
"This class implements `Parcelable` but does not "
|
||||||
+ "provide a `CREATOR` field");
|
+ "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(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
PrivateResourceDetector.class,
|
PrivateResourceDetector.class,
|
||||||
Scope.SOURCE_AND_RESOURCE_FILES,
|
Scope.SOURCE_AND_RESOURCE_FILES,
|
||||||
Scope.JAVA_FILE_SCOPE,
|
Scope.SOURCE_FILE_SCOPE,
|
||||||
Scope.RESOURCE_FILE_SCOPE);
|
Scope.RESOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** The main issue discovered by this detector */
|
/** The main issue discovered by this detector */
|
||||||
@@ -259,7 +259,7 @@ public class PrivateResourceDetector extends ResourceXmlDetector implements Uast
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeCheckProject(@NonNull Context context) {
|
public void beforeCheckFile(@NonNull Context context) {
|
||||||
File file = context.file;
|
File file = context.file;
|
||||||
boolean isXmlFile = LintUtils.isXmlFile(file);
|
boolean isXmlFile = LintUtils.isXmlFile(file);
|
||||||
if (!isXmlFile && !LintUtils.isBitmapFile(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
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
// Handle
|
// Handle
|
||||||
// View#inflate(Context context, int resource, ViewGroup root)
|
// View#inflate(Context context, int resource, ViewGroup root)
|
||||||
// LayoutInflater#inflate(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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.EmptyUastVisitor;
|
import org.jetbrains.uast.visitor.EmptyUastVisitor;
|
||||||
@@ -99,7 +98,7 @@ public class RtlDetector extends LayoutDetector implements UastScanner {
|
|||||||
RtlDetector.class,
|
RtlDetector.class,
|
||||||
EnumSet.of(Scope.RESOURCE_FILE, Scope.SOURCE_FILE, Scope.MANIFEST),
|
EnumSet.of(Scope.RESOURCE_FILE, Scope.SOURCE_FILE, Scope.MANIFEST),
|
||||||
Scope.RESOURCE_FILE_SCOPE,
|
Scope.RESOURCE_FILE_SCOPE,
|
||||||
Scope.JAVA_FILE_SCOPE,
|
Scope.SOURCE_FILE_SCOPE,
|
||||||
Scope.MANIFEST_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):
|
// Can't resolve types (for example while editing code with errors):
|
||||||
// rely on heuristics like import statements and class qualifiers
|
// rely on heuristics like import statements and class qualifiers
|
||||||
if (parent instanceof UQualifiedExpression &&
|
if (parent instanceof UQualifiedExpression &&
|
||||||
!(GRAVITY_CLASS.equals(((UQualifiedExpression) parent).getReceiver().renderString()))) {
|
!(UastUtils.matchesQualified(((UQualifiedExpression) parent).getReceiver(), GRAVITY_CLASS))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (parent instanceof USimpleReferenceExpression) {
|
if (parent instanceof USimpleReferenceExpression) {
|
||||||
@@ -624,7 +623,7 @@ public class RtlDetector extends LayoutDetector implements UastScanner {
|
|||||||
+ "behavior in right-to-left locales",
|
+ "behavior in right-to-left locales",
|
||||||
(isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END).toUpperCase(Locale.US),
|
(isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END).toUpperCase(Locale.US),
|
||||||
(isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT).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);
|
mContext.report(USE_START, node, location, message);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ import org.jetbrains.uast.check.UastScanner;
|
|||||||
*/
|
*/
|
||||||
public class SQLiteDetector extends Detector implements UastScanner {
|
public class SQLiteDetector extends Detector implements UastScanner {
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
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 */
|
/** Using STRING instead of TEXT for columns */
|
||||||
public static final Issue ISSUE = Issue.create(
|
public static final Issue ISSUE = Issue.create(
|
||||||
@@ -77,7 +76,7 @@ public class SQLiteDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
UFunction resolvedFunction = node.resolve(context);
|
UFunction resolvedFunction = node.resolve(context);
|
||||||
UClass containingClass = UastUtils.getContainingClass(resolvedFunction);
|
UClass containingClass = UastUtils.getContainingClass(resolvedFunction);
|
||||||
if (resolvedFunction == null || containingClass == null) {
|
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,"
|
+ "(STRING is a numeric type and its value can be adjusted; for example,"
|
||||||
+ "strings that look like integers can drop leading zeroes. See issue "
|
+ "strings that look like integers can drop leading zeroes. See issue "
|
||||||
+ "explanation for details.)";
|
+ "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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.ULiteralExpression;
|
import org.jetbrains.uast.ULiteralExpression;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
@@ -57,7 +56,7 @@ public class SdCardDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
SdCardDetector.class,
|
SdCardDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE))
|
Scope.SOURCE_FILE_SCOPE))
|
||||||
.addMoreInfo(
|
.addMoreInfo(
|
||||||
"http://developer.android.com/guide/topics/data/data-storage.html#filesExternal"); //$NON-NLS-1$
|
"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$
|
|| s.startsWith("file:///sdcard/")) { //$NON-NLS-1$
|
||||||
String message = "Do not hardcode \"/sdcard/\"; " +
|
String message = "Do not hardcode \"/sdcard/\"; " +
|
||||||
"use `Environment.getExternalStorageDirectory().getPath()` instead";
|
"use `Environment.getExternalStorageDirectory().getPath()` instead";
|
||||||
Location location = UastAndroidUtils.getLocation(node);
|
Location location = mContext.getLocation(node);
|
||||||
mContext.report(ISSUE, node, location, message);
|
mContext.report(ISSUE, node, location, message);
|
||||||
} else if (s.startsWith("/data/data/") //$NON-NLS-1$
|
} else if (s.startsWith("/data/data/") //$NON-NLS-1$
|
||||||
|| s.startsWith("/data/user/")) { //$NON-NLS-1$
|
|| s.startsWith("/data/user/")) { //$NON-NLS-1$
|
||||||
String message = "Do not hardcode \"`/data/`\"; " +
|
String message = "Do not hardcode \"`/data/`\"; " +
|
||||||
"use `Context.getFilesDir().getPath()` instead";
|
"use `Context.getFilesDir().getPath()` instead";
|
||||||
Location location = UastAndroidUtils.getLocation(node);
|
Location location = mContext.getLocation(node);
|
||||||
mContext.report(ISSUE, node, location, message);
|
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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.UCallExpression;
|
import org.jetbrains.uast.UCallExpression;
|
||||||
import org.jetbrains.uast.USimpleReferenceExpression;
|
import org.jetbrains.uast.USimpleReferenceExpression;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
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(
|
private static final Implementation IMPLEMENTATION_JAVA = new Implementation(
|
||||||
SecurityDetector.class,
|
SecurityDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Exported services */
|
/** Exported services */
|
||||||
public static final Issue EXPORTED_SERVICE = Issue.create(
|
public static final Issue EXPORTED_SERVICE = Issue.create(
|
||||||
@@ -366,12 +365,12 @@ public class SecurityDetector extends Detector implements Detector.XmlScanner, U
|
|||||||
@Override
|
@Override
|
||||||
public boolean visitSimpleReferenceExpression(@NotNull USimpleReferenceExpression node) {
|
public boolean visitSimpleReferenceExpression(@NotNull USimpleReferenceExpression node) {
|
||||||
if ("MODE_WORLD_WRITEABLE".equals(node.getIdentifier())) { //$NON-NLS-1$
|
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,
|
mContext.report(WORLD_WRITEABLE, node, location,
|
||||||
"Using `MODE_WORLD_WRITEABLE` when creating files can be " +
|
"Using `MODE_WORLD_WRITEABLE` when creating files can be " +
|
||||||
"risky, review carefully");
|
"risky, review carefully");
|
||||||
} else if ("MODE_WORLD_READABLE".equals(node.getIdentifier())) { //$NON-NLS-1$
|
} 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,
|
mContext.report(WORLD_READABLE, node, location,
|
||||||
"Using `MODE_WORLD_READABLE` when creating files can be " +
|
"Using `MODE_WORLD_READABLE` when creating files can be " +
|
||||||
"risky, review carefully");
|
"risky, review carefully");
|
||||||
|
|||||||
+3
-4
@@ -33,7 +33,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ public class ServiceCastDetector extends Detector implements UastScanner {
|
|||||||
Severity.FATAL,
|
Severity.FATAL,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
ServiceCastDetector.class,
|
ServiceCastDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
/** Constructs a new {@link ServiceCastDetector} check */
|
/** Constructs a new {@link ServiceCastDetector} check */
|
||||||
public ServiceCastDetector() {
|
public ServiceCastDetector() {
|
||||||
@@ -75,7 +74,7 @@ public class ServiceCastDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
UExpression receiver = UastUtils.getQualifiedCallElement(node);
|
UExpression receiver = UastUtils.getQualifiedCallElement(node);
|
||||||
UElement parent = receiver.getParent();
|
UElement parent = receiver.getParent();
|
||||||
if (!(parent instanceof UBinaryExpressionWithType)
|
if (!(parent instanceof UBinaryExpressionWithType)
|
||||||
@@ -103,7 +102,7 @@ public class ServiceCastDetector extends Detector implements UastScanner {
|
|||||||
String message = String.format(
|
String message = String.format(
|
||||||
"Suspicious cast to `%1$s` for a `%2$s`: expected `%3$s`",
|
"Suspicious cast to `%1$s` for a `%2$s`: expected `%3$s`",
|
||||||
stripPackage(castType), name, stripPackage(expectedClass));
|
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 java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.uast.UCallExpression;
|
import org.jetbrains.uast.UCallExpression;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ public class SetJavaScriptEnabledDetector extends Detector implements UastScanne
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
SetJavaScriptEnabledDetector.class,
|
SetJavaScriptEnabledDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE))
|
Scope.SOURCE_FILE_SCOPE))
|
||||||
.addMoreInfo(
|
.addMoreInfo(
|
||||||
"http://developer.android.com/guide/practices/security.html"); //$NON-NLS-1$
|
"http://developer.android.com/guide/practices/security.html"); //$NON-NLS-1$
|
||||||
|
|
||||||
@@ -58,14 +57,14 @@ public class SetJavaScriptEnabledDetector extends Detector implements UastScanne
|
|||||||
// ---- Implements UastScanner ----
|
// ---- Implements UastScanner ----
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
if (node.getValueArgumentCount() != 1) {
|
if (node.getValueArgumentCount() != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object value = node.getValueArguments().get(0).evaluate();
|
Object value = node.getValueArguments().get(0).evaluate();
|
||||||
if (value instanceof Boolean && (Boolean) value) {
|
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 " +
|
"Using `setJavaScriptEnabled` can introduce XSS vulnerabilities " +
|
||||||
"into you application, review carefully.");
|
"into you application, review carefully.");
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-18
@@ -34,9 +34,8 @@ import java.util.List;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
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 org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +56,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
SharedPrefsDetector.class,
|
SharedPrefsDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
public static final String ANDROID_CONTENT_SHARED_PREFERENCES =
|
public static final String ANDROID_CONTENT_SHARED_PREFERENCES =
|
||||||
"android.content.SharedPreferences"; //$NON-NLS-1$
|
"android.content.SharedPreferences"; //$NON-NLS-1$
|
||||||
@@ -82,7 +81,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
assert "edit".equals(node.getFunctionName());
|
assert "edit".equals(node.getFunctionName());
|
||||||
|
|
||||||
boolean verifiedType = false;
|
boolean verifiedType = false;
|
||||||
@@ -149,7 +148,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
|
|||||||
CommitFinder finder = new CommitFinder(context, node, allowCommitBeforeTarget);
|
CommitFinder finder = new CommitFinder(context, node, allowCommitBeforeTarget);
|
||||||
method.accept(finder);
|
method.accept(finder);
|
||||||
if (!finder.isCommitCalled()) {
|
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");
|
"`SharedPreferences.edit()` without a corresponding `commit()` or `apply()` call");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,8 +219,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
|
|||||||
returnValueIgnored = true;
|
returnValueIgnored = true;
|
||||||
} else if (qualifiedElement instanceof UIfExpression) {
|
} else if (qualifiedElement instanceof UIfExpression) {
|
||||||
returnValueIgnored = ((UIfExpression) qualifiedElement).getCondition() != node;
|
returnValueIgnored = ((UIfExpression) qualifiedElement).getCondition() != node;
|
||||||
} else if (qualifiedElement instanceof USpecialExpressionList &&
|
} else if (qualifiedElement instanceof UReturnExpression) {
|
||||||
((USpecialExpressionList)qualifiedElement).getKind() == UastSpecialExpressionKind.RETURN) {
|
|
||||||
returnValueIgnored = false;
|
returnValueIgnored = false;
|
||||||
} else if (qualifiedElement instanceof UVariable) {
|
} else if (qualifiedElement instanceof UVariable) {
|
||||||
returnValueIgnored = false;
|
returnValueIgnored = false;
|
||||||
@@ -231,11 +229,16 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
|
|||||||
returnValueIgnored = ((UWhileExpression) qualifiedElement).getCondition() != node;
|
returnValueIgnored = ((UWhileExpression) qualifiedElement).getCondition() != node;
|
||||||
} else if (qualifiedElement instanceof UDoWhileExpression) {
|
} else if (qualifiedElement instanceof UDoWhileExpression) {
|
||||||
returnValueIgnored = ((UDoWhileExpression) qualifiedElement).getCondition() != node;
|
returnValueIgnored = ((UDoWhileExpression) qualifiedElement).getCondition() != node;
|
||||||
} else if (qualifiedElement instanceof UExpressionSwitchClauseExpression) {
|
} else if (qualifiedElement instanceof USwitchClauseExpression) {
|
||||||
returnValueIgnored = ((UExpressionSwitchClauseExpression) qualifiedElement).getCaseValue() != node;
|
List<UExpression> caseValues = ((USwitchClauseExpression) qualifiedElement).getCaseValues();
|
||||||
} else if (qualifiedElement instanceof USpecialExpressionList &&
|
for (UExpression caseValue : caseValues) {
|
||||||
((USpecialExpressionList)qualifiedElement).getKind() == JavaSpecialExpressionKinds.ASSERT) {
|
if (caseValue == node) {
|
||||||
returnValueIgnored = !((USpecialExpressionList) qualifiedElement).getExpressions().contains(node);
|
returnValueIgnored = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (qualifiedElement instanceof JavaUAssertExpression) {
|
||||||
|
returnValueIgnored = !((JavaUAssertExpression) qualifiedElement).getCondition().equals(node);
|
||||||
} else {
|
} else {
|
||||||
returnValueIgnored = true;
|
returnValueIgnored = true;
|
||||||
}
|
}
|
||||||
@@ -243,7 +246,7 @@ public class SharedPrefsDetector extends Detector implements UastScanner {
|
|||||||
String message = "Consider using `apply()` instead; `commit` writes "
|
String message = "Consider using `apply()` instead; `commit` writes "
|
||||||
+ "its data to persistent storage immediately, whereas "
|
+ "its data to persistent storage immediately, whereas "
|
||||||
+ "`apply` will handle it in the background";
|
+ "`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
|
@Override
|
||||||
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
|
public boolean visitReturnExpression(@NotNull UReturnExpression node) {
|
||||||
if (node.getKind() == UastSpecialExpressionKind.RETURN) {
|
if (mTarget.equals(node.getExpressionType())) {
|
||||||
if (node.getExpressions().contains(mTarget)) {
|
mFound = true;
|
||||||
mFound = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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(
|
private static final Implementation IMPLEMENTATION_XML_AND_JAVA = new Implementation(
|
||||||
StringFormatDetector.class,
|
StringFormatDetector.class,
|
||||||
EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.SOURCE_FILE),
|
EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.SOURCE_FILE),
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
|
|
||||||
/** Whether formatting strings are invalid */
|
/** Whether formatting strings are invalid */
|
||||||
@@ -987,7 +987,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements UastSca
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
if (mFormatStrings == null && !context.getLintContext().getClient().supportsProjectResources()) {
|
if (mFormatStrings == null && !context.getLintContext().getClient().supportsProjectResources()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-55
@@ -53,7 +53,6 @@ import org.jetbrains.uast.check.UastAndroidContext;
|
|||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.java.JavaUFunction;
|
import org.jetbrains.uast.java.JavaUFunction;
|
||||||
import org.jetbrains.uast.java.JavaUastCallKinds;
|
import org.jetbrains.uast.java.JavaUastCallKinds;
|
||||||
import org.jetbrains.uast.kinds.UastOperator;
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@@ -79,7 +78,7 @@ import java.util.Set;
|
|||||||
public class SupportAnnotationDetector extends Detector implements UastScanner {
|
public class SupportAnnotationDetector extends Detector implements UastScanner {
|
||||||
|
|
||||||
public static final Implementation IMPLEMENTATION
|
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 */
|
/** Method result should be used */
|
||||||
public static final Issue RANGE = Issue.create(
|
public static final Issue RANGE = Issue.create(
|
||||||
@@ -753,7 +752,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
|
|||||||
UQualifiedExpression select = (UQualifiedExpression) node.getReceiver();
|
UQualifiedExpression select = (UQualifiedExpression) node.getReceiver();
|
||||||
if (select.getReceiver() instanceof UQualifiedExpression) { // android.R....
|
if (select.getReceiver() instanceof UQualifiedExpression) { // android.R....
|
||||||
UQualifiedExpression innerSelect = (UQualifiedExpression) select.getReceiver();
|
UQualifiedExpression innerSelect = (UQualifiedExpression) select.getReceiver();
|
||||||
if (innerSelect.getSelector().renderString().equals(R_CLASS)) {
|
if (UastUtils.matchesQualified(innerSelect.getSelector(), R_CLASS)) {
|
||||||
String typeName = select.getSelector().renderString();
|
String typeName = select.getSelector().renderString();
|
||||||
ResourceType type = ResourceType.getEnum(typeName);
|
ResourceType type = ResourceType.getEnum(typeName);
|
||||||
return type != null ? Collections.singletonList(type) : null;
|
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
|
// 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();
|
UElement parent = node.getParent();
|
||||||
if (parent instanceof UQualifiedExpression) {
|
if (parent instanceof UQualifiedExpression) {
|
||||||
UElement grandParent = parent.getParent();
|
UElement grandParent = parent.getParent();
|
||||||
@@ -788,8 +787,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
} else if (argument instanceof UCallExpression) {
|
} else if (argument instanceof UCallExpression) {
|
||||||
UDeclaration resolved = ((UCallExpression)argument).resolve(context);
|
UDeclaration resolved = ((UCallExpression)argument).resolve(context);
|
||||||
//noinspection ConstantConditions
|
if (resolved != null) {
|
||||||
if (resolved instanceof UAnnotated) {
|
|
||||||
List<UAnnotation> annotations = ((UAnnotated) resolved).getAnnotations();
|
List<UAnnotation> annotations = ((UAnnotated) resolved).getAnnotations();
|
||||||
for (UAnnotation annotation : annotations) {
|
for (UAnnotation annotation : annotations) {
|
||||||
String signature = annotation.getFqName();
|
String signature = annotation.getFqName();
|
||||||
@@ -972,7 +970,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
|
|||||||
assert s != null;
|
assert s != null;
|
||||||
actual = s.length();
|
actual = s.length();
|
||||||
} else if (argument instanceof UCallExpression
|
} else if (argument instanceof UCallExpression
|
||||||
&& ((UCallExpression)argument).getKind() == JavaUastCallKinds.ARRAY_INITIALIZER) {
|
&& ((UCallExpression)argument).getKind() == UastCallKind.ARRAY_INITIALIZER) {
|
||||||
UCallExpression initializer = (UCallExpression) argument;
|
UCallExpression initializer = (UCallExpression) argument;
|
||||||
actual = initializer.getValueArgumentCount();
|
actual = initializer.getValueArgumentCount();
|
||||||
} else {
|
} else {
|
||||||
@@ -1059,7 +1057,7 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
|
|||||||
if (!flag) {
|
if (!flag) {
|
||||||
reportTypeDef(context, annotation, argument, errorNode);
|
reportTypeDef(context, annotation, argument, errorNode);
|
||||||
}
|
}
|
||||||
} else if (argument instanceof UIfExpression && ((UIfExpression) argument).isTernary()) {
|
} else if (argument instanceof UIfExpression) {
|
||||||
UIfExpression expression = (UIfExpression) argument;
|
UIfExpression expression = (UIfExpression) argument;
|
||||||
if (expression.getThenBranch() != null) {
|
if (expression.getThenBranch() != null) {
|
||||||
checkTypeDefConstant(context, annotation, expression.getThenBranch(), errorNode, flag);
|
checkTypeDefConstant(context, annotation, expression.getThenBranch(), errorNode, flag);
|
||||||
@@ -1077,7 +1075,6 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
|
|||||||
"Flag not allowed here");
|
"Flag not allowed here");
|
||||||
}
|
}
|
||||||
} else if (argument instanceof UBinaryExpression) {
|
} else if (argument instanceof UBinaryExpression) {
|
||||||
// If it's ?: then check both the if and else clauses
|
|
||||||
UBinaryExpression expression = (UBinaryExpression) argument;
|
UBinaryExpression expression = (UBinaryExpression) argument;
|
||||||
if (flag) {
|
if (flag) {
|
||||||
checkTypeDefConstant(context, annotation, expression.getLeftOperand(), errorNode, true);
|
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,
|
private static void checkTypeDefConstant(@NonNull UastAndroidContext context,
|
||||||
@NonNull UAnnotation annotation, @NonNull UElement argument,
|
@NonNull UAnnotation annotation, @NonNull UElement argument,
|
||||||
@Nullable UElement errorNode, boolean flag, Object value) {
|
@Nullable UElement errorNode, boolean flag, Object value) {
|
||||||
List<Pair<String, Object>> valueArguments = annotation.getValues();
|
for (UNamedExpression namedExpression : annotation.getValueArguments()) {
|
||||||
for (Pair<String, Object> o : valueArguments) {
|
UExpression expression = namedExpression.getExpression();
|
||||||
if (o.getSecond().equals(value)) {
|
if (expression instanceof UCallExpression &&
|
||||||
return;
|
((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,
|
private static void reportTypeDef(@NonNull UastAndroidContext context,
|
||||||
@@ -1242,47 +1244,6 @@ public class SupportAnnotationDetector extends Detector implements UastScanner {
|
|||||||
return null;
|
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 ----
|
// ---- Implements UastScanner ----
|
||||||
|
|
||||||
@Override
|
@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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
@@ -51,7 +50,7 @@ public class ToastDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
ToastDetector.class,
|
ToastDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
|
|
||||||
/** Constructs a new {@link ToastDetector} check */
|
/** Constructs a new {@link ToastDetector} check */
|
||||||
@@ -72,7 +71,7 @@ public class ToastDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
assert "makeText".equals(node.getFunctionName());
|
assert "makeText".equals(node.getFunctionName());
|
||||||
|
|
||||||
UElement qualifiedExpression = node.getParent();
|
UElement qualifiedExpression = node.getParent();
|
||||||
@@ -80,8 +79,7 @@ public class ToastDetector extends Detector implements UastScanner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String operand = ((UQualifiedExpression)qualifiedExpression).getReceiver().renderString();
|
if (!UastUtils.endsWithQualified(((UQualifiedExpression) qualifiedExpression).getReceiver(), "Toast")) {
|
||||||
if (!(operand.equals("Toast") || operand.endsWith(".Toast"))) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +105,7 @@ public class ToastDetector extends Detector implements UastScanner {
|
|||||||
ShowFinder finder = new ShowFinder(nodeWithPossibleQualifier);
|
ShowFinder finder = new ShowFinder(nodeWithPossibleQualifier);
|
||||||
method.accept(finder);
|
method.accept(finder);
|
||||||
if (!finder.isShowCalled()) {
|
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()` ?");
|
"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) {
|
public boolean visitCallExpression(@NotNull UCallExpression node) {
|
||||||
if (node == mTarget) {
|
if (node == mTarget) {
|
||||||
mSeenTarget = true;
|
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
|
// TODO: Do more flow analysis to see whether we're really calling show
|
||||||
// on the right type of object?
|
// on the right type of object?
|
||||||
mFound = true;
|
mFound = true;
|
||||||
@@ -147,9 +145,8 @@ public class ToastDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean visitSpecialExpressionList(@NotNull USpecialExpressionList node) {
|
public boolean visitReturnExpression(@NotNull UReturnExpression node) {
|
||||||
if (node.getKind() == UastSpecialExpressionKind.RETURN && node.firstOrNull() == mTarget) {
|
if (mTarget.equals(node.getReturnExpression())) {
|
||||||
// If you just do "return Toast.makeText(...) don't warn
|
|
||||||
mFound = true;
|
mFound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -35,10 +35,8 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
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
|
* 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,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
ViewConstructorDetector.class,
|
ViewConstructorDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
/** Constructs a new {@link ViewConstructorDetector} check */
|
/** Constructs a new {@link ViewConstructorDetector} check */
|
||||||
public ViewConstructorDetector() {
|
public ViewConstructorDetector() {
|
||||||
@@ -140,7 +138,7 @@ public class ViewConstructorDetector extends Detector implements UastScanner {
|
|||||||
+ "`(Context)` or `(Context,AttributeSet)` "
|
+ "`(Context)` or `(Context,AttributeSet)` "
|
||||||
+ "or `(Context,AttributeSet,int)`",
|
+ "or `(Context,AttributeSet,int)`",
|
||||||
node.getFqName());
|
node.getFqName());
|
||||||
Location location = UastAndroidUtils.getLocation(node.getNameElement());
|
Location location = context.getLocation(node.getNameElement());
|
||||||
context.report(ISSUE, node, location, message /*data*/);
|
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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ public class ViewHolderDetector extends Detector implements UastScanner {
|
|||||||
|
|
||||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
ViewHolderDetector.class,
|
ViewHolderDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
/** Using a view inflater unconditionally in an AdapterView */
|
/** Using a view inflater unconditionally in an AdapterView */
|
||||||
public static final Issue ISSUE = Issue.create(
|
public static final Issue ISSUE = Issue.create(
|
||||||
@@ -109,32 +108,8 @@ public class ViewHolderDetector extends Detector implements UastScanner {
|
|||||||
if (GET_VIEW.equals(node.getName())) {
|
if (GET_VIEW.equals(node.getName())) {
|
||||||
List<UVariable> parameters = node.getValueParameters();
|
List<UVariable> parameters = node.getValueParameters();
|
||||||
if (parameters.size() == 3) {
|
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
|
//noinspection RedundantIfStatement
|
||||||
if (!third.getType().matchesFqName(CLASS_VIEWGROUP)) {
|
if (!parameters.get(2).getType().matchesFqName(CLASS_VIEWGROUP)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +177,7 @@ public class ViewHolderDetector extends Detector implements UastScanner {
|
|||||||
+ "Should use View Holder pattern (use recycled view passed "
|
+ "Should use View Holder pattern (use recycled view passed "
|
||||||
+ "into this method as the second parameter) for smoother "
|
+ "into this method as the second parameter) for smoother "
|
||||||
+ "scrolling";
|
+ "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.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.w3c.dom.Attr;
|
import org.w3c.dom.Attr;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
@@ -98,7 +97,7 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
|
|||||||
new Implementation(
|
new Implementation(
|
||||||
ViewTypeDetector.class,
|
ViewTypeDetector.class,
|
||||||
EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_SOURCE_FILES),
|
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
|
/** Flag used to do no work if we're running in incremental mode in a .java file without
|
||||||
* a client supporting project resources */
|
* a client supporting project resources */
|
||||||
@@ -168,7 +167,7 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitFunctionCall(UastAndroidContext context, UCallExpression node) {
|
public void visitCall(UastAndroidContext context, UCallExpression node) {
|
||||||
JavaContext lintContext = context.getLintContext();
|
JavaContext lintContext = context.getLintContext();
|
||||||
LintClient client = lintContext.getClient();
|
LintClient client = lintContext.getClient();
|
||||||
if (mIgnore == Boolean.TRUE) {
|
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
|
// TODO: Do flow analysis as in the StringFormatDetector in order
|
||||||
// to handle variable references too
|
// to handle variable references too
|
||||||
if (first instanceof UQualifiedExpression) {
|
if (first instanceof UQualifiedExpression) {
|
||||||
String resource = first.renderString();
|
if (UastUtils.startsWithQualified(first, "R.id")) { //$NON-NLS-1$
|
||||||
if (resource.startsWith("R.id.")) { //$NON-NLS-1$
|
String id = ((UQualifiedExpression) first).getSelectorAsIdentifier();
|
||||||
String id = ((UQualifiedExpression) first).getSelector().renderString();
|
|
||||||
|
|
||||||
if (client.supportsProjectResources()) {
|
if (id != null) {
|
||||||
AbstractResourceRepository resources = client
|
if (client.supportsProjectResources()) {
|
||||||
.getProjectResources(lintContext.getMainProject(), true);
|
AbstractResourceRepository resources = client
|
||||||
if (resources == null) {
|
.getProjectResources(lintContext.getMainProject(), true);
|
||||||
return;
|
if (resources == null) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<ResourceItem> items = resources.getResourceItem(ResourceType.ID,
|
List<ResourceItem> items = resources.getResourceItem(ResourceType.ID, id);
|
||||||
id);
|
if (items != null && !items.isEmpty()) {
|
||||||
if (items != null && !items.isEmpty()) {
|
Set<String> compatible = Sets.newHashSet();
|
||||||
Set<String> compatible = Sets.newHashSet();
|
for (ResourceItem item : items) {
|
||||||
for (ResourceItem item : items) {
|
Collection<String> tags = getViewTags(lintContext, item);
|
||||||
Collection<String> tags = getViewTags(lintContext, item);
|
if (tags != null) {
|
||||||
if (tags != null) {
|
compatible.addAll(tags);
|
||||||
compatible.addAll(tags);
|
}
|
||||||
|
}
|
||||||
|
if (!compatible.isEmpty()) {
|
||||||
|
ArrayList<String> layoutTypes = Lists.newArrayList(compatible);
|
||||||
|
checkCompatible(lintContext, castType, null, layoutTypes, cast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!compatible.isEmpty()) {
|
} else {
|
||||||
ArrayList<String> layoutTypes = Lists.newArrayList(compatible);
|
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);
|
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;
|
return (UBinaryExpressionWithType) expression;
|
||||||
} else if (expression instanceof UQualifiedExpression) {
|
} else if (expression instanceof UQualifiedExpression) {
|
||||||
return findContainingTypeCast(expression.getParent());
|
return findContainingTypeCast(expression.getParent());
|
||||||
|
} else if (expression instanceof UParenthesizedExpression) {
|
||||||
|
return findContainingTypeCast(((UParenthesizedExpression) expression).getExpression());
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -337,7 +338,7 @@ public class ViewTypeDetector extends ResourceXmlDetector implements UastScanner
|
|||||||
String message = String.format(
|
String message = String.format(
|
||||||
"Unexpected cast to `%1$s`: layout tag was `%2$s`",
|
"Unexpected cast to `%1$s`: layout tag was `%2$s`",
|
||||||
castType, layoutType);
|
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 java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
|
|
||||||
@@ -58,7 +57,7 @@ public class WrongCallDetector extends Detector implements UastScanner {
|
|||||||
Severity.FATAL,
|
Severity.FATAL,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
WrongCallDetector.class,
|
WrongCallDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
/** Constructs a new {@link WrongCallDetector} */
|
/** Constructs a new {@link WrongCallDetector} */
|
||||||
public WrongCallDetector() {
|
public WrongCallDetector() {
|
||||||
@@ -83,7 +82,7 @@ public class WrongCallDetector extends Detector implements UastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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)
|
// 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
|
// as well as within the same overriding method (e.g. you can't call super.onLayout
|
||||||
// from the onMeasure method)
|
// 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!
|
// Keep in sync with {@link #getOldValue} and {@link #getNewValue} below!
|
||||||
"Suspicious method call; should probably call \"`%1$s`\" rather than \"`%2$s`\"",
|
"Suspicious method call; should probably call \"`%1$s`\" rather than \"`%2$s`\"",
|
||||||
suggestion, name);
|
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.annotations.NotNull;
|
||||||
import org.jetbrains.uast.UImportStatement;
|
import org.jetbrains.uast.UImportStatement;
|
||||||
import org.jetbrains.uast.check.UastAndroidUtils;
|
|
||||||
import org.jetbrains.uast.check.UastAndroidContext;
|
import org.jetbrains.uast.check.UastAndroidContext;
|
||||||
import org.jetbrains.uast.check.UastScanner;
|
import org.jetbrains.uast.check.UastScanner;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
@@ -62,7 +61,7 @@ public class WrongImportDetector extends Detector implements UastScanner {
|
|||||||
Severity.WARNING,
|
Severity.WARNING,
|
||||||
new Implementation(
|
new Implementation(
|
||||||
WrongImportDetector.class,
|
WrongImportDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE));
|
Scope.SOURCE_FILE_SCOPE));
|
||||||
|
|
||||||
/** Constructs a new {@link WrongImportDetector} check */
|
/** Constructs a new {@link WrongImportDetector} check */
|
||||||
public WrongImportDetector() {
|
public WrongImportDetector() {
|
||||||
@@ -92,7 +91,7 @@ public class WrongImportDetector extends Detector implements UastScanner {
|
|||||||
public boolean visitImportStatement(@NotNull UImportStatement node) {
|
public boolean visitImportStatement(@NotNull UImportStatement node) {
|
||||||
String fqn = node.getFqNameToImport();
|
String fqn = node.getFqNameToImport();
|
||||||
if (fqn != null && fqn.equals("android.R")) { //$NON-NLS-1$
|
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,
|
mContext.report(ISSUE, node, location,
|
||||||
"Don't include `android.R` here; use a fully qualified name for "
|
"Don't include `android.R` here; use a fully qualified name for "
|
||||||
+ "each usage instead");
|
+ "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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.intellij.codeInspection.HTMLComposer;
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.SdkConstants;
|
import com.android.SdkConstants;
|
||||||
@@ -139,14 +123,14 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
|
|||||||
scope = Scope.RESOURCE_FILE_SCOPE;
|
scope = Scope.RESOURCE_FILE_SCOPE;
|
||||||
}
|
}
|
||||||
} else if (fileType == StdFileTypes.JAVA) {
|
} 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)) {
|
} else if (name.equals(OLD_PROGUARD_FILE) || name.equals(FN_PROJECT_PROGUARD_FILE)) {
|
||||||
scope = EnumSet.of(Scope.PROGUARD_FILE);
|
scope = EnumSet.of(Scope.PROGUARD_FILE);
|
||||||
} else if (fileType == StdFileTypes.PROPERTIES) {
|
} else if (fileType == StdFileTypes.PROPERTIES) {
|
||||||
scope = Scope.PROPERTY_SCOPE;
|
scope = Scope.PROPERTY_SCOPE;
|
||||||
} else {
|
} else {
|
||||||
if (UastConverterUtils.isFileSupported(client.getLanguagePlugins(), mainFile.getName())) {
|
if (UastConverterUtils.isFileSupported(client.getLanguagePlugins(), mainFile.getName())) {
|
||||||
scope = Scope.JAVA_FILE_SCOPE;
|
scope = Scope.SOURCE_FILE_SCOPE;
|
||||||
} else {
|
} else {
|
||||||
// #collectionInformation above should have prevented this
|
// #collectionInformation above should have prevented this
|
||||||
assert false;
|
assert false;
|
||||||
@@ -178,8 +162,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
|
|||||||
final IssueRegistry fullRegistry = new IntellijLintIssueRegistry();
|
final IssueRegistry fullRegistry = new IntellijLintIssueRegistry();
|
||||||
|
|
||||||
for (Issue issue : fullRegistry.getIssues()) {
|
for (Issue issue : fullRegistry.getIssues()) {
|
||||||
final String inspectionShortName = org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
|
final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(project, issue);
|
||||||
.getInspectionShortNameByIssue(project, issue);
|
|
||||||
if (inspectionShortName == null) {
|
if (inspectionShortName == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -216,12 +199,12 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Pair<org.jetbrains.android.inspections.klint.AndroidLintInspectionBase, HighlightDisplayLevel> pair =
|
final Pair<AndroidLintInspectionBase, HighlightDisplayLevel> pair =
|
||||||
AndroidLintUtil.getHighlighLevelAndInspection(project, issue, file);
|
AndroidLintUtil.getHighlighLevelAndInspection(project, issue, file);
|
||||||
if (pair == null) {
|
if (pair == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final org.jetbrains.android.inspections.klint.AndroidLintInspectionBase inspection = pair.getFirst();
|
final AndroidLintInspectionBase inspection = pair.getFirst();
|
||||||
HighlightDisplayLevel displayLevel = pair.getSecond();
|
HighlightDisplayLevel displayLevel = pair.getSecond();
|
||||||
|
|
||||||
if (inspection != null) {
|
if (inspection != null) {
|
||||||
@@ -259,7 +242,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
|
|||||||
if (action.isAvailable(project, startElement)) {
|
if (action.isAvailable(project, startElement)) {
|
||||||
ProblemHighlightType type = annotation.getHighlightType();
|
ProblemHighlightType type = annotation.getHighlightType();
|
||||||
annotation.registerFix(action, null, key, InspectionManager.getInstance(project).createProblemDescriptor(
|
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) {
|
if (createHtmlAnnotation != null) {
|
||||||
// Based on LocalInspectionsPass#createHighlightInfo
|
// Based on LocalInspectionsPass#createHighlightInfo
|
||||||
String link = " <a "
|
String link = " <a "
|
||||||
+ "href=\"#lint/" + issue.getId() + "\""
|
+ "href=\"#lint/" + issue.getId() + "\""
|
||||||
+ (UIUtil.isUnderDarcula() ? " color=\"7AB4C9\" " : "")
|
+ (UIUtil.isUnderDarcula() ? " color=\"7AB4C9\" " : "")
|
||||||
+ ">" + DaemonBundle.message("inspection.extended.description")
|
+ ">" + DaemonBundle.message("inspection.extended.description")
|
||||||
+ "</a> " + getShowMoreShortCut();
|
+ "</a> " + getShowMoreShortCut();
|
||||||
String tooltip = XmlStringUtil.wrapInHtml(RAW.convertTo(message, HTML) + link);
|
String tooltip = XmlStringUtil.wrapInHtml(RAW.convertTo(message, HTML) + link);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -333,7 +316,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
|
|||||||
ourCreateHtmlAnnotationMethodFailed = true;
|
ourCreateHtmlAnnotationMethodFailed = true;
|
||||||
try {
|
try {
|
||||||
ourCreateHtmlAnnotationMethod = AnnotationHolder.class.getMethod("createAnnotation", HighlightSeverity.class,
|
ourCreateHtmlAnnotationMethod = AnnotationHolder.class.getMethod("createAnnotation", HighlightSeverity.class,
|
||||||
TextRange.class, String.class, String.class);
|
TextRange.class, String.class, String.class);
|
||||||
}
|
}
|
||||||
catch (NoSuchMethodException ignore) {
|
catch (NoSuchMethodException ignore) {
|
||||||
}
|
}
|
||||||
@@ -440,7 +423,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class MyEditInspectionToolsSettingsAction extends CustomEditInspectionToolsSettingsAction {
|
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>() {
|
super(key, new Computable<String>() {
|
||||||
@Override
|
@Override
|
||||||
public String compute() {
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.tools.klint.client.api.LintDriver;
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.annotations.concurrency.GuardedBy;
|
import com.android.annotations.concurrency.GuardedBy;
|
||||||
@@ -215,8 +199,7 @@ public abstract class AndroidLintInspectionBase extends GlobalInspectionTool {
|
|||||||
if (AndroidLintExternalAnnotator.INCLUDE_IDEA_SUPPRESS_ACTIONS) {
|
if (AndroidLintExternalAnnotator.INCLUDE_IDEA_SUPPRESS_ACTIONS) {
|
||||||
final List<SuppressQuickFix> result = new ArrayList<SuppressQuickFix>();
|
final List<SuppressQuickFix> result = new ArrayList<SuppressQuickFix>();
|
||||||
result.add(suppressLintQuickFix);
|
result.add(suppressLintQuickFix);
|
||||||
result.addAll(Arrays.asList(
|
result.addAll(Arrays.asList(BatchSuppressManager.SERVICE.getInstance().createBatchSuppressActions(HighlightDisplayKey.find(getShortName()))));
|
||||||
BatchSuppressManager.SERVICE.getInstance().createBatchSuppressActions(HighlightDisplayKey.find(getShortName()))));
|
|
||||||
result.addAll(Arrays.asList(new XmlSuppressableInspectionTool.SuppressTagStatic(getShortName()),
|
result.addAll(Arrays.asList(new XmlSuppressableInspectionTool.SuppressTagStatic(getShortName()),
|
||||||
new XmlSuppressableInspectionTool.SuppressForFile(getShortName())));
|
new XmlSuppressableInspectionTool.SuppressForFile(getShortName())));
|
||||||
return result.toArray(new SuppressQuickFix[result.size()]);
|
return result.toArray(new SuppressQuickFix[result.size()]);
|
||||||
@@ -250,13 +233,7 @@ public abstract class AndroidLintInspectionBase extends GlobalInspectionTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestOnly
|
@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
|
* 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
|
* it insists that the start and end {@link PsiElement} instances must have a valid
|
||||||
* <b>text</b> range, which does not apply for images.
|
* <b>text</b> range, which does not apply for images.
|
||||||
* <p>
|
* <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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.tools.klint.checks.*;
|
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 static class AndroidKLintGetInstanceInspection extends AndroidLintInspectionBase {
|
||||||
public AndroidKLintGetInstanceInspection() {
|
public AndroidKLintGetInstanceInspection() {
|
||||||
super(AndroidBundle.message("android.lint.inspections.get.instance"), CipherGetInstanceDetector.ISSUE);
|
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 static class AndroidKLintAssertInspection extends AndroidLintInspectionBase {
|
||||||
public AndroidKLintAssertInspection() {
|
public AndroidKLintAssertInspection() {
|
||||||
super(AndroidBundle.message("android.lint.inspections.assert"), AssertDetector.ISSUE);
|
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 static class AndroidKLintExportedServiceInspection extends AndroidLintInspectionBase {
|
||||||
public AndroidKLintExportedServiceInspection() {
|
public AndroidKLintExportedServiceInspection() {
|
||||||
super(AndroidBundle.message("android.lint.inspections.exported.service"), SecurityDetector.EXPORTED_SERVICE);
|
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 static class AndroidKLintOverrideAbstractInspection extends AndroidLintInspectionBase {
|
||||||
public AndroidKLintOverrideAbstractInspection() {
|
public AndroidKLintOverrideAbstractInspection() {
|
||||||
super(AndroidBundle.message("android.lint.inspections.override.abstract"), OverrideConcreteDetector.ISSUE);
|
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 static class AndroidKLintRequiredSizeInspection extends AndroidLintInspectionBase {
|
||||||
public AndroidKLintRequiredSizeInspection() {
|
public AndroidKLintRequiredSizeInspection() {
|
||||||
super(AndroidBundle.message("android.lint.inspections.required.size"), RequiredAttributeDetector.ISSUE);
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public interface AndroidLintQuickFix {
|
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
|
@NotNull
|
||||||
String getName();
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.tools.klint.detector.api.Issue;
|
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
|
package org.jetbrains.android.inspections.klint
|
||||||
|
|
||||||
import com.android.builder.model.AndroidProject
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
|||||||
+8
-8
@@ -57,13 +57,13 @@ class DomPsiParser extends XmlParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node) {
|
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();
|
return textRange.getStartOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node) {
|
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();
|
return textRange.getEndOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ class DomPsiParser extends XmlParser {
|
|||||||
XmlFile xmlFile = (XmlFile)psiFile;
|
XmlFile xmlFile = (XmlFile)psiFile;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return org.jetbrains.android.inspections.klint.DomPsiConverter.convert(xmlFile);
|
return DomPsiConverter.convert(xmlFile);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
myClient.log(t, "Failed converting PSI parse tree to DOM for file %1$s",
|
myClient.log(t, "Failed converting PSI parse tree to DOM for file %1$s",
|
||||||
context.file.getPath());
|
context.file.getPath());
|
||||||
@@ -103,7 +103,7 @@ class DomPsiParser extends XmlParser {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Location getLocation(@NonNull XmlContext context, @NonNull Node node) {
|
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 start = new DefaultPosition(-1, -1, textRange.getStartOffset());
|
||||||
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
||||||
return Location.create(context.file, start, end);
|
return Location.create(context.file, start, end);
|
||||||
@@ -112,7 +112,7 @@ class DomPsiParser extends XmlParser {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Location getLocation(@NonNull XmlContext context, @NonNull Node node, int startDelta, int endDelta) {
|
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 start = new DefaultPosition(-1, -1, textRange.getStartOffset() + startDelta);
|
||||||
Position end = new DefaultPosition(-1, -1, textRange.getStartOffset() + endDelta);
|
Position end = new DefaultPosition(-1, -1, textRange.getStartOffset() + endDelta);
|
||||||
return Location.create(context.file, start, end);
|
return Location.create(context.file, start, end);
|
||||||
@@ -121,7 +121,7 @@ class DomPsiParser extends XmlParser {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Location getNameLocation(@NonNull XmlContext context, @NonNull Node node) {
|
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 start = new DefaultPosition(-1, -1, textRange.getStartOffset());
|
||||||
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
||||||
return Location.create(context.file, start, end);
|
return Location.create(context.file, start, end);
|
||||||
@@ -130,7 +130,7 @@ class DomPsiParser extends XmlParser {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Location getValueLocation(@NonNull XmlContext context, @NonNull Attr node) {
|
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 start = new DefaultPosition(-1, -1, textRange.getStartOffset());
|
||||||
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
||||||
return Location.create(context.file, start, end);
|
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 start = new DefaultPosition(-1, -1, textRange.getStartOffset());
|
||||||
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
||||||
return Location.create(myFile, start, end);
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.annotations.NonNull;
|
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.idea.sdk.IdeSdks;
|
||||||
import com.android.tools.klint.client.api.*;
|
import com.android.tools.klint.client.api.*;
|
||||||
import com.android.tools.klint.checks.ApiLookup;
|
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.analysis.AnalysisScope;
|
||||||
import com.intellij.openapi.Disposable;
|
import com.intellij.openapi.Disposable;
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
@@ -77,10 +68,10 @@ import java.util.Map;
|
|||||||
import static com.android.tools.klint.detector.api.TextFormat.RAW;
|
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.
|
* 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");
|
protected static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.IntellijLintClient");
|
||||||
|
|
||||||
@NonNull protected Project myProject;
|
@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 */
|
/** Creates a lint client for batch inspections */
|
||||||
public static IntellijLintClient forBatch(@NotNull Project project,
|
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 AnalysisScope scope,
|
||||||
@NotNull List<com.android.tools.klint.detector.api.Issue> issues) {
|
@NotNull List<Issue> issues) {
|
||||||
return new BatchLintClient(project, problemMap, scope, issues);
|
return new BatchLintClient(project, problemMap, scope, issues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +130,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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()) {
|
if (project.isGradleProject() && project.isAndroidProject() && !project.isLibrary()) {
|
||||||
AndroidProject model = project.getGradleProjectModel();
|
AndroidProject model = project.getGradleProjectModel();
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
@@ -147,30 +138,30 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
LintOptions lintOptions = model.getLintOptions();
|
LintOptions lintOptions = model.getLintOptions();
|
||||||
final Map<String, Integer> overrides = lintOptions.getSeverityOverrides();
|
final Map<String, Integer> overrides = lintOptions.getSeverityOverrides();
|
||||||
if (overrides != null && !overrides.isEmpty()) {
|
if (overrides != null && !overrides.isEmpty()) {
|
||||||
return new com.android.tools.klint.client.api.DefaultConfiguration(this, project, null) {
|
return new DefaultConfiguration(this, project, null) {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@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());
|
Integer severity = overrides.get(issue.getId());
|
||||||
if (severity != null) {
|
if (severity != null) {
|
||||||
switch (severity.intValue()) {
|
switch (severity.intValue()) {
|
||||||
case LintOptions.SEVERITY_FATAL:
|
case LintOptions.SEVERITY_FATAL:
|
||||||
return com.android.tools.klint.detector.api.Severity.FATAL;
|
return Severity.FATAL;
|
||||||
case LintOptions.SEVERITY_ERROR:
|
case LintOptions.SEVERITY_ERROR:
|
||||||
return com.android.tools.klint.detector.api.Severity.ERROR;
|
return Severity.ERROR;
|
||||||
case LintOptions.SEVERITY_WARNING:
|
case LintOptions.SEVERITY_WARNING:
|
||||||
return com.android.tools.klint.detector.api.Severity.WARNING;
|
return Severity.WARNING;
|
||||||
case LintOptions.SEVERITY_INFORMATIONAL:
|
case LintOptions.SEVERITY_INFORMATIONAL:
|
||||||
return com.android.tools.klint.detector.api.Severity.INFORMATIONAL;
|
return Severity.INFORMATIONAL;
|
||||||
case LintOptions.SEVERITY_IGNORE:
|
case LintOptions.SEVERITY_IGNORE:
|
||||||
default:
|
default:
|
||||||
return com.android.tools.klint.detector.api.Severity.IGNORE;
|
return Severity.IGNORE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a LIST lookup. I should make this faster!
|
// This is a LIST lookup. I should make this faster!
|
||||||
if (!getIssues().contains(issue)) {
|
if (!getIssues().contains(issue)) {
|
||||||
return com.android.tools.klint.detector.api.Severity.IGNORE;
|
return Severity.IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getSeverity(issue);
|
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
|
@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);
|
return getIssues().contains(issue) && super.isEnabled(issue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void report(@NonNull com.android.tools.klint.detector.api.Context context,
|
public void report(@NonNull Context context,
|
||||||
@NonNull com.android.tools.klint.detector.api.Issue issue,
|
@NonNull Issue issue,
|
||||||
@NonNull com.android.tools.klint.detector.api.Severity severity,
|
@NonNull Severity severity,
|
||||||
@Nullable com.android.tools.klint.detector.api.Location location,
|
@Nullable Location location,
|
||||||
@NonNull String message,
|
@NonNull String message,
|
||||||
@NonNull com.android.tools.klint.detector.api.TextFormat format) {
|
@NonNull TextFormat format) {
|
||||||
assert false : message;
|
assert false : message;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull protected List<com.android.tools.klint.detector.api.Issue> getIssues() {
|
@NonNull protected List<Issue> getIssues() {
|
||||||
return Collections.emptyList();
|
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
|
* 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.
|
* 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,
|
protected void reportSecondary(@NonNull Context context, @NonNull Issue issue, @NonNull Severity severity, @NonNull Location location,
|
||||||
@NonNull String message, @NonNull com.android.tools.klint.detector.api.TextFormat format) {
|
@NonNull String message, @NonNull TextFormat format) {
|
||||||
com.android.tools.klint.detector.api.Location secondary = location.getSecondary();
|
Location secondary = location.getSecondary();
|
||||||
if (secondary != null) {
|
if (secondary != null) {
|
||||||
if (secondary.getMessage() != null) {
|
if (secondary.getMessage() != null) {
|
||||||
message = message + " (" + secondary.getMessage() + ")";
|
message = message + " (" + secondary.getMessage() + ")";
|
||||||
@@ -226,14 +217,14 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void log(@NonNull com.android.tools.klint.detector.api.Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) {
|
public void log(@NonNull 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) {
|
if (severity == Severity.ERROR || severity == Severity.FATAL) {
|
||||||
if (format != null) {
|
if (format != null) {
|
||||||
LOG.error(String.format(format, args), exception);
|
LOG.error(String.format(format, args), exception);
|
||||||
} else if (exception != null) {
|
} else if (exception != null) {
|
||||||
LOG.error(exception);
|
LOG.error(exception);
|
||||||
}
|
}
|
||||||
} else if (severity == com.android.tools.klint.detector.api.Severity.WARNING) {
|
} else if (severity == Severity.WARNING) {
|
||||||
if (format != null) {
|
if (format != null) {
|
||||||
LOG.warn(String.format(format, args), exception);
|
LOG.warn(String.format(format, args), exception);
|
||||||
} else if (exception != null) {
|
} else if (exception != null) {
|
||||||
@@ -249,7 +240,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public com.android.tools.klint.client.api.XmlParser getXmlParser() {
|
public XmlParser getXmlParser() {
|
||||||
return new DomPsiParser(this);
|
return new DomPsiParser(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,35 +444,35 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected List<com.android.tools.klint.detector.api.Issue> getIssues() {
|
protected List<Issue> getIssues() {
|
||||||
return myState.getIssues();
|
return myState.getIssues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void report(@NonNull com.android.tools.klint.detector.api.Context context,
|
public void report(@NonNull Context context,
|
||||||
@NonNull com.android.tools.klint.detector.api.Issue issue,
|
@NonNull Issue issue,
|
||||||
@NonNull com.android.tools.klint.detector.api.Severity severity,
|
@NonNull Severity severity,
|
||||||
@Nullable com.android.tools.klint.detector.api.Location location,
|
@Nullable Location location,
|
||||||
@NonNull String message,
|
@NonNull String message,
|
||||||
@NonNull com.android.tools.klint.detector.api.TextFormat format) {
|
@NonNull TextFormat format) {
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
final File file = location.getFile();
|
final File file = location.getFile();
|
||||||
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
|
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
|
||||||
|
|
||||||
if (myState.getMainFile().equals(vFile)) {
|
if (myState.getMainFile().equals(vFile)) {
|
||||||
final com.android.tools.klint.detector.api.Position start = location.getStart();
|
final Position start = location.getStart();
|
||||||
final com.android.tools.klint.detector.api.Position end = location.getEnd();
|
final Position end = location.getEnd();
|
||||||
|
|
||||||
final TextRange textRange = start != null && end != null && start.getOffset() <= end.getOffset()
|
final TextRange textRange = start != null && end != null && start.getOffset() <= end.getOffset()
|
||||||
? new TextRange(start.getOffset(), end.getOffset())
|
? new TextRange(start.getOffset(), end.getOffset())
|
||||||
: TextRange.EMPTY_RANGE;
|
: 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);
|
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()))) {
|
if (secondary != null && myState.getMainFile().equals(LocalFileSystem.getInstance().findFileByIoFile(secondary.getFile()))) {
|
||||||
reportSecondary(context, issue, severity, location, message, format);
|
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) {
|
public List<File> getResourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) {
|
||||||
AndroidFacet facet = AndroidFacet.getInstance(myState.getModule());
|
AndroidFacet facet = AndroidFacet.getInstance(myState.getModule());
|
||||||
if (facet != null) {
|
if (facet != null) {
|
||||||
return org.jetbrains.android.inspections.klint.IntellijLintUtils.getResourceDirectories(facet);
|
return IntellijLintUtils.getResourceDirectories(facet);
|
||||||
}
|
}
|
||||||
return super.getResourceFolders(project);
|
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 */
|
/** Lint client used for batch operations */
|
||||||
private static class BatchLintClient extends IntellijLintClient {
|
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 AnalysisScope myScope;
|
||||||
private final List<com.android.tools.klint.detector.api.Issue> myIssues;
|
private final List<Issue> myIssues;
|
||||||
|
|
||||||
public BatchLintClient(@NotNull Project project,
|
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 AnalysisScope scope,
|
||||||
@NotNull List<com.android.tools.klint.detector.api.Issue> issues) {
|
@NotNull List<Issue> issues) {
|
||||||
super(project);
|
super(project);
|
||||||
myProblemMap = problemMap;
|
myProblemMap = problemMap;
|
||||||
myScope = scope;
|
myScope = scope;
|
||||||
@@ -591,17 +582,17 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected List<com.android.tools.klint.detector.api.Issue> getIssues() {
|
protected List<Issue> getIssues() {
|
||||||
return myIssues;
|
return myIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void report(@NonNull com.android.tools.klint.detector.api.Context context,
|
public void report(@NonNull Context context,
|
||||||
@NonNull com.android.tools.klint.detector.api.Issue issue,
|
@NonNull Issue issue,
|
||||||
@NonNull com.android.tools.klint.detector.api.Severity severity,
|
@NonNull Severity severity,
|
||||||
@Nullable com.android.tools.klint.detector.api.Location location,
|
@Nullable Location location,
|
||||||
@NonNull String message,
|
@NonNull String message,
|
||||||
@NonNull com.android.tools.klint.detector.api.TextFormat format) {
|
@NonNull TextFormat format) {
|
||||||
VirtualFile vFile = null;
|
VirtualFile vFile = null;
|
||||||
File file = null;
|
File file = null;
|
||||||
|
|
||||||
@@ -642,31 +633,31 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
if (inScope) {
|
if (inScope) {
|
||||||
file = new File(PathUtil.getCanonicalPath(file.getPath()));
|
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) {
|
if (file2ProblemList == null) {
|
||||||
file2ProblemList = new HashMap<File, List<org.jetbrains.android.inspections.klint.ProblemData>>();
|
file2ProblemList = new HashMap<File, List<ProblemData>>();
|
||||||
myProblemMap.put(issue, file2ProblemList);
|
myProblemMap.put(issue, file2ProblemList);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<org.jetbrains.android.inspections.klint.ProblemData> problemList = file2ProblemList.get(file);
|
List<ProblemData> problemList = file2ProblemList.get(file);
|
||||||
if (problemList == null) {
|
if (problemList == null) {
|
||||||
problemList = new ArrayList<org.jetbrains.android.inspections.klint.ProblemData>();
|
problemList = new ArrayList<ProblemData>();
|
||||||
file2ProblemList.put(file, problemList);
|
file2ProblemList.put(file, problemList);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRange textRange = TextRange.EMPTY_RANGE;
|
TextRange textRange = TextRange.EMPTY_RANGE;
|
||||||
|
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
final com.android.tools.klint.detector.api.Position start = location.getStart();
|
final Position start = location.getStart();
|
||||||
final com.android.tools.klint.detector.api.Position end = location.getEnd();
|
final Position end = location.getEnd();
|
||||||
|
|
||||||
if (start != null && end != null && start.getOffset() <= end.getOffset()) {
|
if (start != null && end != null && start.getOffset() <= end.getOffset()) {
|
||||||
textRange = new TextRange(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);
|
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) {
|
if (location != null && location.getSecondary() != null) {
|
||||||
reportSecondary(context, issue, severity, location, message, format);
|
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) {
|
if (module != null) {
|
||||||
AndroidFacet facet = AndroidFacet.getInstance(module);
|
AndroidFacet facet = AndroidFacet.getInstance(module);
|
||||||
if (facet != null) {
|
if (facet != null) {
|
||||||
return org.jetbrains.android.inspections.klint.IntellijLintUtils.getResourceDirectories(facet);
|
return IntellijLintUtils.getResourceDirectories(facet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.getResourceFolders(project);
|
return super.getResourceFolders(project);
|
||||||
@@ -736,7 +727,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@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);
|
XmlTag tag = LocalResourceRepository.getItemTag(myProject, item);
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
ResourceFile source = item.getSource();
|
ResourceFile source = item.getSource();
|
||||||
@@ -762,7 +753,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
return super.getResourceVisibilityProvider();
|
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 File myFile;
|
||||||
private final XmlElement myNode;
|
private final XmlElement myNode;
|
||||||
private Object myClientData;
|
private Object myClientData;
|
||||||
@@ -774,7 +765,7 @@ public class IntellijLintClient extends com.android.tools.klint.client.api.LintC
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public com.android.tools.klint.detector.api.Location resolve() {
|
public Location resolve() {
|
||||||
if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
|
if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
|
||||||
return ApplicationManager.getApplication().runReadAction(this);
|
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());
|
Position start = new 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());
|
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
||||||
return com.android.tools.klint.detector.api.Location.create(myFile, start, end);
|
return Location.create(myFile, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public com.android.tools.klint.detector.api.Location compute() {
|
public Location compute() {
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-7
@@ -49,16 +49,12 @@ public class IntellijLintIssueRegistry extends BuiltinIssueRegistry {
|
|||||||
Implementation implementation = issue.getImplementation();
|
Implementation implementation = issue.getImplementation();
|
||||||
EnumSet<Scope> scope = implementation.getScope();
|
EnumSet<Scope> scope = implementation.getScope();
|
||||||
Class<? extends Detector> detectorClass = implementation.getDetectorClass();
|
Class<? extends Detector> detectorClass = implementation.getDetectorClass();
|
||||||
if (detectorClass == RegistrationDetector.class) {
|
if (detectorClass == SupportAnnotationDetector.class) {
|
||||||
issue.setImplementation(IntellijRegistrationDetector.IMPLEMENTATION);
|
|
||||||
} else if (detectorClass == ViewTypeDetector.class) {
|
|
||||||
issue.setImplementation(IntellijViewTypeDetector.IMPLEMENTATION);
|
|
||||||
} else if (detectorClass == SupportAnnotationDetector.class) {
|
|
||||||
// Handled by the ResourceTypeInspection
|
// Handled by the ResourceTypeInspection
|
||||||
continue;
|
continue;
|
||||||
} else if (scope.contains(Scope.CLASS_FILE) ||
|
} else if (scope.contains(Scope.CLASS_FILE) ||
|
||||||
scope.contains(Scope.ALL_CLASS_FILES) ||
|
scope.contains(Scope.ALL_CLASS_FILES) ||
|
||||||
scope.contains(Scope.JAVA_LIBRARIES)) {
|
scope.contains(Scope.JAVA_LIBRARIES)) {
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
assert !IntellijLintProject.SUPPORT_CLASS_FILES; // When enabled, adjust this to include class detector based issues
|
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);
|
mScope = Scope.infer(projects);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
if (!org.jetbrains.android.inspections.klint.IntellijLintProject.SUPPORT_CLASS_FILES && (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.ALL_CLASS_FILES)
|
if (!IntellijLintProject.SUPPORT_CLASS_FILES && (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.ALL_CLASS_FILES)
|
||||||
|| mScope.contains(Scope.JAVA_LIBRARIES))) {
|
|| mScope.contains(Scope.JAVA_LIBRARIES))) {
|
||||||
mScope = EnumSet.copyOf(mScope); // make mutable
|
mScope = EnumSet.copyOf(mScope); // make mutable
|
||||||
// Can't run class file based checks
|
// Can't run class file based checks
|
||||||
mScope.remove(Scope.CLASS_FILE);
|
mScope.remove(Scope.CLASS_FILE);
|
||||||
@@ -93,7 +93,7 @@ public class IntellijLintRequest extends LintRequest {
|
|||||||
if (mProjects == null) {
|
if (mProjects == null) {
|
||||||
if (myIncremental && myFileList != null && myFileList.size() == 1 && myModules.size() == 1) {
|
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 =
|
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)
|
mProjects = pair.first != null ? Collections.singletonList(pair.first)
|
||||||
: Collections.<com.android.tools.klint.detector.api.Project>emptyList();
|
: Collections.<com.android.tools.klint.detector.api.Project>emptyList();
|
||||||
myMainProject = pair.second;
|
myMainProject = pair.second;
|
||||||
@@ -102,7 +102,7 @@ public class IntellijLintRequest extends LintRequest {
|
|||||||
// and add projects for the gradle libraries and set error reporting to
|
// and add projects for the gradle libraries and set error reporting to
|
||||||
// false on those
|
// false on those
|
||||||
//mProjects = computeProjects()
|
//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 {
|
} else {
|
||||||
mProjects = super.getProjects();
|
mProjects = super.getProjects();
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-237
@@ -20,6 +20,12 @@ import com.android.annotations.NonNull;
|
|||||||
import com.android.annotations.Nullable;
|
import com.android.annotations.Nullable;
|
||||||
import com.android.builder.model.SourceProvider;
|
import com.android.builder.model.SourceProvider;
|
||||||
import com.android.tools.klint.client.api.LintRequest;
|
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.google.common.base.Splitter;
|
||||||
import com.intellij.debugger.engine.JVMNameUtil;
|
import com.intellij.debugger.engine.JVMNameUtil;
|
||||||
import com.intellij.facet.Facet;
|
import com.intellij.facet.Facet;
|
||||||
@@ -36,9 +42,7 @@ import com.intellij.openapi.vfs.VirtualFile;
|
|||||||
import com.intellij.psi.*;
|
import com.intellij.psi.*;
|
||||||
import com.intellij.psi.util.ClassUtil;
|
import com.intellij.psi.util.ClassUtil;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
import com.intellij.psi.util.TypeConversionUtil;
|
|
||||||
import org.jetbrains.android.facet.AndroidFacet;
|
import org.jetbrains.android.facet.AndroidFacet;
|
||||||
import org.jetbrains.annotations.NonNls;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.uast.UClass;
|
import org.jetbrains.uast.UClass;
|
||||||
import org.jetbrains.uast.UElement;
|
import org.jetbrains.uast.UElement;
|
||||||
@@ -49,9 +53,6 @@ import java.io.File;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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
|
* Common utilities for handling lint within IntelliJ
|
||||||
* TODO: Merge with {@link AndroidLintUtil}
|
* TODO: Merge with {@link AndroidLintUtil}
|
||||||
@@ -60,11 +61,6 @@ public class IntellijLintUtils {
|
|||||||
private 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");
|
private static final ProjectSystemId GRADLE_ID = new ProjectSystemId("GRADLE");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,7 +71,7 @@ public class IntellijLintUtils {
|
|||||||
* @return the location of the given element
|
* @return the location of the given element
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@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
|
//noinspection ConstantConditions
|
||||||
assert element.getContainingFile().getVirtualFile() == null
|
assert element.getContainingFile().getVirtualFile() == null
|
||||||
|| FileUtil.filesEqual(VfsUtilCore.virtualToIoFile(element.getContainingFile().getVirtualFile()), file);
|
|| FileUtil.filesEqual(VfsUtilCore.virtualToIoFile(element.getContainingFile().getVirtualFile()), file);
|
||||||
@@ -90,19 +86,19 @@ public class IntellijLintUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextRange textRange = element.getTextRange();
|
TextRange textRange = element.getTextRange();
|
||||||
com.android.tools.klint.detector.api.Position start = new com.android.tools.klint.detector.api.DefaultPosition(-1, -1, textRange.getStartOffset());
|
Position start = new 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());
|
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
|
||||||
return com.android.tools.klint.detector.api.Location.create(file, start, end);
|
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
|
* @param context the context to look up the file for
|
||||||
* @return the corresponding {@link PsiFile}, or null
|
* @return the corresponding {@link PsiFile}, or null
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@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);
|
VirtualFile file = VfsUtil.findFileByIoFile(context.file, false);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -115,106 +111,17 @@ public class IntellijLintUtils {
|
|||||||
return PsiManager.getInstance(project).findFile(file);
|
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) {
|
public static boolean isSuppressed(@NonNull UElement element, @NonNull UFile file, @NonNull 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
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.
|
* Computes the internal class name of the given class.
|
||||||
* For example, for PsiClass foo.bar.Foo.Bar it returns foo/bar/Foo$Bar.
|
* 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
|
* @param psiClass the class to look up the internal name for
|
||||||
* @return the internal class name
|
* @return the internal class name
|
||||||
* @see com.android.tools.klint.detector.api.ClassContext#getInternalName(String)
|
* @see ClassContext#getInternalName(String)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String getInternalName(@NonNull PsiClass psiClass) {
|
public static String getInternalName(@NonNull PsiClass psiClass) {
|
||||||
@@ -232,13 +139,13 @@ public class IntellijLintUtils {
|
|||||||
if (sig == null) {
|
if (sig == null) {
|
||||||
String qualifiedName = psiClass.getQualifiedName();
|
String qualifiedName = psiClass.getQualifiedName();
|
||||||
if (qualifiedName != null) {
|
if (qualifiedName != null) {
|
||||||
return com.android.tools.klint.detector.api.ClassContext.getInternalName(qualifiedName);
|
return ClassContext.getInternalName(qualifiedName);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} else if (sig.indexOf('.') != -1) {
|
} else if (sig.indexOf('.') != -1) {
|
||||||
// Workaround -- ClassUtil doesn't treat this correctly!
|
// Workaround -- ClassUtil doesn't treat this correctly!
|
||||||
// .replace('.', '/');
|
// .replace('.', '/');
|
||||||
sig = com.android.tools.klint.detector.api.ClassContext.getInternalName(sig);
|
sig = ClassContext.getInternalName(sig);
|
||||||
}
|
}
|
||||||
return 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) {
|
public static AndroidModelFacade getModelFacade(AndroidFacet facet) {
|
||||||
return new AndroidModelFacade(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 {
|
public class IntellijViewTypeDetector extends ViewTypeDetector {
|
||||||
static final Implementation IMPLEMENTATION = new Implementation(
|
static final Implementation IMPLEMENTATION = new Implementation(
|
||||||
IntellijViewTypeDetector.class,
|
IntellijViewTypeDetector.class,
|
||||||
Scope.JAVA_FILE_SCOPE);
|
Scope.SOURCE_FILE_SCOPE);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.tools.klint.detector.api.Issue;
|
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;
|
package org.jetbrains.android.inspections.klint;
|
||||||
|
|
||||||
import com.android.tools.klint.detector.api.Issue;
|
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 {
|
override fun visitCallExpression(node: UCallExpression): Boolean {
|
||||||
if (applicableFunctionNames.isNotEmpty()) {
|
if (applicableFunctionNames.isNotEmpty()) {
|
||||||
if (node.kind == FUNCTION_CALL && node.functionName in applicableFunctionNames) {
|
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> getApplicableSuperClasses();
|
||||||
List<String> getApplicableConstructorTypes();
|
List<String> getApplicableConstructorTypes();
|
||||||
|
|
||||||
void visitFunctionCall(UastAndroidContext context, UCallExpression node);
|
void visitCall(UastAndroidContext context, UCallExpression node);
|
||||||
void visitClass(UastAndroidContext context, UClass node);
|
void visitClass(UastAndroidContext context, UClass node);
|
||||||
void visitConstructor(UastAndroidContext context, UCallExpression functionCall, UFunction constructor);
|
void visitConstructor(UastAndroidContext context, UCallExpression functionCall, UFunction constructor);
|
||||||
|
|
||||||
|
|||||||
@@ -16,15 +16,28 @@
|
|||||||
@file:JvmName("UastUtils")
|
@file:JvmName("UastUtils")
|
||||||
package org.jetbrains.uast
|
package org.jetbrains.uast
|
||||||
|
|
||||||
import org.jetbrains.uast.kinds.UastClassKind
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
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? {
|
tailrec fun UElement?.getContainingClass(): UClass? {
|
||||||
val parent = this?.parent ?: return null
|
val parent = this?.parent ?: return null
|
||||||
if (parent is UClass) return parent
|
if (parent is UClass) return parent
|
||||||
return parent.getContainingClass()
|
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? {
|
tailrec fun UElement?.getContainingFile(): UFile? {
|
||||||
val parent = this?.parent ?: return null
|
val parent = this?.parent ?: return null
|
||||||
if (parent is UFile) return parent
|
if (parent is UFile) return parent
|
||||||
@@ -33,20 +46,45 @@ tailrec fun UElement?.getContainingFile(): UFile? {
|
|||||||
|
|
||||||
fun UElement?.getContainingClassOrEmpty() = getContainingClass() ?: UClassNotResolved
|
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? {
|
tailrec fun UElement?.getContainingFunction(): UFunction? {
|
||||||
val parent = this?.parent ?: return null
|
val parent = this?.parent ?: return null
|
||||||
if (parent is UFunction) return parent
|
if (parent is UFunction) return parent
|
||||||
return parent.getContainingFunction()
|
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? {
|
tailrec fun UElement?.getContainingDeclaration(): UDeclaration? {
|
||||||
val parent = this?.parent ?: return null
|
val parent = this?.parent ?: return null
|
||||||
if (parent is UDeclaration) return parent
|
if (parent is UDeclaration) return parent
|
||||||
return parent.getContainingDeclaration()
|
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
|
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 {
|
fun UElement.log(firstLine: String, vararg nested: Any?): String {
|
||||||
return (if (firstLine.isBlank()) "" else firstLine + "\n") + nested.joinToString("\n") {
|
return (if (firstLine.isBlank()) "" else firstLine + "\n") + nested.joinToString("\n") {
|
||||||
when (it) {
|
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
|
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.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 }
|
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 {
|
fun UClass.getAllDeclarations(context: UastContext): List<UDeclaration> = mutableListOf<UDeclaration>().apply {
|
||||||
this += declarations
|
this += declarations
|
||||||
for (superType in superTypes) {
|
for (superType in superTypes) {
|
||||||
@@ -107,14 +160,6 @@ fun UCallExpression.getQualifiedCallElement(): UExpression {
|
|||||||
return findParent(parent as? UExpression) ?: this
|
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)
|
inline fun <reified T: UElement> UElement.getParentOfType(): T? = getParentOfType(T::class.java)
|
||||||
|
|
||||||
fun <T: UElement> UElement.getParentOfType(clazz: Class<T>): T? {
|
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? {
|
fun <T> UClass.findStaticMemberOfType(name: String, type: Class<out T>): T? {
|
||||||
for (companion in companions) {
|
for (companion in companions) {
|
||||||
val classKind = companion.kind as? UastClassKind.UastCompanionObject ?: continue
|
val member = companion.declarations.firstOrNull {
|
||||||
if (!classKind.default) continue
|
it.name == name && type.isInstance(it) && it is UModifierOwner && it.hasModifier(UastModifier.STATIC)
|
||||||
|
}
|
||||||
val member = companion.declarations.firstOrNull { it.name == name && type.isInstance(it) }
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
if (member != null) return member as T
|
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.name == name && it is UModifierOwner
|
||||||
&& it.hasModifier(UastModifier.STATIC) && type.isInstance(it)
|
&& it.hasModifier(UastModifier.STATIC) && type.isInstance(it)
|
||||||
} as T
|
} 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 visitPrefixExpression(node: UPrefixExpression) = visitElement(node)
|
||||||
open fun visitPostfixExpression(node: UPostfixExpression) = visitElement(node)
|
open fun visitPostfixExpression(node: UPostfixExpression) = visitElement(node)
|
||||||
open fun visitSpecialExpressionList(node: USpecialExpressionList) = 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 visitIfExpression(node: UIfExpression) = visitElement(node)
|
||||||
open fun visitSwitchExpression(node: USwitchExpression) = visitElement(node)
|
open fun visitSwitchExpression(node: USwitchExpression) = visitElement(node)
|
||||||
open fun visitSwitchClauseExpression(node: USwitchClauseExpression) = 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 visitLiteralExpression(node: ULiteralExpression) = visitElement(node)
|
||||||
open fun visitThisExpression(node: UThisExpression) = visitElement(node)
|
open fun visitThisExpression(node: UThisExpression) = visitElement(node)
|
||||||
open fun visitSuperExpression(node: USuperExpression) = 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 visitArrayAccessExpression(node: UArrayAccessExpression) = visitElement(node)
|
||||||
open fun visitCallableReferenceExpression(node: UCallableReferenceExpression) = visitElement(node)
|
open fun visitCallableReferenceExpression(node: UCallableReferenceExpression) = visitElement(node)
|
||||||
open fun visitClassLiteralExpression(node: UClassLiteralExpression) = visitElement(node)
|
open fun visitClassLiteralExpression(node: UClassLiteralExpression) = visitElement(node)
|
||||||
open fun visitLambdaExpression(node: ULambdaExpression) = visitElement(node)
|
open fun visitLambdaExpression(node: ULambdaExpression) = visitElement(node)
|
||||||
open fun visitObjectLiteralExpression(node: UObjectLiteralExpression) = 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()
|
object EmptyUastVisitor : UastVisitor()
|
||||||
@@ -17,24 +17,59 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
Represents an applied JVM annotation.
|
||||||
|
*/
|
||||||
interface UAnnotation : UElement, UNamed, UFqNamed {
|
interface UAnnotation : UElement, UNamed, UFqNamed {
|
||||||
|
/**
|
||||||
|
* Returns the list of named value arguments.
|
||||||
|
*/
|
||||||
val valueArguments: List<UNamedExpression>
|
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?
|
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()
|
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()) }
|
fun getValues() = valueArguments.map { Pair(it.name, it.expression.evaluate()) }
|
||||||
|
|
||||||
override fun logString() = log("UAnnotation ($name)")
|
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) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitAnnotation(this)) return
|
if (visitor.visitAnnotation(this)) return
|
||||||
valueArguments.acceptList(visitor)
|
valueArguments.acceptList(visitor)
|
||||||
|
visitor.afterVisitAnnotation(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an annotated element.
|
||||||
|
*/
|
||||||
interface UAnnotated : UElement {
|
interface UAnnotated : UElement {
|
||||||
|
/**
|
||||||
|
* Returns the list of annotations applied to the current element.
|
||||||
|
*/
|
||||||
val annotations: List<UAnnotation>
|
val annotations: List<UAnnotation>
|
||||||
}
|
}
|
||||||
@@ -17,39 +17,153 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The common interface for all Uast elements.
|
||||||
|
*/
|
||||||
interface UElement {
|
interface UElement {
|
||||||
|
/**
|
||||||
|
* Returns the element parent.
|
||||||
|
*/
|
||||||
val parent: UElement?
|
val parent: UElement?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this element is valid, false otherwise.
|
||||||
|
*/
|
||||||
open val isValid: Boolean
|
open val isValid: Boolean
|
||||||
get() = true
|
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
|
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()
|
fun renderString(): String = logString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes the element to the specified visitor.
|
||||||
|
*
|
||||||
|
* @param visitor the visitor to pass the element to.
|
||||||
|
*/
|
||||||
fun accept(visitor: UastVisitor) {
|
fun accept(visitor: UastVisitor) {
|
||||||
visitor.visitElement(this)
|
visitor.visitElement(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for the [UElement] which has a name.
|
||||||
|
*/
|
||||||
interface UNamed {
|
interface UNamed {
|
||||||
|
/**
|
||||||
|
* Returns the name of this element.
|
||||||
|
*/
|
||||||
val name: String
|
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
|
fun matchesName(name: String) = this.name == name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for the [UElement] which has a qualified name.
|
||||||
|
*/
|
||||||
interface UFqNamed : UNamed {
|
interface UFqNamed : UNamed {
|
||||||
|
/**
|
||||||
|
* Returns the qualified name of this element, or null if the qualified name was not resolved.
|
||||||
|
*/
|
||||||
val fqName: String?
|
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
|
fun matchesFqName(fqName: String) = this.fqName == fqName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for the [UElement] which has Uast modifiers.
|
||||||
|
*/
|
||||||
interface UModifierOwner {
|
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
|
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 {
|
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?
|
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
|
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
|
fun resolveClass(context: UastContext): UClass? = resolve(context) as? UClass
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ interface UExpression : UElement {
|
|||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
visitor.visitElement(this)
|
visitor.visitElement(this)
|
||||||
|
visitor.afterVisitElement(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +37,6 @@ interface NoModifiers : UModifierOwner {
|
|||||||
override fun hasModifier(modifier: UastModifier) = false
|
override fun hasModifier(modifier: UastModifier) = false
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmptyExpression(override val parent: UElement) : UExpression {
|
class EmptyUExpression(override val parent: UElement) : UExpression {
|
||||||
override fun logString() = "EmptyExpression"
|
override fun logString() = "EmptyExpression"
|
||||||
}
|
}
|
||||||
@@ -17,11 +17,31 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
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 {
|
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?
|
val packageFqName: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of import statements.
|
||||||
|
*/
|
||||||
val importStatements: List<UImportStatement>
|
val importStatements: List<UImportStatement>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of declarations in this file (classes, properties, functions, etc).
|
||||||
|
*/
|
||||||
val declarations: List<UDeclaration>
|
val declarations: List<UDeclaration>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of classes containing in this file.
|
||||||
|
*/
|
||||||
val classes: List<UClass>
|
val classes: List<UClass>
|
||||||
get() = declarations.filterIsInstance<UClass>()
|
get() = declarations.filterIsInstance<UClass>()
|
||||||
|
|
||||||
@@ -32,12 +52,13 @@ interface UFile: UElement {
|
|||||||
if (visitor.visitFile(this)) return
|
if (visitor.visitFile(this)) return
|
||||||
declarations.acceptList(visitor)
|
declarations.acceptList(visitor)
|
||||||
importStatements.acceptList(visitor)
|
importStatements.acceptList(visitor)
|
||||||
|
visitor.afterVisitFile(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = "UFile (package = $packageFqName)\n" + declarations.joinToString("\n") { it.logString().withMargin }
|
override fun logString() = "UFile (package = $packageFqName)\n" + declarations.joinToString("\n") { it.logString().withMargin }
|
||||||
|
|
||||||
override fun renderString() = buildString {
|
override fun renderString() = buildString {
|
||||||
if (packageFqName != null) {
|
if (!packageFqName.isNullOrBlank()) {
|
||||||
appendln("package $packageFqName")
|
appendln("package $packageFqName")
|
||||||
appendln()
|
appendln()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,26 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent a
|
||||||
|
*
|
||||||
|
* `do {
|
||||||
|
* // body
|
||||||
|
* } while (expr)`
|
||||||
|
*
|
||||||
|
* loop expression.
|
||||||
|
*/
|
||||||
interface UDoWhileExpression : ULoopExpression {
|
interface UDoWhileExpression : ULoopExpression {
|
||||||
|
/**
|
||||||
|
* Returns the loop post-condition.
|
||||||
|
*/
|
||||||
val condition: UExpression
|
val condition: UExpression
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitDoWhileExpression(this)) return
|
if (visitor.visitDoWhileExpression(this)) return
|
||||||
condition.accept(visitor)
|
condition.accept(visitor)
|
||||||
body.accept(visitor)
|
body.accept(visitor)
|
||||||
|
visitor.afterVisitDoWhileExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString() = buildString {
|
override fun renderString() = buildString {
|
||||||
|
|||||||
@@ -17,8 +17,24 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a
|
||||||
|
*
|
||||||
|
* `for (element : collectionOfElements) {
|
||||||
|
* // body
|
||||||
|
* }`
|
||||||
|
*
|
||||||
|
* loop expression.
|
||||||
|
*/
|
||||||
interface UForEachExpression : ULoopExpression {
|
interface UForEachExpression : ULoopExpression {
|
||||||
|
/**
|
||||||
|
* Returns the loop variable.
|
||||||
|
*/
|
||||||
val variable: UVariable
|
val variable: UVariable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the iterated value (collection, sequence, iterable etc.)
|
||||||
|
*/
|
||||||
val iteratedValue: UExpression
|
val iteratedValue: UExpression
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
@@ -26,6 +42,7 @@ interface UForEachExpression : ULoopExpression {
|
|||||||
variable.accept(visitor)
|
variable.accept(visitor)
|
||||||
iteratedValue.accept(visitor)
|
iteratedValue.accept(visitor)
|
||||||
body.accept(visitor)
|
body.accept(visitor)
|
||||||
|
visitor.afterVisitForEachExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString() = buildString {
|
override fun renderString() = buildString {
|
||||||
|
|||||||
@@ -17,9 +17,29 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a
|
||||||
|
*
|
||||||
|
* `for (initDeclarations; loopCondition; update) {
|
||||||
|
* // body
|
||||||
|
* }`
|
||||||
|
*
|
||||||
|
* loop expression.
|
||||||
|
*/
|
||||||
interface UForExpression : ULoopExpression {
|
interface UForExpression : ULoopExpression {
|
||||||
|
/**
|
||||||
|
* Returns the [UExpression] containing variable declarations, or null if the are no variables declared.
|
||||||
|
*/
|
||||||
val declaration: UExpression?
|
val declaration: UExpression?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the loop condition, or null if the condition is empty.
|
||||||
|
*/
|
||||||
val condition: UExpression?
|
val condition: UExpression?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the loop update expression(s).
|
||||||
|
*/
|
||||||
val update: UExpression?
|
val update: UExpression?
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
@@ -28,6 +48,7 @@ interface UForExpression : ULoopExpression {
|
|||||||
condition?.accept(visitor)
|
condition?.accept(visitor)
|
||||||
update?.accept(visitor)
|
update?.accept(visitor)
|
||||||
body.accept(visitor)
|
body.accept(visitor)
|
||||||
|
visitor.afterVisitForExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString() = buildString {
|
override fun renderString() = buildString {
|
||||||
|
|||||||
@@ -17,10 +17,40 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
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 {
|
interface UIfExpression : UExpression {
|
||||||
|
/**
|
||||||
|
* Returns the condition expression.
|
||||||
|
*/
|
||||||
val condition: UExpression
|
val condition: UExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the expression which is executed if the condition is true, or null if the expression is empty.
|
||||||
|
*/
|
||||||
val thenBranch: UExpression?
|
val thenBranch: UExpression?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the expression which is executed if the condition is false, or null if the expression is empty.
|
||||||
|
*/
|
||||||
val elseBranch: UExpression?
|
val elseBranch: UExpression?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the expression is ternary (condition ? trueExpression : falseExpression).
|
||||||
|
*/
|
||||||
val isTernary: Boolean
|
val isTernary: Boolean
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
@@ -28,9 +58,11 @@ interface UIfExpression : UExpression {
|
|||||||
condition.accept(visitor)
|
condition.accept(visitor)
|
||||||
thenBranch?.accept(visitor)
|
thenBranch?.accept(visitor)
|
||||||
elseBranch?.accept(visitor)
|
elseBranch?.accept(visitor)
|
||||||
|
visitor.afterVisitIfExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = log("UIfExpression", condition, thenBranch, elseBranch)
|
override fun logString() = log("UIfExpression", condition, thenBranch, elseBranch)
|
||||||
|
|
||||||
override fun renderString() = buildString {
|
override fun renderString() = buildString {
|
||||||
if (isTernary) {
|
if (isTernary) {
|
||||||
append("(" + condition.renderString() + ")")
|
append("(" + condition.renderString() + ")")
|
||||||
@@ -42,7 +74,7 @@ interface UIfExpression : UExpression {
|
|||||||
append("if (${condition.renderString()}) ")
|
append("if (${condition.renderString()}) ")
|
||||||
thenBranch?.let { append(it.renderString()) }
|
thenBranch?.let { append(it.renderString()) }
|
||||||
val elseBranch = elseBranch
|
val elseBranch = elseBranch
|
||||||
if (elseBranch != null && elseBranch !is EmptyExpression) {
|
if (elseBranch != null && elseBranch !is EmptyUExpression) {
|
||||||
if (thenBranch !is UBlockExpression) append(" ")
|
if (thenBranch !is UBlockExpression) append(" ")
|
||||||
append("else ")
|
append("else ")
|
||||||
append(elseBranch.renderString())
|
append(elseBranch.renderString())
|
||||||
|
|||||||
@@ -15,6 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.jetbrains.uast
|
package org.jetbrains.uast
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the loop expression.
|
||||||
|
*/
|
||||||
interface ULoopExpression : UExpression {
|
interface ULoopExpression : UExpression {
|
||||||
|
/**
|
||||||
|
* Returns the loop body [UExpression].
|
||||||
|
*/
|
||||||
val body: UExpression
|
val body: UExpression
|
||||||
}
|
}
|
||||||
@@ -17,14 +17,35 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a
|
||||||
|
*
|
||||||
|
* ` switch (expression) {
|
||||||
|
* case value1 -> expr1
|
||||||
|
* case value2 -> expr2
|
||||||
|
* ...
|
||||||
|
* else -> exprElse
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* conditional expression.
|
||||||
|
*/
|
||||||
interface USwitchExpression : UExpression {
|
interface USwitchExpression : UExpression {
|
||||||
|
/*
|
||||||
|
Returns the expression on which the `switch` expression is performed.
|
||||||
|
*/
|
||||||
val expression: UExpression?
|
val expression: UExpression?
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns the switch body.
|
||||||
|
Body should contain [USwitchClauseExpression] expressions.
|
||||||
|
*/
|
||||||
val body: UExpression
|
val body: UExpression
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitSwitchExpression(this)) return
|
if (visitor.visitSwitchExpression(this)) return
|
||||||
expression?.accept(visitor)
|
expression?.accept(visitor)
|
||||||
body.accept(visitor)
|
body.accept(visitor)
|
||||||
|
visitor.afterVisitSwitchExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = log("USwitchExpression", expression, body)
|
override fun logString() = log("USwitchExpression", expression, body)
|
||||||
@@ -35,23 +56,47 @@ interface USwitchExpression : UExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface USwitchClauseExpression : UExpression
|
/**
|
||||||
|
* Represents a [USwitchExpression] clause.
|
||||||
interface UExpressionSwitchClauseExpression : USwitchClauseExpression {
|
* [USwitchClauseExpression] does not contain the clause body,
|
||||||
val caseValue: UExpression
|
* 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) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitSwitchClauseExpression(this)) return
|
if (visitor.visitSwitchClauseExpression(this)) return
|
||||||
caseValue.accept(visitor)
|
caseValues?.acceptList(visitor)
|
||||||
|
visitor.afterVisitSwitchClauseExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString() = caseValue.renderString() + " -> "
|
override fun renderString() = (caseValues?.joinToString { it.renderString() } ?: "else") + " -> "
|
||||||
override fun logString() = log("UExpressionSwitchClauseExpression", caseValue)
|
override fun logString() = log("USwitchClauseExpression", caseValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UDefaultSwitchClauseExpression : USwitchClauseExpression {
|
/**
|
||||||
override fun logString() = "UDefaultSwitchClause"
|
* Represents a [USwitchExpression] clause with the body.
|
||||||
override fun renderString() = "else -> "
|
* [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
|
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 {
|
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>?
|
val resources: List<UElement>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the `try` clause expression.
|
||||||
|
*/
|
||||||
val tryClause: UExpression
|
val tryClause: UExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the `catch` clauses [UCatchClause] expression list.
|
||||||
|
*/
|
||||||
val catchClauses: List<UCatchClause>
|
val catchClauses: List<UCatchClause>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the `finally` clause expression, or null if the `finally` clause is absent.
|
||||||
|
*/
|
||||||
val finallyClause: UExpression?
|
val finallyClause: UExpression?
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
@@ -29,13 +64,14 @@ interface UTryExpression : UExpression {
|
|||||||
tryClause.accept(visitor)
|
tryClause.accept(visitor)
|
||||||
catchClauses.acceptList(visitor)
|
catchClauses.acceptList(visitor)
|
||||||
finallyClause?.accept(visitor)
|
finallyClause?.accept(visitor)
|
||||||
|
visitor.afterVisitTryExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString() = buildString {
|
override fun renderString() = buildString {
|
||||||
append("try ")
|
append("try ")
|
||||||
appendln(tryClause.renderString())
|
appendln(tryClause.renderString().trim('\n'))
|
||||||
catchClauses.forEach { appendln(it.renderString()) }
|
catchClauses.forEach { appendln(it.renderString().trim('\n')) }
|
||||||
finallyClause?.let { append("finally ").append(it.renderString()) }
|
finallyClause?.let { append("finally ").append(it.renderString().trim('\n')) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = "UTryExpression\n" +
|
override fun logString() = "UTryExpression\n" +
|
||||||
@@ -44,9 +80,23 @@ interface UTryExpression : UExpression {
|
|||||||
(finallyClause?.let { it.logString().withMargin } ?: "<no finally clause>" )
|
(finallyClause?.let { it.logString().withMargin } ?: "<no finally clause>" )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the `catch` clause in [UTryExpression].
|
||||||
|
*/
|
||||||
interface UCatchClause : UElement {
|
interface UCatchClause : UElement {
|
||||||
|
/**
|
||||||
|
* Returns the `catch` clause body expression.
|
||||||
|
*/
|
||||||
val body: UExpression
|
val body: UExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exception parameter variables for this `catch` clause.
|
||||||
|
*/
|
||||||
val parameters: List<UVariable>
|
val parameters: List<UVariable>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exception types for this `catch` clause.
|
||||||
|
*/
|
||||||
val types: List<UType>
|
val types: List<UType>
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
@@ -54,6 +104,7 @@ interface UCatchClause : UElement {
|
|||||||
body.accept(visitor)
|
body.accept(visitor)
|
||||||
parameters.acceptList(visitor)
|
parameters.acceptList(visitor)
|
||||||
types.acceptList(visitor)
|
types.acceptList(visitor)
|
||||||
|
visitor.afterVisitCatchClause(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = log("UCatchClause", body)
|
override fun logString() = log("UCatchClause", body)
|
||||||
|
|||||||
@@ -17,13 +17,26 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a
|
||||||
|
*
|
||||||
|
* `while (condition) {
|
||||||
|
* // body
|
||||||
|
* }`
|
||||||
|
*
|
||||||
|
* expression.
|
||||||
|
*/
|
||||||
interface UWhileExpression : ULoopExpression {
|
interface UWhileExpression : ULoopExpression {
|
||||||
|
/**
|
||||||
|
* Returns the loop condition.
|
||||||
|
*/
|
||||||
val condition: UExpression
|
val condition: UExpression
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitWhileExpression(this)) return
|
if (visitor.visitWhileExpression(this)) return
|
||||||
condition.accept(visitor)
|
condition.accept(visitor)
|
||||||
body.accept(visitor)
|
body.accept(visitor)
|
||||||
|
visitor.afterVisitWhileExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString() = buildString {
|
override fun renderString() = buildString {
|
||||||
|
|||||||
@@ -15,41 +15,94 @@
|
|||||||
*/
|
*/
|
||||||
package org.jetbrains.uast
|
package org.jetbrains.uast
|
||||||
|
|
||||||
import org.jetbrains.uast.kinds.UastClassKind
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
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
|
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 isAnonymous: Boolean
|
||||||
val visibility: UastVisibility
|
|
||||||
|
/**
|
||||||
|
* Returns the class kind (ordinary class, interface, annotation, etc.).
|
||||||
|
*/
|
||||||
val kind: UastClassKind
|
val kind: UastClassKind
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default type for this class.
|
||||||
|
*/
|
||||||
val defaultType: UType
|
val defaultType: UType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the class companions.
|
||||||
|
*/
|
||||||
val companions: List<UClass>
|
val companions: List<UClass>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the class JVM name, or null if the JVM name is unknown.
|
||||||
|
*/
|
||||||
open val internalName: String?
|
open val internalName: String?
|
||||||
get() = null
|
get() = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the all supertypes of this class.
|
||||||
|
*/
|
||||||
val superTypes: List<UType>
|
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>
|
val declarations: List<UDeclaration>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns nested classes declared in this class.
|
||||||
|
*/
|
||||||
val nestedClasses: List<UClass>
|
val nestedClasses: List<UClass>
|
||||||
get() = declarations.filterIsInstance<UClass>()
|
get() = declarations.filterIsInstance<UClass>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns functions declared in this class.
|
||||||
|
*/
|
||||||
val functions: List<UFunction>
|
val functions: List<UFunction>
|
||||||
get() = declarations.filterIsInstance<UFunction>()
|
get() = declarations.filterIsInstance<UFunction>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns properties declared in this class.
|
||||||
|
*/
|
||||||
val properties: List<UVariable>
|
val properties: List<UVariable>
|
||||||
get() = declarations.filterIsInstance<UVariable>()
|
get() = declarations.filterIsInstance<UVariable>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns constructors declared in this class.
|
||||||
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val constructors: List<UFunction>
|
val constructors: List<UFunction>
|
||||||
get() = declarations.filter { it is UFunction && it.kind == UastFunctionKind.CONSTRUCTOR } as 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
|
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?
|
fun getSuperClass(context: UastContext): UClass?
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
@@ -57,24 +110,24 @@ interface UClass : UDeclaration, UFqNamed, UModifierOwner, UAnnotated {
|
|||||||
nameElement?.accept(visitor)
|
nameElement?.accept(visitor)
|
||||||
declarations.acceptList(visitor)
|
declarations.acceptList(visitor)
|
||||||
annotations.acceptList(visitor)
|
annotations.acceptList(visitor)
|
||||||
|
visitor.afterVisitClass(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString(): String {
|
override fun renderString() = buildString {
|
||||||
val modifiers = listOf(UastModifier.ABSTRACT, UastModifier.FINAL, UastModifier.STATIC)
|
appendWithSpace(visibility.name)
|
||||||
.filter { hasModifier(it) }.joinToString(" ") { it.name }.let { if (it.isBlank()) it else "$it " }
|
appendWithSpace(renderModifiers())
|
||||||
|
appendWithSpace(kind.text)
|
||||||
val name = if (isAnonymous) "" else " $name"
|
appendWithSpace(name)
|
||||||
|
|
||||||
val declarations = if (declarations.isEmpty()) "" else buildString {
|
val declarations = if (declarations.isEmpty()) "" else buildString {
|
||||||
appendln("{")
|
appendln("{")
|
||||||
append(declarations.joinToString("\n") { it.renderString() }.withMargin)
|
append(declarations.joinToString("\n\n") { it.renderString().trim('\n') }.withMargin)
|
||||||
append("\n}")
|
append("\n}")
|
||||||
}
|
}
|
||||||
|
append(declarations)
|
||||||
return "${visibility.name} " + modifiers + kind.text + name + " " + 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 {
|
object UClassNotResolved : UClass {
|
||||||
@@ -88,7 +141,7 @@ object UClassNotResolved : UClass {
|
|||||||
override val defaultType = UastErrorType
|
override val defaultType = UastErrorType
|
||||||
override val nameElement = null
|
override val nameElement = null
|
||||||
override val parent = null
|
override val parent = null
|
||||||
override val name = "<class not resolved>"
|
override val name = ERROR_NAME
|
||||||
override val fqName = null
|
override val fqName = null
|
||||||
override val internalName = null
|
override val internalName = null
|
||||||
|
|
||||||
|
|||||||
@@ -17,16 +17,38 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a declaration.
|
||||||
|
*/
|
||||||
interface UDeclaration : UElement, UNamed {
|
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?
|
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) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
visitor.visitElement(this)
|
visitor.visitElement(this)
|
||||||
|
visitor.afterVisitElement(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object UDeclarationNotResolved : UDeclaration {
|
object UDeclarationNotResolved : UDeclaration {
|
||||||
override val name = "<declaration not resolved>"
|
override val name = ERROR_NAME
|
||||||
override val nameElement = null
|
override val nameElement = null
|
||||||
override val parent = null
|
override val parent = null
|
||||||
|
|
||||||
|
|||||||
@@ -17,58 +17,109 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
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 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?
|
open val bytecodeDescriptor: String?
|
||||||
get() = null
|
get() = null
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of all super functions for this function.
|
||||||
|
*/
|
||||||
fun getSuperFunctions(context: UastContext): List<UFunction>
|
fun getSuperFunctions(context: UastContext): List<UFunction>
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitFunction(this)) return
|
if (visitor.visitFunction(this)) return
|
||||||
nameElement?.accept(visitor)
|
nameElement?.accept(visitor)
|
||||||
valueParameters.acceptList(visitor)
|
valueParameters.acceptList(visitor)
|
||||||
body.accept(visitor)
|
body?.accept(visitor)
|
||||||
annotations.acceptList(visitor)
|
annotations.acceptList(visitor)
|
||||||
typeParameters.acceptList(visitor)
|
typeParameters.acceptList(visitor)
|
||||||
returnType?.accept(visitor)
|
returnType?.accept(visitor)
|
||||||
|
visitor.afterVisitFunction(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString(): String {
|
override fun renderString(): String = buildString {
|
||||||
val typeParameters = if (typeParameterCount == 0) "" else "<" + typeParameters.joinToString { it.renderString() } + "> "
|
appendWithSpace(visibility.name)
|
||||||
val valueParameters = valueParameters.joinToString { it.renderString() }
|
appendWithSpace(renderModifiers())
|
||||||
val returnType = returnType?.let { ": " + it.renderString() } ?: ""
|
append("fun ")
|
||||||
val body = when (body) {
|
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()
|
is UBlockExpression -> " " + body.renderString()
|
||||||
else -> " = " + body.renderString()
|
else -> " = " + body.renderString()
|
||||||
}
|
}
|
||||||
return "${visibility.name} fun " + typeParameters + name + "(" + valueParameters + ")" + returnType + body
|
append(bodyRendered)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = "UFunction ($name, kind = ${kind.text}, " +
|
override fun logString() = log("UFunction ($name, kind = ${kind.text}, paramCount = $valueParameterCount)", body)
|
||||||
"paramCount = $valueParameterCount)\n" + body.logString().withMargin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object UFunctionNotResolved : UFunction {
|
object UFunctionNotResolved : UFunction {
|
||||||
override val kind = UastFunctionKind("<unknown>")
|
override val kind = UastFunctionKind(ERROR_NAME)
|
||||||
override val valueParameters = emptyList<UVariable>()
|
override val valueParameters = emptyList<UVariable>()
|
||||||
override val valueParameterCount = 0
|
override val valueParameterCount = 0
|
||||||
override val typeParameters = emptyList<UTypeReference>()
|
override val typeParameters = emptyList<UTypeReference>()
|
||||||
override val typeParameterCount = 0
|
override val typeParameterCount = 0
|
||||||
override val returnType = null
|
override val returnType = null
|
||||||
override val body = EmptyExpression(this)
|
override val body = null
|
||||||
override val visibility = UastVisibility.PRIVATE
|
override val visibility = UastVisibility.PRIVATE
|
||||||
override val nameElement = null
|
override val nameElement = null
|
||||||
override val parent = null
|
override val parent = null
|
||||||
override val name = "<function not resolved>"
|
override val name = ERROR_NAME
|
||||||
|
|
||||||
override fun hasModifier(modifier: UastModifier) = false
|
override fun hasModifier(modifier: UastModifier) = false
|
||||||
override fun getSuperFunctions(context: UastContext) = emptyList<UFunction>()
|
override fun getSuperFunctions(context: UastContext) = emptyList<UFunction>()
|
||||||
|
|||||||
@@ -15,19 +15,33 @@
|
|||||||
*/
|
*/
|
||||||
package org.jetbrains.uast
|
package org.jetbrains.uast
|
||||||
|
|
||||||
import org.jetbrains.uast.kinds.UastImportKind
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an import statement.
|
||||||
|
*/
|
||||||
interface UImportStatement : UElement {
|
interface UImportStatement : UElement {
|
||||||
|
/**
|
||||||
|
* Returns the qualified name to import.
|
||||||
|
*/
|
||||||
val fqNameToImport: String?
|
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?
|
fun resolve(context: UastContext): UDeclaration?
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
visitor.visitImportStatement(this)
|
visitor.visitImportStatement(this)
|
||||||
|
visitor.afterVisitImportStatement(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = "UImport ($fqNameToImport)"
|
override fun logString() = "UImport ($fqNameToImport)"
|
||||||
|
|||||||
@@ -17,50 +17,105 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
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 {
|
interface UType : UElement, UNamed, UFqNamed, UAnnotated, UResolvable {
|
||||||
override fun matchesName(name: String) = this.name == name || this.name.endsWith(".$name")
|
/**
|
||||||
|
* 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 */
|
* The simple type name is only for the debug purposes. Do not check against it in the production code.
|
||||||
|
*/
|
||||||
override val name: String
|
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
|
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
|
val isLong: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the type is either a boxed or an unboxed [Float], false otherwise.
|
||||||
|
*/
|
||||||
val isFloat: Boolean
|
val isFloat: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the type is either a boxed or an unboxed [Double], false otherwise.
|
||||||
|
*/
|
||||||
val isDouble: Boolean
|
val isDouble: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the type is either a boxed or an unboxed [Character], false otherwise.
|
||||||
|
*/
|
||||||
val isChar: Boolean
|
val isChar: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the type is either a boxed or an unboxed [Boolean], false otherwise.
|
||||||
|
*/
|
||||||
val isBoolean: Boolean
|
val isBoolean: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the type is either a boxed or an unboxed [Byte], false otherwise.
|
||||||
|
*/
|
||||||
val isByte: Boolean
|
val isByte: Boolean
|
||||||
|
|
||||||
override fun logString() = "UType ($name)"
|
override fun logString() = "UType ($name)"
|
||||||
override fun renderString() = 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 resolve(context: UastContext): UClass?
|
||||||
|
|
||||||
override fun resolveOrEmpty(context: UastContext) = resolve(context) ?: UClassNotResolved
|
override fun resolveOrEmpty(context: UastContext) = resolve(context) ?: UClassNotResolved
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitType(this)) return
|
if (visitor.visitType(this)) return
|
||||||
annotations.acceptList(visitor)
|
annotations.acceptList(visitor)
|
||||||
|
visitor.afterVisitType(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a type reference.
|
||||||
|
*/
|
||||||
interface UTypeReference : UDeclaration, UResolvable {
|
interface UTypeReference : UDeclaration, UResolvable {
|
||||||
override fun renderString() = ""
|
override fun renderString() = ""
|
||||||
override fun logString() = log("UTypeReference")
|
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 resolve(context: UastContext): UClass?
|
||||||
|
|
||||||
override fun resolveOrEmpty(context: UastContext) = resolve(context) ?: UClassNotResolved
|
override fun resolveOrEmpty(context: UastContext) = resolve(context) ?: UClassNotResolved
|
||||||
}
|
}
|
||||||
|
|
||||||
object UastErrorType : UType, NoAnnotations {
|
object UastErrorType : UType, NoAnnotations {
|
||||||
override val isInt = false
|
override val isInt = false
|
||||||
override val isLong = false
|
override val isLong = false
|
||||||
|
override val isShort = false
|
||||||
override val isFloat = false
|
override val isFloat = false
|
||||||
override val isDouble = false
|
override val isDouble = false
|
||||||
override val isChar = false
|
override val isChar = false
|
||||||
override val isByte = false
|
override val isByte = false
|
||||||
override val parent = null
|
override val parent = null
|
||||||
override val name = "<error>"
|
override val name = ERROR_NAME
|
||||||
override val fqName = null
|
override val fqName = null
|
||||||
override val isBoolean = false
|
override val isBoolean = false
|
||||||
override fun resolve(context: UastContext) = null
|
override fun resolve(context: UastContext) = null
|
||||||
|
|||||||
@@ -17,16 +17,26 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
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?
|
val initializer: UExpression?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the variable kind.
|
||||||
|
*/
|
||||||
val kind: UastVariableKind
|
val kind: UastVariableKind
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the variable type.
|
||||||
|
*/
|
||||||
val type: UType
|
val type: UType
|
||||||
val visibility: UastVisibility
|
|
||||||
|
|
||||||
open val getters: List<UFunction>?
|
/**
|
||||||
get() = null
|
* Return the list of accessors if the variable is a property, or null otherwise.
|
||||||
|
*/
|
||||||
open val setters: List<UFunction>?
|
open val accessors: List<UFunction>?
|
||||||
get() = null
|
get() = null
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
@@ -35,13 +45,28 @@ interface UVariable : UDeclaration, UModifierOwner, UAnnotated {
|
|||||||
initializer?.accept(visitor)
|
initializer?.accept(visitor)
|
||||||
annotations.acceptList(visitor)
|
annotations.acceptList(visitor)
|
||||||
type.accept(visitor)
|
type.accept(visitor)
|
||||||
|
visitor.afterVisitVariable(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderString(): String {
|
override fun renderString(): String = buildString {
|
||||||
val initializer = if (initializer != null && initializer !is EmptyExpression) " = ${initializer!!.renderString()}" else ""
|
if (kind != UastVariableKind.VALUE_PARAMETER) appendWithSpace(visibility.name)
|
||||||
val prefix = if (kind == UastVariableKind.VALUE_PARAMETER) "" else "var "
|
appendWithSpace(renderModifiers())
|
||||||
val emptyLine = if (kind == UastVariableKind.MEMBER) "\n" else ""
|
if (kind != UastVariableKind.VALUE_PARAMETER) append("var ")
|
||||||
return "$prefix$name: " + type.name + initializer + emptyLine
|
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" +
|
override fun logString() = "UVariable ($name, kind = ${kind.name})\n" +
|
||||||
@@ -50,11 +75,11 @@ interface UVariable : UDeclaration, UModifierOwner, UAnnotated {
|
|||||||
|
|
||||||
object UVariableNotResolved : UVariable {
|
object UVariableNotResolved : UVariable {
|
||||||
override val initializer = null
|
override val initializer = null
|
||||||
override val kind = UastVariableKind.MEMBER
|
override val kind = UastVariableKind(ERROR_NAME)
|
||||||
override val type = UastErrorType
|
override val type = UastErrorType
|
||||||
override val nameElement = null
|
override val nameElement = null
|
||||||
override val parent = null
|
override val parent = null
|
||||||
override val name = "<variable not resolved>"
|
override val name = ERROR_NAME
|
||||||
override val visibility = UastVisibility.LOCAL
|
override val visibility = UastVisibility.LOCAL
|
||||||
|
|
||||||
override fun hasModifier(modifier: UastModifier) = false
|
override fun hasModifier(modifier: UastModifier) = false
|
||||||
|
|||||||
@@ -17,14 +17,25 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents `receiver[index0, ..., indexN]` expression.
|
||||||
|
*/
|
||||||
interface UArrayAccessExpression : UExpression {
|
interface UArrayAccessExpression : UExpression {
|
||||||
|
/**
|
||||||
|
* Returns the receiver expression.
|
||||||
|
*/
|
||||||
val receiver: UExpression
|
val receiver: UExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of index expressions.
|
||||||
|
*/
|
||||||
val indices: List<UExpression>
|
val indices: List<UExpression>
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitArrayAccessExpression(this)) return
|
if (visitor.visitArrayAccessExpression(this)) return
|
||||||
receiver.accept(visitor)
|
receiver.accept(visitor)
|
||||||
indices.acceptList(visitor)
|
indices.acceptList(visitor)
|
||||||
|
visitor.afterVisitArrayAccessExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() = log("UArrayAccessExpression", receiver, indices)
|
override fun logString() = log("UArrayAccessExpression", receiver, indices)
|
||||||
|
|||||||
@@ -17,15 +17,30 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a binary expression (value1 op value2), eg. `2 + "A"`.
|
||||||
|
*/
|
||||||
interface UBinaryExpression : UExpression {
|
interface UBinaryExpression : UExpression {
|
||||||
|
/**
|
||||||
|
* Returns the left operand.
|
||||||
|
*/
|
||||||
val leftOperand: UExpression
|
val leftOperand: UExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the binary operator.
|
||||||
|
*/
|
||||||
val operator: UastBinaryOperator
|
val operator: UastBinaryOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the right operand.
|
||||||
|
*/
|
||||||
val rightOperand: UExpression
|
val rightOperand: UExpression
|
||||||
|
|
||||||
override fun accept(visitor: UastVisitor) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitBinaryExpression(this)) return
|
if (visitor.visitBinaryExpression(this)) return
|
||||||
leftOperand.accept(visitor)
|
leftOperand.accept(visitor)
|
||||||
rightOperand.accept(visitor)
|
rightOperand.accept(visitor)
|
||||||
|
visitor.afterVisitBinaryExpression(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logString() =
|
override fun logString() =
|
||||||
|
|||||||
+16
-1
@@ -17,17 +17,32 @@ package org.jetbrains.uast
|
|||||||
|
|
||||||
import org.jetbrains.uast.visitor.UastVisitor
|
import org.jetbrains.uast.visitor.UastVisitor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a binary expression with type (value op type), e.g. ("A" instanceof String).
|
||||||
|
*/
|
||||||
interface UBinaryExpressionWithType : UExpression {
|
interface UBinaryExpressionWithType : UExpression {
|
||||||
|
/**
|
||||||
|
* Returns the operand expression.
|
||||||
|
*/
|
||||||
val operand: UExpression
|
val operand: UExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the operation kind.
|
||||||
|
*/
|
||||||
val operationKind: UastBinaryExpressionWithTypeKind
|
val operationKind: UastBinaryExpressionWithTypeKind
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type.
|
||||||
|
*/
|
||||||
val type: UType
|
val type: UType
|
||||||
|
|
||||||
override fun logString() = log("UBinaryExpressionWithType (${getExpressionType()?.name}, ${operationKind.name})", operand)
|
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) {
|
override fun accept(visitor: UastVisitor) {
|
||||||
if (visitor.visitBinaryExpressionWithType(this)) return
|
if (visitor.visitBinaryExpressionWithType(this)) return
|
||||||
operand.accept(visitor)
|
operand.accept(visitor)
|
||||||
type.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