as31: Fix compilation for Android Studio 2.4

This commit is contained in:
Vyacheslav Gerasimov
2017-03-23 13:07:43 +03:00
committed by Nikolay Krasko
parent a4699add8e
commit 6bca651848
6 changed files with 2640 additions and 0 deletions
@@ -0,0 +1,149 @@
/*
* Copyright 2010-2015 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.kotlin.android.navigation;
import com.android.resources.ResourceType;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlElement;
import org.jetbrains.android.dom.AndroidAttributeValue;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.dom.manifest.ManifestElementWithRequiredName;
import org.jetbrains.android.dom.resources.Attr;
import org.jetbrains.android.dom.resources.DeclareStyleable;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.resourceManagers.LocalResourceManager;
import org.jetbrains.android.resourceManagers.ModuleResourceManagers;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.android.AndroidUtilKt;
import org.jetbrains.kotlin.psi.KtSimpleNameExpression;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// TODO: ask for extension point
// this class is mostly copied from org.jetbrains.android.AndroidGotoDeclarationHandler
public class KotlinAndroidGotoDeclarationHandler implements GotoDeclarationHandler {
@Override
public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement sourceElement, int offset, Editor editor) {
KtSimpleNameExpression referenceExpression = GotoResourceHelperKt.getReferenceExpression(sourceElement);
if (referenceExpression == null) {
return null;
}
AndroidFacet facet = AndroidUtilKt.getAndroidFacetForFile(referenceExpression);
if (facet == null) {
return null;
}
AndroidResourceUtil.MyReferredResourceFieldInfo info = GotoResourceHelperKt.getInfo(referenceExpression, facet);
if (info == null) return null;
String nestedClassName = info.getClassName();
String fieldName = info.getFieldName();
List<PsiElement> resourceList = new ArrayList<PsiElement>();
if (info.isFromManifest()) {
collectManifestElements(nestedClassName, fieldName, facet, resourceList);
}
else {
ModuleResourceManagers managers = ModuleResourceManagers.getInstance(facet);
ResourceManager manager = info.isSystem()
? managers.getSystemResourceManager(false)
: managers.getLocalResourceManager();
if (manager == null) {
return null;
}
manager.collectLazyResourceElements(nestedClassName, fieldName, false, referenceExpression, resourceList);
if (manager instanceof LocalResourceManager) {
LocalResourceManager lrm = (LocalResourceManager) manager;
if (nestedClassName.equals(ResourceType.ATTR.getName())) {
for (Attr attr : lrm.findAttrs(fieldName)) {
resourceList.add(attr.getName().getXmlAttributeValue());
}
}
else if (nestedClassName.equals(ResourceType.STYLEABLE.getName())) {
for (DeclareStyleable styleable : lrm.findStyleables(fieldName)) {
resourceList.add(styleable.getName().getXmlAttributeValue());
}
for (Attr styleable : lrm.findStyleableAttributesByFieldName(fieldName)) {
resourceList.add(styleable.getName().getXmlAttributeValue());
}
}
}
}
if (resourceList.size() > 1) {
// Sort to ensure the output is stable, and to prefer the base folders
Collections.sort(resourceList, AndroidResourceUtil.RESOURCE_ELEMENT_COMPARATOR);
}
return resourceList.toArray(new PsiElement[resourceList.size()]);
}
private static void collectManifestElements(
@NotNull String nestedClassName,
@NotNull String fieldName,
@NotNull AndroidFacet facet,
@NotNull List<PsiElement> result
) {
Manifest manifest = facet.getManifest();
if (manifest == null) {
return;
}
List<? extends ManifestElementWithRequiredName> list;
if ("permission".equals(nestedClassName)) {
list = manifest.getPermissions();
}
else if ("permission_group".equals(nestedClassName)) {
list = manifest.getPermissionGroups();
}
else {
return;
}
for (ManifestElementWithRequiredName domElement : list) {
AndroidAttributeValue<String> nameAttribute = domElement.getName();
String name = nameAttribute.getValue();
if (AndroidUtils.equal(name, fieldName, false)) {
XmlElement psiElement = nameAttribute.getXmlAttributeValue();
if (psiElement != null) {
result.add(psiElement);
}
}
}
}
@Override
public String getActionText(DataContext context) {
return null;
}
}
@@ -0,0 +1,363 @@
/*
* 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.kotlin.idea.codeInsight.gradle
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemDescriptorBase
import com.intellij.openapi.util.Ref
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.InspectionTestUtil
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
import org.jetbrains.kotlin.idea.inspections.gradle.DeprecatedGradleDependencyInspection
import org.jetbrains.kotlin.idea.inspections.gradle.DifferentKotlinGradleVersionInspection
import org.jetbrains.kotlin.idea.inspections.gradle.DifferentStdlibGradleVersionInspection
import org.jetbrains.kotlin.idea.inspections.runInspection
import org.junit.Assert
import org.junit.Test
class GradleInspectionTest : GradleImportingTestCase() {
@Test
fun testDifferentStdlibGradleVersion() {
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.2")
}
}
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:1.0.3"
}
"""
)
importProject()
val tool = DifferentStdlibGradleVersionInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals("Plugin version (1.0.2) is not the same as library version (1.0.3)", problems.single())
}
@Test
fun testDifferentStdlibGradleVersionWithImplementation() {
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.2")
}
}
apply plugin: 'kotlin'
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.0.3"
}
"""
)
importProject()
val tool = DifferentStdlibGradleVersionInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals("Plugin version (1.0.2) is not the same as library version (1.0.3)", problems.single())
}
@Test
fun testDifferentStdlibJre7GradleVersion() {
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0-beta-17")
}
}
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.0-beta-22"
}
"""
)
importProject()
val tool = DifferentStdlibGradleVersionInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals("Plugin version (1.1.0-beta-17) is not the same as library version (1.1.0-beta-22)", problems.single())
}
@Test
fun testDifferentStdlibJdk7GradleVersion() {
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0-beta-17")
}
}
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.1.0-beta-22"
}
"""
)
importProject()
val tool = DifferentStdlibGradleVersionInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals("Plugin version (1.1.0-beta-17) is not the same as library version (1.1.0-beta-22)", problems.single())
}
@Test
fun testDifferentStdlibGradleVersionWithVariables() {
createProjectSubFile(
"gradle.properties", """
|kotlin=1.0.1
|lib_version=1.0.3""".trimMargin()
)
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}kotlin")
}
}
apply plugin: 'kotlin'
dependencies {
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: lib_version
}
"""
)
importProject()
val tool = DifferentStdlibGradleVersionInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals("Plugin version (1.0.1) is not the same as library version (1.0.3)", problems.single())
}
@Test
fun testDifferentKotlinGradleVersion() {
createProjectSubFile("gradle.properties", """test=1.0.1""")
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}{test}")
}
}
apply plugin: 'kotlin'
"""
)
importProject()
val tool = DifferentKotlinGradleVersionInspection()
tool.testVersionMessage = "\$PLUGIN_VERSION"
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals(
"Kotlin version that is used for building with Gradle (1.0.1) differs from the one bundled into the IDE plugin (\$PLUGIN_VERSION)",
problems.single()
)
}
@Test
fun testJreInOldVersion() {
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60")
}
}
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:1.1.60"
}
"""
)
importProject()
val tool = DeprecatedGradleDependencyInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.isEmpty())
}
@Test
fun testJreIsDeprecated() {
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60")
}
}
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0"
}
"""
)
importProject()
val tool = DeprecatedGradleDependencyInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals(
"kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7",
problems.single()
)
}
@Test
fun testJreIsDeprecatedWithImplementation() {
val localFile = createProjectSubFile(
"build.gradle", """
group 'Again'
version '1.0-SNAPSHOT'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0")
}
}
apply plugin: 'kotlin'
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0"
}
"""
)
importProject()
val tool = DeprecatedGradleDependencyInspection()
val problems = getInspectionResult(tool, localFile)
Assert.assertTrue(problems.size == 1)
Assert.assertEquals(
"kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7",
problems.single()
)
}
fun getInspectionResult(tool: LocalInspectionTool, file: VirtualFile): List<String> {
val resultRef = Ref<List<String>>()
invokeTestRunnable {
val presentation = runInspection(tool, myProject, listOf(file))
val foundProblems = presentation.problemElements
.values
.mapNotNull { it as? ProblemDescriptorBase }
.map { it.descriptionTemplate }
resultRef.set(foundProblems)
}
return resultRef.get()
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2015 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.kotlin.idea.framework;
import com.intellij.framework.library.LibraryVersionProperties;
import com.intellij.openapi.roots.libraries.LibraryPresentationProvider;
import com.intellij.openapi.roots.libraries.LibraryProperties;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.KotlinIcons;
import javax.swing.*;
import java.util.List;
public class JavaRuntimePresentationProvider extends LibraryPresentationProvider<LibraryVersionProperties> {
public static JavaRuntimePresentationProvider getInstance() {
return LibraryPresentationProvider.EP_NAME.findExtension(JavaRuntimePresentationProvider.class);
}
protected JavaRuntimePresentationProvider() {
super(JavaRuntimeLibraryDescription.Companion.getKOTLIN_JAVA_RUNTIME_KIND());
}
@Nullable
@Override
public Icon getIcon(@Nullable LibraryProperties properties) {
return KotlinIcons.SMALL_LOGO;
}
@Nullable
@Override
public LibraryVersionProperties detect(@NotNull List<VirtualFile> classesRoots) {
String version = JavaRuntimeDetectionUtil.getJavaRuntimeVersion(classesRoots);
return version == null ? null : new LibraryVersionProperties(version);
}
}
@@ -0,0 +1,82 @@
/*
* Copyright 2010-2015 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.kotlin.idea.reporter
import com.intellij.diagnostic.ITNReporter
import com.intellij.openapi.diagnostic.IdeaLoggingEvent
import com.intellij.openapi.diagnostic.SubmittedReportInfo
import com.intellij.openapi.ui.Messages
import com.intellij.util.Consumer
import org.jetbrains.kotlin.idea.KotlinPluginUpdater
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.PluginUpdateStatus
import org.jetbrains.kotlin.idea.actions.internal.KotlinInternalMode
import java.awt.Component
/**
* We need to wrap ITNReporter for force showing or errors from kotlin plugin even from released version of IDEA.
*/
class KotlinReportSubmitter : ITNReporter() {
private var hasUpdate = false
private var hasLatestVersion = false
override fun showErrorInRelease(event: IdeaLoggingEvent): Boolean {
val notificationEnabled = "disabled" != System.getProperty("kotlin.fatal.error.notification", "enabled")
return notificationEnabled && (!hasUpdate || KotlinInternalMode.enabled)
}
override fun submit(events: Array<IdeaLoggingEvent>, additionalInfo: String?, parentComponent: Component?, consumer: Consumer<SubmittedReportInfo>): Boolean {
if (hasUpdate) {
if (KotlinInternalMode.enabled) {
return super.submit(events, additionalInfo, parentComponent, consumer)
}
return true
}
if (hasLatestVersion) {
return super.submit(events, additionalInfo, parentComponent, consumer)
}
KotlinPluginUpdater.getInstance().runUpdateCheck { status ->
if (status is PluginUpdateStatus.Update) {
hasUpdate = true
if (parentComponent != null) {
if (KotlinInternalMode.enabled) {
super.submit(events, additionalInfo, parentComponent, consumer)
}
val rc = Messages.showDialog(parentComponent,
"You're running Kotlin plugin version ${KotlinPluginUtil.getPluginVersion()}, " +
"while the latest version is ${status.pluginDescriptor.version}",
"Update Kotlin Plugin",
arrayOf("Update", "Ignore"),
0, Messages.getInformationIcon())
if (rc == 0) {
KotlinPluginUpdater.getInstance().installPluginUpdate(status)
}
}
}
else {
hasLatestVersion = true
super.submit(events, additionalInfo, parentComponent, consumer)
}
false
}
return true
}
}
@@ -0,0 +1,863 @@
package org.jetbrains.android.inspections.klint;
import com.android.annotations.NonNull;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.LintOptions;
import com.android.ide.common.repository.ResourceVisibilityLookup;
import com.android.ide.common.res2.AbstractResourceRepository;
import com.android.ide.common.res2.ResourceFile;
import com.android.ide.common.res2.ResourceItem;
import com.android.sdklib.repository.AndroidSdkHandler;
import com.android.tools.idea.project.AndroidProjectInfo;
import com.android.tools.idea.project.AndroidProjectInfo;
import com.android.tools.idea.res.AppResourceRepository;
import com.android.tools.idea.res.LocalResourceRepository;
import com.android.tools.idea.res.ModuleResourceRepository;
import com.android.tools.idea.res.ProjectResourceRepository;
import com.android.tools.idea.sdk.IdeSdks;
import com.android.tools.klint.checks.ApiLookup;
import com.android.tools.klint.client.api.*;
import com.android.tools.klint.detector.api.*;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.intellij.analysis.AnalysisScope;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.lang.UrlClassLoader;
import com.intellij.util.net.HttpConfigurable;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.sdk.AndroidSdkData;
import org.jetbrains.android.sdk.AndroidSdkType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import static com.android.tools.klint.detector.api.TextFormat.RAW;
import static org.jetbrains.android.inspections.klint.IntellijLintIssueRegistry.CUSTOM_ERROR;
import static org.jetbrains.android.inspections.klint.IntellijLintIssueRegistry.CUSTOM_WARNING;
/**
* Implementation of the {@linkplain LintClient} API for executing lint within the IDE:
* reading files, reporting issues, logging errors, etc.
*/
public class IntellijLintClient extends LintClient implements Disposable {
protected static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.IntellijLintClient");
@NonNull protected Project myProject;
@Nullable protected Map<com.android.tools.klint.detector.api.Project, Module> myModuleMap;
public IntellijLintClient(@NonNull Project project) {
super(CLIENT_STUDIO);
myProject = project;
}
/** Creates a lint client for batch inspections */
public static IntellijLintClient forBatch(@NotNull Project project,
@NotNull Map<Issue, Map<File, List<ProblemData>>> problemMap,
@NotNull AnalysisScope scope,
@NotNull List<Issue> issues) {
return new BatchLintClient(project, problemMap, scope, issues);
}
/**
* Returns an {@link ApiLookup} service.
*
* @param project the project to use for locating the Android SDK
* @return an API lookup if one can be found
*/
@Nullable
public static ApiLookup getApiLookup(@NotNull Project project) {
return ApiLookup.get(new IntellijLintClient(project));
}
/**
* Creates a lint client used for in-editor single file lint analysis (e.g. background checking while user is editing.)
*/
public static IntellijLintClient forEditor(@NotNull State state) {
return new EditorLintClient(state);
}
@Nullable
protected Module findModuleForLintProject(@NotNull Project project,
@NotNull com.android.tools.klint.detector.api.Project lintProject) {
if (myModuleMap != null) {
Module module = myModuleMap.get(lintProject);
if (module != null) {
return module;
}
}
final File dir = lintProject.getDir();
final VirtualFile vDir = LocalFileSystem.getInstance().findFileByIoFile(dir);
return vDir != null ? ModuleUtilCore.findModuleForFile(vDir, project) : null;
}
void setModuleMap(@Nullable Map<com.android.tools.klint.detector.api.Project, Module> moduleMap) {
myModuleMap = moduleMap;
}
@NonNull
@Override
public Configuration getConfiguration(@NonNull com.android.tools.klint.detector.api.Project project, @Nullable final LintDriver driver) {
if (project.isGradleProject() && project.isAndroidProject() && !project.isLibrary()) {
AndroidProject model = project.getGradleProjectModel();
if (model != null) {
try {
LintOptions lintOptions = model.getLintOptions();
final Map<String, Integer> overrides = lintOptions.getSeverityOverrides();
if (overrides != null && !overrides.isEmpty()) {
return new DefaultConfiguration(this, project, null) {
@NonNull
@Override
public Severity getSeverity(@NonNull Issue issue) {
Integer severity = overrides.get(issue.getId());
if (severity != null) {
switch (severity.intValue()) {
case LintOptions.SEVERITY_FATAL:
return Severity.FATAL;
case LintOptions.SEVERITY_ERROR:
return Severity.ERROR;
case LintOptions.SEVERITY_WARNING:
return Severity.WARNING;
case LintOptions.SEVERITY_INFORMATIONAL:
return Severity.INFORMATIONAL;
case LintOptions.SEVERITY_IGNORE:
default:
return Severity.IGNORE;
}
}
// This is a LIST lookup. I should make this faster!
if (!getIssues().contains(issue) && (driver == null || !driver.isCustomIssue(issue))) {
return Severity.IGNORE;
}
return super.getSeverity(issue);
}
};
}
} catch (Exception e) {
LOG.error(e);
}
}
}
return new DefaultConfiguration(this, project, null) {
@Override
public boolean isEnabled(@NonNull Issue issue) {
if (getIssues().contains(issue) && super.isEnabled(issue)) {
return true;
}
return driver != null && driver.isCustomIssue(issue);
}
};
}
@Override
public void report(@NonNull Context context,
@NonNull Issue issue,
@NonNull Severity severity,
@NonNull Location location,
@NonNull String message,
@NonNull TextFormat format) {
assert false : message;
}
@NonNull protected List<Issue> getIssues() {
return Collections.emptyList();
}
@Nullable
protected Module getModule() {
return null;
}
/**
* Recursively calls {@link #report} on the secondary location of this error, if any, which in turn may call it on a third
* linked location, and so on.This is necessary since IntelliJ problems don't have secondary locations; instead, we create one
* problem for each location associated with the lint error.
*/
protected void reportSecondary(@NonNull Context context, @NonNull Issue issue, @NonNull Severity severity, @NonNull Location location,
@NonNull String message, @NonNull TextFormat format) {
Location secondary = location.getSecondary();
if (secondary != null) {
if (secondary.getMessage() != null) {
message = message + " (" + secondary.getMessage() + ")";
}
report(context, issue, severity, secondary, message, format);
}
}
@Override
public void log(@NonNull Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) {
if (severity == Severity.ERROR || severity == Severity.FATAL) {
if (format != null) {
LOG.error(String.format(format, args), exception);
} else if (exception != null) {
LOG.error(exception);
}
} else if (severity == Severity.WARNING) {
if (format != null) {
LOG.warn(String.format(format, args), exception);
} else if (exception != null) {
LOG.warn(exception);
}
} else {
if (format != null) {
LOG.info(String.format(format, args), exception);
} else if (exception != null) {
LOG.info(exception);
}
}
}
@Override
public XmlParser getXmlParser() {
return new DomPsiParser(this);
}
@Nullable
@Override
public JavaParser getJavaParser(@Nullable com.android.tools.klint.detector.api.Project project) {
return new IdeaJavaParser(this, myProject);
}
@NonNull
@Override
public List<File> getJavaClassFolders(@NonNull com.android.tools.klint.detector.api.Project project) {
// todo: implement when class files checking detectors will be available
return Collections.emptyList();
}
@NonNull
@Override
public List<File> getJavaLibraries(@NonNull com.android.tools.klint.detector.api.Project project, boolean includeProvided) {
// todo: implement
return Collections.emptyList();
}
@Override
@NonNull
public String readFile(@NonNull final File file) {
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
if (vFile == null) {
LOG.debug("Cannot find file " + file.getPath() + " in the VFS");
return "";
}
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Nullable
@Override
public String compute() {
final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
if (psiFile == null) {
LOG.info("Cannot find file " + file.getPath() + " in the PSI");
return null;
}
else {
return psiFile.getText();
}
}
});
}
@Override
public void dispose() {
}
@Nullable
@Override
public File getSdkHome() {
Module module = getModule();
if (module != null) {
Sdk moduleSdk = ModuleRootManager.getInstance(module).getSdk();
if (moduleSdk != null && moduleSdk.getSdkType() instanceof AndroidSdkType) {
String path = moduleSdk.getHomePath();
if (path != null) {
File home = new File(path);
if (home.exists()) {
return home;
}
}
}
}
File sdkHome = super.getSdkHome();
if (sdkHome != null) {
return sdkHome;
}
for (Module m : ModuleManager.getInstance(myProject).getModules()) {
Sdk moduleSdk = ModuleRootManager.getInstance(m).getSdk();
if (moduleSdk != null) {
if (moduleSdk.getSdkType() instanceof AndroidSdkType) {
String path = moduleSdk.getHomePath();
if (path != null) {
File home = new File(path);
if (home.exists()) {
return home;
}
}
}
}
}
return IdeSdks.getInstance().getAndroidSdkPath();
}
@Nullable
@Override
public AndroidSdkHandler getSdk() {
if (mSdk == null) {
Module module = getModule();
AndroidSdkHandler sdk = getLocalSdk(module);
if (sdk != null) {
mSdk = sdk;
} else {
for (Module m : ModuleManager.getInstance(myProject).getModules()) {
sdk = getLocalSdk(m);
if (sdk != null) {
mSdk = sdk;
break;
}
}
if (mSdk == null) {
mSdk = super.getSdk();
}
}
}
return mSdk;
}
@Nullable
private static AndroidSdkHandler getLocalSdk(@Nullable Module module) {
if (module != null) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
AndroidSdkData sdkData = AndroidSdkData.getSdkData(facet); // AS24 AndroidSdkData.getSdkData()
if (sdkData != null) {
return sdkData.getSdkHandler();
}
}
}
return null;
}
@Override
public boolean isGradleProject(com.android.tools.klint.detector.api.Project project) {
Module module = getModule();
if (module != null) {
AndroidFacet facet = AndroidFacet.getInstance(module);
return facet != null && facet.requiresAndroidModel();
}
return AndroidProjectInfo.getInstance(this.myProject).requiresAndroidModel();
}
// Overridden such that lint doesn't complain about missing a bin dir property in the event
// that no SDK is configured
@Override
@Nullable
public File findResource(@NonNull String relativePath) {
File top = getSdkHome();
if (top != null) {
File file = new File(top, relativePath);
if (file.exists()) {
return file;
}
}
return null;
}
@Nullable private static volatile String ourSystemPath;
@Override
@Nullable
public File getCacheDir(boolean create) {
final String path = ourSystemPath != null ? ourSystemPath : (ourSystemPath = PathUtil.getCanonicalPath(PathManager.getSystemPath()));
File lint = new File(path, "lint");
if (create && !lint.exists()) {
lint.mkdirs();
}
return lint;
}
@Override
public boolean isProjectDirectory(@NonNull File dir) {
return new File(dir, Project.DIRECTORY_STORE_FOLDER).exists();
}
private static List<Issue> ourReportedCustomIssues;
private static void recordCustomIssue(@NonNull Issue issue) {
if (ourReportedCustomIssues == null) {
ourReportedCustomIssues = Lists.newArrayList();
} else if (ourReportedCustomIssues.contains(issue)) {
return;
}
ourReportedCustomIssues.add(issue);
}
@Nullable
public static Issue findCustomIssue(@NonNull String errorMessage) {
if (ourReportedCustomIssues != null) {
// We stash the original id into the error message such that we can
// find it later
int begin = errorMessage.lastIndexOf('[');
int end = errorMessage.lastIndexOf(']');
if (begin < end && begin != -1) {
String id = errorMessage.substring(begin + 1, end);
for (Issue issue : ourReportedCustomIssues) {
if (id.equals(issue.getId())) {
return issue;
}
}
}
}
return null;
}
/**
* A lint client used for in-editor single file lint analysis (e.g. background checking while user is editing.)
* <p>
* Since this applies only to a given file and module, it can take some shortcuts over what the general
* {@link BatchLintClient} has to do.
* */
private static class EditorLintClient extends IntellijLintClient {
private final State myState;
public EditorLintClient(@NotNull State state) {
super(state.getModule().getProject());
myState = state;
}
@Nullable
@Override
protected Module getModule() {
return myState.getModule();
}
@NonNull
@Override
protected List<Issue> getIssues() {
return myState.getIssues();
}
@Override
public void report(@NonNull Context context,
@NonNull Issue issue,
@NonNull Severity severity,
@NonNull Location location,
@NonNull String message,
@NonNull TextFormat format) {
if (location != null) {
final File file = location.getFile();
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
if (context.getDriver().isCustomIssue(issue)) {
// Record original issue id in the message (such that we can find
// it later, in #findCustomIssue)
message += " [" + issue.getId() + "]";
recordCustomIssue(issue);
issue = Severity.WARNING.compareTo(severity) <= 0 ? CUSTOM_WARNING : CUSTOM_ERROR;
}
if (myState.getMainFile().equals(vFile)) {
final Position start = location.getStart();
final Position end = location.getEnd();
final TextRange textRange = start != null && end != null && start.getOffset() <= end.getOffset()
? new TextRange(start.getOffset(), end.getOffset())
: TextRange.EMPTY_RANGE;
Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null;
message = format.convertTo(message, RAW);
myState.getProblems().add(new ProblemData(issue, message, textRange, configuredSeverity));
}
Location secondary = location.getSecondary();
if (secondary != null && myState.getMainFile().equals(LocalFileSystem.getInstance().findFileByIoFile(secondary.getFile()))) {
reportSecondary(context, issue, severity, location, message, format);
}
}
}
@Override
@NotNull
public String readFile(@NonNull File file) {
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
if (vFile == null) {
try {
return Files.toString(file, Charsets.UTF_8);
} catch (IOException ioe) {
LOG.debug("Cannot find file " + file.getPath() + " in the VFS");
return "";
}
}
final String content = getFileContent(vFile);
if (content == null) {
LOG.info("Cannot find file " + file.getPath() + " in the PSI");
return "";
}
return content;
}
@Nullable
private String getFileContent(final VirtualFile vFile) {
if (Comparing.equal(myState.getMainFile(), vFile)) {
return myState.getMainFileContent();
}
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Nullable
@Override
public String compute() {
final Module module = myState.getModule();
final Project project = module.getProject();
if (project.isDisposed()) {
return null;
}
final PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile);
if (psiFile == null) {
return null;
}
final Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile);
if (document != null) {
final DocumentListener listener = new DocumentListener() {
@Override
public void beforeDocumentChange(DocumentEvent event) {
}
@Override
public void documentChanged(DocumentEvent event) {
myState.markDirty();
}
};
document.addDocumentListener(listener, EditorLintClient.this);
}
return psiFile.getText();
}
});
}
@NonNull
@Override
public List<File> getJavaSourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) {
final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(myState.getModule()).getSourceRoots(false);
final List<File> result = new ArrayList<File>(sourceRoots.length);
for (VirtualFile root : sourceRoots) {
result.add(new File(root.getPath()));
}
return result;
}
@NonNull
@Override
public List<File> getResourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) {
AndroidFacet facet = AndroidFacet.getInstance(myState.getModule());
if (facet != null) {
return IntellijLintUtils.getResourceDirectories(facet);
}
return super.getResourceFolders(project);
}
}
/** Lint client used for batch operations */
private static class BatchLintClient extends IntellijLintClient {
private final Map<Issue, Map<File, List<ProblemData>>> myProblemMap;
private final AnalysisScope myScope;
private final List<Issue> myIssues;
public BatchLintClient(@NotNull Project project,
@NotNull Map<Issue, Map<File, List<ProblemData>>> problemMap,
@NotNull AnalysisScope scope,
@NotNull List<Issue> issues) {
super(project);
myProblemMap = problemMap;
myScope = scope;
myIssues = issues;
}
@Nullable
@Override
protected Module getModule() {
// No default module
return null;
}
@NonNull
@Override
protected List<Issue> getIssues() {
return myIssues;
}
@Override
public void report(@NonNull Context context,
@NonNull Issue issue,
@NonNull Severity severity,
@NonNull Location location,
@NonNull String message,
@NonNull TextFormat format) {
VirtualFile vFile = null;
File file = null;
if (location != null) {
file = location.getFile();
vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
}
else if (context.getProject() != null) {
final Module module = findModuleForLintProject(myProject, context.getProject());
if (module != null) {
final AndroidFacet facet = AndroidFacet.getInstance(module);
vFile = facet != null ? AndroidRootUtil.getPrimaryManifestFile(facet) : null;
if (vFile != null) {
file = new File(vFile.getPath());
}
}
}
boolean inScope = vFile != null && myScope.contains(vFile);
// In analysis batch mode, the AnalysisScope contains a specific set of virtual
// files, not directories, so any errors reported against a directory will not
// be considered part of the scope and therefore won't be reported. Correct
// for this.
if (!inScope && vFile != null && vFile.isDirectory()) {
if (myScope.getScopeType() == AnalysisScope.PROJECT) {
inScope = true;
} else if (myScope.getScopeType() == AnalysisScope.MODULE ||
myScope.getScopeType() == AnalysisScope.MODULES) {
final Module module = findModuleForLintProject(myProject, context.getProject());
if (module != null && myScope.containsModule(module)) {
inScope = true;
}
}
}
if (inScope) {
if (context.getDriver().isCustomIssue(issue)) {
// Record original issue id in the message (such that we can find
// it later, in #findCustomIssue)
message += " [" + issue.getId() + "]";
recordCustomIssue(issue);
issue = Severity.WARNING.compareTo(severity) <= 0 ? CUSTOM_WARNING : CUSTOM_ERROR;
}
file = new File(PathUtil.getCanonicalPath(file.getPath()));
Map<File, List<ProblemData>> file2ProblemList = myProblemMap.get(issue);
if (file2ProblemList == null) {
file2ProblemList = new HashMap<File, List<ProblemData>>();
myProblemMap.put(issue, file2ProblemList);
}
List<ProblemData> problemList = file2ProblemList.get(file);
if (problemList == null) {
problemList = new ArrayList<ProblemData>();
file2ProblemList.put(file, problemList);
}
TextRange textRange = TextRange.EMPTY_RANGE;
if (location != null) {
final Position start = location.getStart();
final Position end = location.getEnd();
if (start != null && end != null && start.getOffset() <= end.getOffset()) {
textRange = new TextRange(start.getOffset(), end.getOffset());
}
}
Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null;
message = format.convertTo(message, RAW);
problemList.add(new ProblemData(issue, message, textRange, configuredSeverity));
if (location != null && location.getSecondary() != null) {
reportSecondary(context, issue, severity, location, message, format);
}
}
}
@NonNull
@Override
public List<File> getJavaSourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) {
final Module module = findModuleForLintProject(myProject, project);
if (module == null) {
return Collections.emptyList();
}
final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(false);
final List<File> result = new ArrayList<File>(sourceRoots.length);
for (VirtualFile root : sourceRoots) {
result.add(new File(root.getPath()));
}
return result;
}
@NonNull
@Override
public List<File> getResourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) {
final Module module = findModuleForLintProject(myProject, project);
if (module != null) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
return IntellijLintUtils.getResourceDirectories(facet);
}
}
return super.getResourceFolders(project);
}
}
@Override
public boolean checkForSuppressComments() {
return false;
}
@Override
public boolean supportsProjectResources() {
return true;
}
@Nullable
@Override
public AbstractResourceRepository getProjectResources(com.android.tools.klint.detector.api.Project project, boolean includeDependencies) {
final Module module = findModuleForLintProject(myProject, project);
if (module != null) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
return includeDependencies ? facet.getProjectResources(true) : facet.getModuleResources(true);
}
}
return null;
}
@Nullable
@Override
public URLConnection openConnection(@NonNull URL url) throws IOException {
return HttpConfigurable.getInstance().openConnection(url.toExternalForm());
}
@Override
public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) {
return UrlClassLoader.build().parent(parent).urls(urls).get();
}
@NonNull
@Override
public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
XmlTag tag = LocalResourceRepository.getItemTag(myProject, item);
if (tag != null) {
ResourceFile source = item.getSource();
assert source != null : item;
return new LocationHandle(source.getFile(), tag);
}
return super.createResourceItemHandle(item);
}
@NonNull
@Override
public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() {
Module module = getModule();
if (module != null) {
AppResourceRepository appResources = AppResourceRepository.getAppResources(module, true);
if (appResources != null) {
ResourceVisibilityLookup.Provider provider = appResources.getResourceVisibilityProvider();
if (provider != null) {
return provider;
}
}
}
return super.getResourceVisibilityProvider();
}
private static class LocationHandle implements Location.Handle, Computable<Location> {
private final File myFile;
private final XmlElement myNode;
private Object myClientData;
public LocationHandle(File file, XmlElement node) {
myFile = file;
myNode = node;
}
@NonNull
@Override
public Location resolve() {
if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
return ApplicationManager.getApplication().runReadAction(this);
}
TextRange textRange = myNode.getTextRange();
// For elements, don't highlight the entire element range; instead, just
// highlight the element name
if (myNode instanceof XmlTag) {
String tag = ((XmlTag)myNode).getName();
int index = myNode.getText().indexOf(tag);
if (index != -1) {
int start = textRange.getStartOffset() + index;
textRange = new TextRange(start, start + tag.length());
}
}
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(myFile, start, end);
}
@Override
public Location compute() {
return resolve();
}
@Override
public void setClientData(@Nullable Object clientData) {
myClientData = clientData;
}
@Override
@Nullable
public Object getClientData() {
return myClientData;
}
}
}