diff --git a/plugins/android/resources/messages/AndroidBundle.properties b/plugins/android/resources/messages/AndroidBundle.properties index feb398e022a1..4ed608cd7172 100644 --- a/plugins/android/resources/messages/AndroidBundle.properties +++ b/plugins/android/resources/messages/AndroidBundle.properties @@ -1,6 +1,6 @@ command.0.execution.failed.with.exit.code.1=Command \"{0}\" execution failed with exit code {1} aidl.filetype.description=Android IDL files -add.string.resource.intention.text=Add a string resource +add.string.resource.intention.text=Extract string resource resource.name=Resource name intention.family=Android package.not.found.error=Package is not specified in the manifest file @@ -8,8 +8,8 @@ cannot.resolve.flag.error=Cannot resolve flag cannot.resolve.format.error=Cannot resolve format create.resource.quickfix.name=Create resource {0} in {1} quick.fixes.family=Android Quick Fixes -not.resource.file.error="File {0} is not resource file" -check.resource.dir.error=Check if resource directory exists +not.resource.file.error=File {0} is not resource file +check.resource.dir.error=Cannot find resource directory for module {0} choose.device.dialog.title=Choose Device replace.avd.question=AVD {0} already exists. Replace? create.avd.dialog.title=Create Android Virtual Device @@ -147,6 +147,7 @@ create.run.configuration.title=Create Android Run Configuration file.must.be.under.module.error=Cannot compute relative path for {0} android.extract.package.choose.dest.apk=Choose destination APK file android.cannot.create.file.error=Cannot create file {0} +android.cannot.create.dir.error=Cannot create directory {0} android.manifest.not.found.error=AndroidManifest.xml doesn't exist or has incorrect root tag android.apt.settings.title=AAPT Compiler android.aidl.settings.title=AIDL Compiler @@ -305,4 +306,20 @@ android.facet.settings.custom.debug.keystore.label=C&ustom debug keystore\: android.export.package.run.proguard.label=Run &ProGuard android.export.package.proguad.config.label=C&onfig file path\: android.extract.package.specify.proguard.cfg.path.error=Please specify ProGuard config file path -android.inspections.lint.name=Android Lint Validation \ No newline at end of file +android.inspections.lint.name=Android Lint Validation +android.lint.quickfixes.family=Android Lint Quick Fixes +android.lint.inspections.add.content.description=Add 'contentDescription' attribute +android.lint.inspections.scroll.view.children=ScrollView element can have only one child +android.lint.inspections.deprecated=Deprecated XML elements +android.lint.inspections.missing.prefix=Missing android XML namespace +android.lint.inspections.adapter.view.children=AdapterView element cannot have children +android.lint.inspections.add.android.prefix=Add android prefix +android.lint.inspections.replace.with.zero.dp=Replace size attribute with 0dp +android.lint.inspections.set.baseline.attribute=Set 'baselineAligned' attribute +android.lint.inspections.remove.attribute=Remove attribute +android.lint.inspections.convert.to.dp=Convert to \\"dp\\"... +android.lint.inspections.set.to.wrap.content=Replace size attribute with 'wrap_content' +android.lint.inspections.add.permission.attribute=Add 'permission' attribute +android.lint.inspections.add.input.type.attribute=Add 'inputType' attribute +android.lint.inspections.remove.unnecessary.view=Remove unnecessary view +android.lint.inspections.replace.with.suggested.characters=Replace with suggested characters \ No newline at end of file diff --git a/plugins/android/src/org/jetbrains/android/actions/CreateResourceDialog.form b/plugins/android/src/org/jetbrains/android/actions/CreateResourceDialog.form index efaa03fc30f8..560b404bdd2d 100644 --- a/plugins/android/src/org/jetbrains/android/actions/CreateResourceDialog.form +++ b/plugins/android/src/org/jetbrains/android/actions/CreateResourceDialog.form @@ -3,7 +3,7 @@ - + diff --git a/plugins/android/src/org/jetbrains/android/actions/CreateXmlResourceDialog.form b/plugins/android/src/org/jetbrains/android/actions/CreateXmlResourceDialog.form new file mode 100644 index 000000000000..e259ae3a5dad --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/actions/CreateXmlResourceDialog.form @@ -0,0 +1,93 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/android/src/org/jetbrains/android/actions/CreateXmlResourceDialog.java b/plugins/android/src/org/jetbrains/android/actions/CreateXmlResourceDialog.java new file mode 100644 index 000000000000..dae344b92ced --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/actions/CreateXmlResourceDialog.java @@ -0,0 +1,264 @@ +package org.jetbrains.android.actions; + +import com.android.AndroidConstants; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.resources.ResourceFolderType; +import com.android.resources.ResourceType; +import com.intellij.CommonBundle; +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.components.JBLabel; +import com.intellij.util.containers.HashSet; +import org.jetbrains.android.dom.resources.ResourceElement; +import org.jetbrains.android.dom.resources.Resources; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.facet.AndroidRootUtil; +import org.jetbrains.android.resourceManagers.ResourceManager; +import org.jetbrains.android.uipreview.DeviceConfiguratorPanel; +import org.jetbrains.android.uipreview.InvalidOptionValueException; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.android.util.AndroidResourceUtil; +import org.jetbrains.android.util.AndroidUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Set; + +/** + * @author Eugene.Kudelevsky + */ +public class CreateXmlResourceDialog extends DialogWrapper { + private JPanel myPanel; + private JPanel myDeviceConfigurationWrapper; + private JTextField myNameField; + private JComboBox myModuleCombo; + private JBLabel myModuleLabel; + private JTextField myDirectoryNameField; + private JBLabel myErrorLabel; + private JTextField myFileNameField; + + private final DeviceConfiguratorPanel myDeviceConfiguratorPanel; + private final Module myModule; + private final ResourceType myResourceType; + + public CreateXmlResourceDialog(@NotNull Module module, @NotNull ResourceType resourceType) { + super(module.getProject()); + + myResourceType = resourceType; + + final Set modulesSet = new HashSet(); + modulesSet.add(module); + + for (AndroidFacet depFacet : AndroidUtils.getAllAndroidDependencies(module, true)) { + modulesSet.add(depFacet.getModule()); + } + + assert modulesSet.size() > 0; + + if (modulesSet.size() == 1) { + myModule = module; + myModuleLabel.setVisible(false); + myModuleCombo.setVisible(false); + } + else { + myModule = null; + + final Module[] modules = modulesSet.toArray(new Module[modulesSet.size()]); + Arrays.sort(modules, new Comparator() { + @Override + public int compare(Module m1, Module m2) { + return m1.getName().compareTo(m2.getName()); + } + }); + + myModuleCombo.setModel(new DefaultComboBoxModel(modules)); + myModuleCombo.setSelectedItem(module); + + myModuleCombo.setRenderer(new ListCellRendererWrapper(myModuleCombo.getRenderer()) { + @Override + public void customize(JList list, Module module, int index, boolean selected, boolean hasFocus) { + setText(module.getName()); + setIcon(ModuleType.get(module).getNodeIcon(false)); + } + }); + } + + myDeviceConfiguratorPanel = new DeviceConfiguratorPanel(null) { + @Override + public void applyEditors() { + try { + doApplyEditors(); + + final FolderConfiguration config = myDeviceConfiguratorPanel.getConfiguration(); + myErrorLabel.setText(""); + myDirectoryNameField.setText(config.getFolderName(ResourceFolderType.VALUES)); + } + catch (InvalidOptionValueException e) { + myErrorLabel.setText("" + e.getMessage() + ""); + myDirectoryNameField.setText(AndroidConstants.FD_RES_VALUES); + } + } + }; + myDeviceConfigurationWrapper.add(myDeviceConfiguratorPanel, BorderLayout.CENTER); + + final String defaultResFileName = ResourceManager.getDefaultResourceFileName(resourceType.getName()); + if (defaultResFileName != null) { + myFileNameField.setText(defaultResFileName); + } + myDirectoryNameField.setText(AndroidConstants.FD_RES_VALUES); + myDeviceConfiguratorPanel.updateAll(); + + init(); + } + + @Override + protected ValidationInfo doValidate() { + final String resourceName = getResourceName(); + final Module selectedModule = getModule(); + final String directoryName = getDirectoryName(); + final String fileName = getFileName(); + + if (resourceName.length() == 0) { + return new ValidationInfo("specify resource name", myNameField); + } + else if (!AndroidResourceUtil.isCorrectAndroidResourceName(resourceName)) { + return new ValidationInfo(resourceName + " is not correct resource name", myNameField); + } + else if (fileName.length() == 0) { + return new ValidationInfo("specify file name", myFileNameField); + } + else if (selectedModule == null) { + return new ValidationInfo("specify module", myModuleCombo); + } + else if (!ResourceFolderType.VALUES.getName().equals( + ResourceManager.getResourceTypeByDirName(directoryName))) { + return new ValidationInfo("directory name is not appropriate for value resources"); + } + + final ValidationInfo info = checkIfResourceAlreadyExists(selectedModule, resourceName, myResourceType, directoryName, fileName); + if (info != null) { + return info; + } + + try { + myDeviceConfiguratorPanel.doApplyEditors(); + } + catch (InvalidOptionValueException e) { + return new ValidationInfo("fix errors in configuration editor"); + } + + return null; + } + + @Nullable + private static ValidationInfo checkIfResourceAlreadyExists(@NotNull Module selectedModule, + @NotNull String resourceName, + @NotNull ResourceType resourceType, + @NotNull String directoryName, + @NotNull String fileName) { + if (resourceName.length() == 0 || + directoryName.length() == 0 || + fileName.length() == 0) { + return null; + } + + final VirtualFile resourceDir = AndroidRootUtil.getResourceDir(selectedModule); + if (resourceDir == null) { + return null; + } + + final VirtualFile resourceSubdir = resourceDir.findChild(directoryName); + if (resourceSubdir == null) { + return null; + } + + final VirtualFile resFile = resourceSubdir.findChild(fileName); + if (resFile == null) { + return null; + } + + if (resFile.getFileType() != StdFileTypes.XML) { + return new ValidationInfo("File " + FileUtil.toSystemDependentName(resFile.getPath()) + " is not XML file"); + } + + final Resources resources = AndroidUtils.loadDomElement(selectedModule, resFile, Resources.class); + if (resources == null) { + return new ValidationInfo(AndroidBundle.message("not.resource.file.error", FileUtil.toSystemDependentName(resFile.getPath()))); + } + + for (ResourceElement element : ResourceManager.getValueResources(resourceType.getName(), resources)) { + if (resourceName.equals(element.getName().getValue())) { + return new ValidationInfo("resource '" + resourceName + "' already exists in " + FileUtil.toSystemDependentName( + resFile.getPath())); + } + } + return null; + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myNameField; + } + + @Override + protected void doOKAction() { + final String resourceName = getResourceName(); + final String fileName = getFileName(); + final String dirName = getDirectoryName(); + final Module module = getModule(); + + if (resourceName.length() == 0) { + Messages.showErrorDialog(myPanel, "Resource name is not specified", CommonBundle.getErrorTitle()); + } + else if (!AndroidResourceUtil.isCorrectAndroidResourceName(resourceName)) { + Messages.showErrorDialog(myPanel, resourceName + " is not correct resource name", CommonBundle.getErrorTitle()); + } + else if (fileName.length() == 0) { + Messages.showErrorDialog(myPanel, "File name is not specified", CommonBundle.getErrorTitle()); + } + else if (dirName.length() == 0) { + Messages.showErrorDialog(myPanel, "Directory name is not specified", CommonBundle.getErrorTitle()); + } + else if (module == null) { + Messages.showErrorDialog(myPanel, "Module is not specified", CommonBundle.getErrorTitle()); + } + else { + super.doOKAction(); + } + } + + @NotNull + public String getResourceName() { + return myNameField.getText().trim(); + } + + @NotNull + public String getDirectoryName() { + return myDirectoryNameField.getText().trim(); + } + + @NotNull + public String getFileName() { + return myFileNameField.getText().trim(); + } + + @Nullable + public Module getModule() { + return myModule != null ? myModule : (Module)myModuleCombo.getSelectedItem(); + } + + @Override + protected JComponent createCenterPanel() { + return myPanel; + } +} diff --git a/plugins/android/src/org/jetbrains/android/dom/converters/ResourceReferenceConverter.java b/plugins/android/src/org/jetbrains/android/dom/converters/ResourceReferenceConverter.java index ad7fee9ff8a5..e77187a20cde 100644 --- a/plugins/android/src/org/jetbrains/android/dom/converters/ResourceReferenceConverter.java +++ b/plugins/android/src/org/jetbrains/android/dom/converters/ResourceReferenceConverter.java @@ -165,7 +165,12 @@ public class ResourceReferenceConverter extends ResolvingConverter getResourceTypes(ConvertContext context) { - ResourceType resourceType = context.getInvocationElement().getAnnotation(ResourceType.class); + return getResourceTypes(context.getInvocationElement()); + } + + @NotNull + public Set getResourceTypes(@NotNull DomElement element) { + ResourceType resourceType = element.getAnnotation(ResourceType.class); Set types = new HashSet(myResourceTypes); if (resourceType != null) { String s = resourceType.value(); diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AddMissingPrefixQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AddMissingPrefixQuickFix.java new file mode 100644 index 000000000000..67b4c390ad08 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AddMissingPrefixQuickFix.java @@ -0,0 +1,72 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.sdklib.SdkConstants; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +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.xml.XmlExtension; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; + +/** + * @author Eugene.Kudelevsky + */ +class AddMissingPrefixQuickFix implements AndroidLintQuickFix { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.lint.AddMissingPrefixQuickFix"); + + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + final XmlAttribute attribute = PsiTreeUtil.getParentOfType(startElement, XmlAttribute.class, false); + if (attribute == null) { + return; + } + + final XmlTag tag = attribute.getParent(); + if (tag == null) { + LOG.debug("tag is null"); + return; + } + + String androidNsPrefix = tag.getPrefixByNamespace(SdkConstants.NS_RESOURCES); + + if (androidNsPrefix == null) { + final PsiFile file = tag.getContainingFile(); + final XmlExtension extension = XmlExtension.getExtension(file); + + if (extension == null) { + LOG.debug("Cannot get XmlExtension for file + " + file); + return; + } + + if (!(file instanceof XmlFile)) { + LOG.debug(file + " is not XmlFile"); + return; + } + + final XmlFile xmlFile = (XmlFile)file; + final String defaultPrefix = "android"; + extension.insertNamespaceDeclaration(xmlFile, null, Collections.singleton(SdkConstants.NS_RESOURCES), defaultPrefix, null); + androidNsPrefix = defaultPrefix; + } + attribute.setName(androidNsPrefix + ':' + attribute.getLocalName()); + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + return PsiTreeUtil.getParentOfType(startElement, XmlAttribute.class, false) != null; + } + + @NotNull + @Override + public String getName() { + return AndroidBundle.message("android.lint.inspections.add.android.prefix"); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidAddStringResourceQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidAddStringResourceQuickFix.java new file mode 100644 index 000000000000..0a614f88a0d4 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidAddStringResourceQuickFix.java @@ -0,0 +1,47 @@ +package org.jetbrains.android.inspections.lint; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.android.intentions.AndroidAddStringResourceAction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidAddStringResourceQuickFix extends AndroidAddStringResourceAction { + private final PsiElement myStartElement; + + public AndroidAddStringResourceQuickFix(@NotNull PsiElement startElement) { + myStartElement = startElement; + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return getAttributeValue(myStartElement) != null; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + invokeIntention(project, editor, file, null); + } + + public void invokeIntention(Project project, Editor editor, PsiFile file, String resName) { + final XmlAttributeValue attributeValue = getAttributeValue(myStartElement); + if (attributeValue != null) { + doInvoke(project, editor, file, resName, attributeValue); + } + } + + @Nullable + private static XmlAttributeValue getAttributeValue(@NotNull PsiElement element) { + final XmlAttribute attribute = PsiTreeUtil.getParentOfType(element, XmlAttribute.class); + return attribute != null ? attribute.getValueElement() : null; + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintExternalAnnotator.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintExternalAnnotator.java index 8e49234ba547..b2a909b18df7 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintExternalAnnotator.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintExternalAnnotator.java @@ -7,11 +7,11 @@ import com.android.tools.lint.client.api.Lint; import com.android.tools.lint.detector.api.Issue; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.codeInsight.intention.HighPriorityAction; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.InspectionProfile; -import com.intellij.codeInspection.ex.CustomEditInspectionToolsSettingsAction; import com.intellij.codeInspection.ex.DisableInspectionToolAction; -import com.intellij.codeInspection.ex.InspectionToolWrapper; +import com.intellij.codeInspection.ex.EditInspectionToolsSettingsAction; import com.intellij.lang.annotation.Annotation; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.ExternalAnnotator; @@ -22,7 +22,10 @@ import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.PsiElement; @@ -30,6 +33,7 @@ import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidRootUtil; +import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -62,7 +66,7 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator pair = getHighlighLevelAndInspection(issue, file); + final Pair pair = AndroidLintUtil.getHighlighLevelAndInspection(issue, file); if (pair == null) { continue; } final AndroidLintInspectionBase inspection = pair.getFirst(); final HighlightDisplayLevel displayLevel = pair.getSecond(); - + final Annotation annotation = createAnnotation(holder, message, range, displayLevel); if (inspection != null) { final HighlightDisplayKey key = HighlightDisplayKey.find(inspection.getShortName()); - + if (key != null) { - annotation.registerFix(new MyDisableInspectionFix(key)); - annotation.registerFix(new CustomEditInspectionToolsSettingsAction(key, new Computable() { - @Override - public String compute() { - return "Edit '" + inspection.getDisplayName() + "' inspection settings"; + final PsiElement startElement = file.findElementAt(range.getStartOffset()); + final PsiElement endElement = file.findElementAt(range.getEndOffset() - 1); + + if (startElement != null && endElement != null) { + for (AndroidLintQuickFix fix : inspection.getQuickFixes(message)) { + if (fix.isApplicable(startElement, endElement, false)) { + annotation.registerFix(new MyFixingIntention(fix, startElement, endElement)); + } } - })); + + for (IntentionAction intention : inspection.getIntentions(startElement, endElement)) { + annotation.registerFix(intention); + } + } + + annotation.registerFix(new MyDisableInspectionFix(key)); + annotation.registerFix(new MyEditInspectionToolsSettingsAction(key, inspection)); } } } @@ -189,36 +203,6 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator getHighlighLevelAndInspection(@NotNull Issue issue, - @NotNull PsiElement context) { - final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(issue); - if (inspectionShortName == null) { - return null; - } - - final HighlightDisplayKey key = HighlightDisplayKey.find(inspectionShortName); - if (key == null) { - return null; - } - - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(context.getProject()).getInspectionProfile(); - if (!profile.isToolEnabled(key, context)) { - return null; - } - - final InspectionToolWrapper toolWrapper = - (InspectionToolWrapper)profile.getInspectionTool(inspectionShortName, context); - if (toolWrapper == null) { - return null; - } - - final AndroidLintInspectionBase inspection = (AndroidLintInspectionBase)toolWrapper.getTool(); - final HighlightDisplayLevel errorLevel = profile.getErrorLevel(key, context); - return new Pair(inspection, - errorLevel != null ? errorLevel : HighlightDisplayLevel.WARNING); - } - private static class MyDisableInspectionFix implements IntentionAction, Iconable { private final DisableInspectionToolAction myDisableInspectionToolAction; @@ -258,4 +242,90 @@ public class AndroidLintExternalAnnotator extends ExternalAnnotator getIssues() { @@ -128,13 +125,21 @@ class AndroidLintGlobalInspectionContext implements GlobalInspectionContextExten private final Project myProject; private final Map>> myProblemMap; private final AnalysisScope myScope; + private final Collection myIssues; private MyLintClient(@NotNull Project project, @NotNull Map>> problemMap, - @NotNull AnalysisScope scope) { + @NotNull AnalysisScope scope, + @NotNull Collection issues) { myProject = project; myProblemMap = problemMap; myScope = scope; + myIssues = issues; + } + + @Override + public Configuration getConfiguration(com.android.tools.lint.detector.api.Project project) { + return new IntellijLintConfiguration(myIssues); } @Override @@ -248,6 +253,9 @@ class AndroidLintGlobalInspectionContext implements GlobalInspectionContextExten @Override public List getJavaSourceFolders(com.android.tools.lint.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 result = new ArrayList(sourceRoots.length); diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java index 5cc3fe5fa98d..dfec9b262f16 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java @@ -5,11 +5,13 @@ import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Severity; import com.intellij.analysis.AnalysisScope; import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; @@ -34,12 +36,12 @@ import java.util.Map; /** * @author Eugene.Kudelevsky */ -abstract class AndroidLintInspectionBase extends GlobalInspectionTool { +public abstract class AndroidLintInspectionBase extends GlobalInspectionTool { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.lint.AndroidLintInspectionBase"); private static final Map ourIssue2InspectionShortName = new HashMap(); - private final Issue myIssue; + protected final Issue myIssue; private final String[] myGroupPath; private final String myDisplayName; @@ -58,6 +60,29 @@ abstract class AndroidLintInspectionBase extends GlobalInspectionTool { addIssue(issue, getShortName()); } + @NotNull + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return AndroidLintQuickFix.EMPTY_ARRAY; + } + + @NotNull + protected IntentionAction[] getIntentions(@NotNull PsiElement startElement, @NotNull PsiElement endElement) { + return IntentionAction.EMPTY_ARRAY; + } + + @NotNull + private LocalQuickFix[] getLocalQuickFixes(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull String message) { + final AndroidLintQuickFix[] fixes = getQuickFixes(message); + final LocalQuickFix[] result = new LocalQuickFix[fixes.length]; + + for (int i = 0; i < fixes.length; i++) { + if (fixes[i].isApplicable(startElement, endElement, true)) { + result[i] = new MyLocalQuickFix(fixes[i]); + } + } + return result; + } + @Override public void runInspection(AnalysisScope scope, final InspectionManager manager, @@ -81,7 +106,7 @@ abstract class AndroidLintInspectionBase extends GlobalInspectionTool { for (final Map.Entry> entry : file2ProblemList.entrySet()) { final File file = entry.getKey(); final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file); - + if (vFile == null) { continue; } @@ -104,9 +129,9 @@ abstract class AndroidLintInspectionBase extends GlobalInspectionTool { } @NotNull - private static ProblemDescriptor[] computeProblemDescriptors(@NotNull PsiFile psiFile, - @NotNull InspectionManager manager, - @NotNull List problems) { + private ProblemDescriptor[] computeProblemDescriptors(@NotNull PsiFile psiFile, + @NotNull InspectionManager manager, + @NotNull List problems) { final List result = new ArrayList(); for (ProblemData problemData : problems) { @@ -128,9 +153,9 @@ abstract class AndroidLintInspectionBase extends GlobalInspectionTool { } } } - + if (f != null) { - result.add(manager.createProblemDescriptor(f, message, false, LocalQuickFix.EMPTY_ARRAY, + result.add(manager.createProblemDescriptor(f, message, false, getLocalQuickFixes(f, f, message), ProblemHighlightType.GENERIC_ERROR_OR_WARNING)); } } @@ -140,7 +165,8 @@ abstract class AndroidLintInspectionBase extends GlobalInspectionTool { if (startElement != null && endElement != null) { result.add(manager.createProblemDescriptor(startElement, endElement, message + "#loc", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)); + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false, + getLocalQuickFixes(startElement, endElement, message))); } } } @@ -218,7 +244,7 @@ abstract class AndroidLintInspectionBase extends GlobalInspectionTool { @Override public boolean isEnabledByDefault() { - return true; + return myIssue.isEnabledByDefault(); } @NotNull @@ -254,4 +280,29 @@ abstract class AndroidLintInspectionBase extends GlobalInspectionTool { return null; } } + + static class MyLocalQuickFix implements LocalQuickFix { + private final AndroidLintQuickFix myLintQuickFix; + + MyLocalQuickFix(@NotNull AndroidLintQuickFix lintQuickFix) { + myLintQuickFix = lintQuickFix; + } + + @NotNull + @Override + public String getName() { + return myLintQuickFix.getName(); + } + + @NotNull + @Override + public String getFamilyName() { + return AndroidBundle.message("android.lint.quickfixes.family"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + myLintQuickFix.apply(descriptor.getStartElement(), descriptor.getEndElement(), null); + } + } } diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java index 2c44d975a613..8beeef803494 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java @@ -1,7 +1,13 @@ package org.jetbrains.android.inspections.lint; import com.android.tools.lint.checks.*; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LintConstants; +import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.InspectionToolProvider; +import com.intellij.psi.PsiElement; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; /** * @author Eugene.Kudelevsky @@ -9,11 +15,11 @@ import com.intellij.codeInspection.InspectionToolProvider; public class AndroidLintInspectionToolProvider implements InspectionToolProvider { @Override public Class[] getInspectionClasses() { - return new Class[] { + return new Class[]{ AndroidLintContentDescriptionInspection.class, AndroidLintAdapterViewChildrenInspection.class, AndroidLintScrollViewCountInspection.class, - AndroidLintDeprecatedInspection.class, + //AndroidLintDeprecatedInspection.class, AndroidLintMissingPrefixInspection.class, AndroidLintDuplicateIdsInspection.class, AndroidLintGridLayoutInspection.class, @@ -169,29 +175,44 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider public AndroidLintContentDescriptionInspection() { super("Missing content description", AccessibilityDetector.ISSUE); } + + @NotNull + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{ + new SetAttributeQuickFix(AndroidBundle.message("android.lint.inspections.add.content.description"), + LintConstants.ATTR_CONTENT_DESCRIPTION, null) + }; + } } public static class AndroidLintAdapterViewChildrenInspection extends AndroidLintInspectionBase { public AndroidLintAdapterViewChildrenInspection() { - super("AdapterView element cannot have children", ChildCountDetector.ADAPTERVIEW_ISSUE); + super(AndroidBundle.message("android.lint.inspections.adapter.view.children"), ChildCountDetector.ADAPTERVIEW_ISSUE); } } public static class AndroidLintScrollViewCountInspection extends AndroidLintInspectionBase { public AndroidLintScrollViewCountInspection() { - super("ScrollView element can have only one child", ChildCountDetector.SCROLLVIEW_ISSUE); + super(AndroidBundle.message("android.lint.inspections.scroll.view.children"), ChildCountDetector.SCROLLVIEW_ISSUE); } } - public static class AndroidLintDeprecatedInspection extends AndroidLintInspectionBase { + // it seems we don't need it because we have our own 'deprecated api' inspection + /*public static class AndroidLintDeprecatedInspection extends AndroidLintInspectionBase { public AndroidLintDeprecatedInspection() { - super("Deprecated XML elements", DeprecationDetector.ISSUE); + super(AndroidBundle.message("android.lint.inspections.deprecated"), DeprecationDetector.ISSUE); } - } + }*/ public static class AndroidLintMissingPrefixInspection extends AndroidLintInspectionBase { public AndroidLintMissingPrefixInspection() { - super("Missing android XML namespace", DetectMissingPrefix.MISSING_NAMESPACE); + super(AndroidBundle.message("android.lint.inspections.missing.prefix"), DetectMissingPrefix.MISSING_NAMESPACE); + } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{new AddMissingPrefixQuickFix()}; } } @@ -211,12 +232,26 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider public AndroidLintHardcodedTextInspection() { super("Hardcoded text", HardcodedValuesDetector.ISSUE); } + + @NotNull + @Override + protected IntentionAction[] getIntentions(@NotNull final PsiElement startElement, @NotNull PsiElement endElement) { + return new IntentionAction[]{new AndroidAddStringResourceQuickFix(startElement)}; + } } public static class AndroidLintInefficientWeightInspection extends AndroidLintInspectionBase { public AndroidLintInefficientWeightInspection() { super("Inefficient layout weight", InefficientWeightDetector.INEFFICIENT_WEIGHT); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{ + new InefficientWeightQuickFix() + }; + } } public static class AndroidLintNestedWeightsInspection extends AndroidLintInspectionBase { @@ -229,6 +264,15 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider public AndroidLintDisableBaselineAlignmentInspection() { super("Missing baselineAligned attribute", InefficientWeightDetector.BASELINE_WEIGHTS); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{ + new SetAttributeQuickFix(AndroidBundle.message("android.lint.inspections.set.baseline.attribute"), + LintConstants.ATTR_BASELINE_ALIGNED, "false") + }; + } } public static class AndroidLintManifestOrderInspection extends AndroidLintInspectionBase { @@ -253,6 +297,12 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider public AndroidLintObsoleteLayoutParamInspection() { super("Obsolete layout params", ObsoleteLayoutParamsDetector.ISSUE); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{new RemoveAttributeQuickFix()}; + } } public static class AndroidLintProguardInspection extends AndroidLintInspectionBase { @@ -265,18 +315,39 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider public AndroidLintPxUsageInspection() { super("Using 'px' dimension", PxUsageDetector.ISSUE); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{new ConvertToDpQuickFix()}; + } } public static class AndroidLintScrollViewSizeInspection extends AndroidLintInspectionBase { public AndroidLintScrollViewSizeInspection() { super("ScrollView size validation", ScrollViewChildDetector.ISSUE); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{new SetScrollViewSizeQuickFix()}; + } } public static class AndroidLintExportedServiceInspection extends AndroidLintInspectionBase { public AndroidLintExportedServiceInspection() { super("Exported service does not require permission", SecurityDetector.EXPORTED_SERVICE); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{ + new SetAttributeQuickFix(AndroidBundle.message("android.lint.inspections.add.permission.attribute"), + LintConstants.ATTR_PERMISSION, null) + }; + } } public static class AndroidLintGrantAllUrisInspection extends AndroidLintInspectionBase { @@ -295,6 +366,15 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider public AndroidLintTextFieldsInspection() { super("Text field missing inputType or hint settings", TextFieldDetector.ISSUE); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{ + new SetAttributeQuickFix(AndroidBundle.message("android.lint.inspections.add.input.type.attribute"), + LintConstants.ATTR_INPUT_TYPE, null) + }; + } } public static class AndroidLintTooManyViewsInspection extends AndroidLintInspectionBase { @@ -309,31 +389,31 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider } } - public static class AndroidLintTypographyDashesInspection extends AndroidLintInspectionBase { + public static class AndroidLintTypographyDashesInspection extends AndroidLintTypographyInspectionBase { public AndroidLintTypographyDashesInspection() { super("Hyphen can be replaced by dash", TypographyDetector.DASHES); } } - public static class AndroidLintTypographyQuotesInspection extends AndroidLintInspectionBase { + public static class AndroidLintTypographyQuotesInspection extends AndroidLintTypographyInspectionBase { public AndroidLintTypographyQuotesInspection() { super("Straight quotes can be replaced by curvy quotes", TypographyDetector.QUOTES); } } - public static class AndroidLintTypographyFractionsInspection extends AndroidLintInspectionBase { + public static class AndroidLintTypographyFractionsInspection extends AndroidLintTypographyInspectionBase { public AndroidLintTypographyFractionsInspection() { super("Fraction string can be replaced with fraction character", TypographyDetector.FRACTIONS); } } - public static class AndroidLintTypographyEllipsisInspection extends AndroidLintInspectionBase { + public static class AndroidLintTypographyEllipsisInspection extends AndroidLintTypographyInspectionBase { public AndroidLintTypographyEllipsisInspection() { super("Ellipsis string can be replaced with ellipsis character", TypographyDetector.ELLIPSIS); } } - public static class AndroidLintTypographyOtherInspection extends AndroidLintInspectionBase { + public static class AndroidLintTypographyOtherInspection extends AndroidLintTypographyInspectionBase { public AndroidLintTypographyOtherInspection() { super("Other typographical problems", TypographyDetector.OTHER); } @@ -355,5 +435,23 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider public AndroidLintUselessLeafInspection() { super("Useless leaf layout", UselessViewDetector.USELESS_LEAF); } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{new RemoveUselessViewQuickFix(myIssue)}; + } + } + + private static class AndroidLintTypographyInspectionBase extends AndroidLintInspectionBase { + public AndroidLintTypographyInspectionBase(String displayName, Issue issue) { + super(displayName, issue); + } + + @NotNull + @Override + protected AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[] {new TypographyQuickFix(myIssue, message)}; + } } } diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintQuickFix.java new file mode 100644 index 000000000000..f029cb923057 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintQuickFix.java @@ -0,0 +1,20 @@ +package org.jetbrains.android.inspections.lint; + +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +interface AndroidLintQuickFix { + AndroidLintQuickFix[] EMPTY_ARRAY = new AndroidLintQuickFix[0]; + + void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor); + + boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode); + + @NotNull + String getName(); +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintUtil.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintUtil.java new file mode 100644 index 000000000000..95767ccf261b --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintUtil.java @@ -0,0 +1,57 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.tools.lint.detector.api.Issue; +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.codeInspection.InspectionProfile; +import com.intellij.codeInspection.ex.InspectionToolWrapper; +import com.intellij.openapi.util.Pair; +import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +class AndroidLintUtil { + @NonNls static final String ATTR_VALUE_VERTICAL = "vertical"; + @NonNls static final String ATTR_VALUE_WRAP_CONTENT = "wrap_content"; + @NonNls static final String ATTR_LAYOUT_HEIGHT = "layout_height"; + @NonNls static final String ATTR_LAYOUT_WIDTH = "layout_width"; + @NonNls static final String ATTR_ORIENTATION = "orientation"; + + private AndroidLintUtil() { + } + + @Nullable + static Pair getHighlighLevelAndInspection(@NotNull Issue issue, + @NotNull PsiElement context) { + final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(issue); + if (inspectionShortName == null) { + return null; + } + + final HighlightDisplayKey key = HighlightDisplayKey.find(inspectionShortName); + if (key == null) { + return null; + } + + final InspectionProfile profile = InspectionProjectProfileManager.getInstance(context.getProject()).getInspectionProfile(); + if (!profile.isToolEnabled(key, context)) { + return null; + } + + final InspectionToolWrapper toolWrapper = + (InspectionToolWrapper)profile.getInspectionTool(inspectionShortName, context); + if (toolWrapper == null) { + return null; + } + + final AndroidLintInspectionBase inspection = (AndroidLintInspectionBase)toolWrapper.getTool(); + final HighlightDisplayLevel errorLevel = profile.getErrorLevel(key, context); + return new Pair(inspection, + errorLevel != null ? errorLevel : HighlightDisplayLevel.WARNING); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/ConvertToDpQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/ConvertToDpQuickFix.java new file mode 100644 index 000000000000..e0190e1bc360 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/ConvertToDpQuickFix.java @@ -0,0 +1,127 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.resources.Density; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.ui.Messages; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Eugene.Kudelevsky + */ +class ConvertToDpQuickFix implements AndroidLintQuickFix { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.lint.ConvertToDpQuickFix"); + private static final Pattern PX_ATTR_VALUE_PATTERN = Pattern.compile("(\\d+)px"); + + private static int ourPrevDpi = Density.DEFAULT_DENSITY; + + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + if (editor == null) { + return; + } + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + final List densities = new ArrayList(); + + for (Density density : Density.values()) { + if (density.getDpiValue() > 0) { + densities.add(density); + } + } + + final String[] densityPresentableNames = new String[densities.size()]; + + String defaultValue = null; + String initialValue = null; + + for (int i = 0; i < densities.size(); i++) { + final Density density = densities.get(i); + densityPresentableNames[i] = getLabelForDensity(density); + + final int dpi = density.getDpiValue(); + if (dpi == 0) { + continue; + } + + if (dpi == ourPrevDpi) { + initialValue = densityPresentableNames[i]; + } + else if (dpi == Density.DEFAULT_DENSITY) { + defaultValue = densityPresentableNames[i]; + } + } + + if (initialValue == null) { + initialValue = defaultValue; + } + if (initialValue == null) { + return; + } + + final int dpi; + + if (ApplicationManager.getApplication().isUnitTestMode()) { + dpi = Density.DEFAULT_DENSITY; + } + else { + final int selectedIndex = Messages + .showChooseDialog("What is the screen density the current px value works with?", "Choose density", densityPresentableNames, + initialValue, null); + if (selectedIndex < 0) { + return; + } + dpi = densities.get(selectedIndex).getDpiValue(); + } + + //noinspection AssignmentToStaticFieldFromInstanceMethod + ourPrevDpi = dpi; + + for (XmlAttribute attribute : tag.getAttributes()) { + final String value = attribute.getValue(); + + if (value.endsWith("px")) { + final Matcher matcher = PX_ATTR_VALUE_PATTERN.matcher(value); + + if (matcher.matches()) { + final String numberString = matcher.group(1); + try { + final int px = Integer.parseInt(numberString); + final int dp = px * 160 / dpi; + attribute.setValue(Integer.toString(dp) + "dp"); + } + catch (NumberFormatException nufe) { + LOG.error(nufe); + } + } + } + } + } + + @NotNull + private static String getLabelForDensity(@NotNull Density density) { + return String.format("%1$s (%2$d)", density.getShortDisplayValue(), density.getDpiValue()); + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + return !inBatchMode && PsiTreeUtil.getParentOfType(startElement, XmlTag.class) != null; + } + + @NotNull + @Override + public String getName() { + return AndroidBundle.message("android.lint.inspections.convert.to.dp"); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/FakeNode.java b/plugins/android/src/org/jetbrains/android/inspections/lint/FakeNode.java new file mode 100644 index 000000000000..8504add151a7 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/FakeNode.java @@ -0,0 +1,200 @@ +package org.jetbrains.android.inspections.lint; + +import org.jetbrains.annotations.NotNull; +import org.w3c.dom.*; + +/** + * @author Eugene.Kudelevsky + */ +class FakeNode implements Node { + private final String myText; + + FakeNode(@NotNull String text) { + myText = text; + } + + @Override + public String getNodeName() { + throw new UnsupportedOperationException(); + } + + @Override + public String getNodeValue() throws DOMException { + return myText; + } + + @Override + public void setNodeValue(String nodeValue) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public short getNodeType() { + throw new UnsupportedOperationException(); + } + + @Override + public Node getParentNode() { + throw new UnsupportedOperationException(); + } + + @Override + public NodeList getChildNodes() { + throw new UnsupportedOperationException(); + } + + @Override + public Node getFirstChild() { + throw new UnsupportedOperationException(); + } + + @Override + public Node getLastChild() { + throw new UnsupportedOperationException(); + } + + @Override + public Node getPreviousSibling() { + throw new UnsupportedOperationException(); + } + + @Override + public Node getNextSibling() { + throw new UnsupportedOperationException(); + } + + @Override + public NamedNodeMap getAttributes() { + throw new UnsupportedOperationException(); + } + + @Override + public Document getOwnerDocument() { + throw new UnsupportedOperationException(); + } + + @Override + public Node insertBefore(Node newChild, Node refChild) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public Node replaceChild(Node newChild, Node oldChild) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public Node removeChild(Node oldChild) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public Node appendChild(Node newChild) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasChildNodes() { + throw new UnsupportedOperationException(); + } + + @Override + public Node cloneNode(boolean deep) { + throw new UnsupportedOperationException(); + } + + @Override + public void normalize() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSupported(String feature, String version) { + throw new UnsupportedOperationException(); + } + + @Override + public String getNamespaceURI() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPrefix() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPrefix(String prefix) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public String getLocalName() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasAttributes() { + throw new UnsupportedOperationException(); + } + + @Override + public String getBaseURI() { + throw new UnsupportedOperationException(); + } + + @Override + public short compareDocumentPosition(Node other) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public String getTextContent() throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public void setTextContent(String textContent) throws DOMException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSameNode(Node other) { + throw new UnsupportedOperationException(); + } + + @Override + public String lookupPrefix(String namespaceURI) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDefaultNamespace(String namespaceURI) { + throw new UnsupportedOperationException(); + } + + @Override + public String lookupNamespaceURI(String prefix) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEqualNode(Node arg) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getFeature(String feature, String version) { + throw new UnsupportedOperationException(); + } + + @Override + public Object setUserData(String key, Object data, UserDataHandler handler) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getUserData(String key) { + throw new UnsupportedOperationException(); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/InefficientWeightQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/InefficientWeightQuickFix.java new file mode 100644 index 000000000000..7d2c4e6ee443 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/InefficientWeightQuickFix.java @@ -0,0 +1,54 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.sdklib.SdkConstants; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +class InefficientWeightQuickFix implements AndroidLintQuickFix { + + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + if (tag == null) { + return; + } + + final XmlTag parentTag = tag.getParentTag(); + if (parentTag == null) { + return; + } + String attrName; + + if (AndroidLintUtil.ATTR_VALUE_VERTICAL + .equals(parentTag.getAttributeValue(AndroidLintUtil.ATTR_ORIENTATION, SdkConstants.NS_RESOURCES))) { + attrName = AndroidLintUtil.ATTR_LAYOUT_HEIGHT; + } + else { + attrName = AndroidLintUtil.ATTR_LAYOUT_WIDTH; + } + tag.setAttribute(attrName, SdkConstants.NS_RESOURCES, "0dp"); + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + if (tag == null) { + return false; + } + return tag.getParentTag() != null; + } + + @NotNull + @Override + public String getName() { + return AndroidBundle.message("android.lint.inspections.replace.with.zero.dp"); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/IntellijLintClient.java b/plugins/android/src/org/jetbrains/android/inspections/lint/IntellijLintClient.java index cab3907e7252..528970c87f64 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/IntellijLintClient.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/IntellijLintClient.java @@ -1,6 +1,7 @@ package org.jetbrains.android.inspections.lint; import com.android.tools.lint.PositionXmlParser; +import com.android.tools.lint.client.api.Configuration; import com.android.tools.lint.client.api.IDomParser; import com.android.tools.lint.client.api.LintClient; import com.android.tools.lint.detector.api.Context; @@ -43,6 +44,11 @@ class IntellijLintClient extends LintClient implements Disposable { myState = state; } + @Override + public Configuration getConfiguration(com.android.tools.lint.detector.api.Project project) { + return new IntellijLintConfiguration(myState.getIssues()); + } + @Override public void report(Context context, Issue issue, Location location, String message, Object data) { if (location != null) { diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/IntellijLintConfiguration.java b/plugins/android/src/org/jetbrains/android/inspections/lint/IntellijLintConfiguration.java new file mode 100644 index 000000000000..d24cede931b2 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/IntellijLintConfiguration.java @@ -0,0 +1,43 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.tools.lint.client.api.Configuration; +import com.android.tools.lint.detector.api.Context; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Location; +import com.android.tools.lint.detector.api.Severity; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Set; + +/** + * @author Eugene.Kudelevsky + */ +class IntellijLintConfiguration extends Configuration { + private final Set myIssueSet; + + IntellijLintConfiguration(@NotNull Collection issueSet) { + myIssueSet = new HashSet(issueSet); + } + + @Override + public void ignore(Context context, Issue issue, Location location, String message, Object data) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSeverity(Issue issue, Severity severity) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEnabled(Issue issue) { + return myIssueSet.contains(issue); + } + + @Override + public Severity getSeverity(Issue issue) { + return Severity.WARNING; + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/RemoveAttributeQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/RemoveAttributeQuickFix.java new file mode 100644 index 000000000000..b9d01eaba5e9 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/RemoveAttributeQuickFix.java @@ -0,0 +1,33 @@ +package org.jetbrains.android.inspections.lint; + +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +class RemoveAttributeQuickFix implements AndroidLintQuickFix { + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + final XmlAttribute attribute = PsiTreeUtil.getParentOfType(startElement, XmlAttribute.class); + if (attribute != null) { + attribute.getParent().setAttribute(attribute.getName(), null); + } + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + return PsiTreeUtil.getParentOfType(startElement, XmlAttribute.class) != null; + } + + @NotNull + @Override + public String getName() { + return AndroidBundle.message("android.lint.inspections.remove.attribute"); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/RemoveUselessViewQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/RemoveUselessViewQuickFix.java new file mode 100644 index 000000000000..3c429b7fe435 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/RemoveUselessViewQuickFix.java @@ -0,0 +1,58 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.tools.lint.checks.UselessViewDetector; +import com.android.tools.lint.detector.api.Issue; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +public class RemoveUselessViewQuickFix implements AndroidLintQuickFix { + private final Issue myIssue; + + public RemoveUselessViewQuickFix(@NotNull Issue issue) { + myIssue = issue; + } + + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + if (tag == null) { + return; + } + + final XmlTag parentTag = tag.getParentTag(); + if (parentTag == null) { + return; + } + + if (myIssue.getId().equals(UselessViewDetector.USELESS_LEAF.getId())) { + tag.delete(); + } + else { + assert false; + // todo: implement + } + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + if (tag == null) { + return false; + } + return tag.getParentTag() != null; + } + + @NotNull + @Override + public String getName() { + return AndroidBundle.message("android.lint.inspections.remove.unnecessary.view"); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/SetAttributeQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/SetAttributeQuickFix.java new file mode 100644 index 000000000000..4143549ef208 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/SetAttributeQuickFix.java @@ -0,0 +1,74 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.sdklib.SdkConstants; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +class SetAttributeQuickFix implements AndroidLintQuickFix { + + private final String myName; + private final String myAttributeName; + private final String myValue; + + SetAttributeQuickFix(@NotNull String name, @NotNull String attributeName, @Nullable String value) { + super(); + myName = name; + myAttributeName = attributeName; + myValue = value; + } + + @NotNull + @Override + public String getName() { + return myName; + } + + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class, false); + + if (tag == null) { + return; + } + final XmlAttribute attribute = tag.setAttribute(myAttributeName, SdkConstants.NS_RESOURCES, ""); + + if (attribute != null) { + if (myValue != null) { + attribute.setValue(myValue); + } + if (editor != null) { + final XmlAttributeValue valueElement = attribute.getValueElement(); + final TextRange valueTextRange = attribute.getValueTextRange(); + + if (valueElement != null && valueTextRange != null) { + final int valueElementStart = valueElement.getTextRange().getStartOffset(); + editor.getCaretModel().moveToOffset(valueElementStart + valueTextRange.getStartOffset()); + + if (valueTextRange.getStartOffset() < valueTextRange.getEndOffset()) { + editor.getSelectionModel().setSelection(valueElementStart + valueTextRange.getStartOffset(), + valueElementStart + valueTextRange.getEndOffset()); + } + } + } + } + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class, false); + if (tag == null) { + return false; + } + return tag.getAttribute(myAttributeName, SdkConstants.NS_RESOURCES) == null; + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/SetScrollViewSizeQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/SetScrollViewSizeQuickFix.java new file mode 100644 index 000000000000..336bb9a11f61 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/SetScrollViewSizeQuickFix.java @@ -0,0 +1,50 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.sdklib.SdkConstants; +import com.android.tools.lint.detector.api.LintConstants; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +class SetScrollViewSizeQuickFix implements AndroidLintQuickFix { + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + if (tag == null) { + return; + } + + final XmlTag parentTag = tag.getParentTag(); + if (parentTag == null) { + return; + } + + final boolean isHorizontal = LintConstants.HORIZONTAL_SCROLL_VIEW.equals(parentTag.getName()); + final String attributeName = isHorizontal + ? AndroidLintUtil.ATTR_LAYOUT_WIDTH + : AndroidLintUtil.ATTR_LAYOUT_HEIGHT; + tag.setAttribute(attributeName, SdkConstants.NS_RESOURCES, AndroidLintUtil.ATTR_VALUE_WRAP_CONTENT); + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + if (tag == null) { + return false; + } + return tag.getParentTag() != null; + } + + @NotNull + @Override + public String getName() { + return AndroidBundle.message("android.lint.inspections.set.to.wrap.content"); + } +} diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/TypographyQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/TypographyQuickFix.java new file mode 100644 index 000000000000..74e7e737cdc8 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/TypographyQuickFix.java @@ -0,0 +1,67 @@ +package org.jetbrains.android.inspections.lint; + +import com.android.tools.lint.checks.TypographyDetector; +import com.android.tools.lint.detector.api.Issue; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.xml.XmlText; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author Eugene.Kudelevsky + */ +class TypographyQuickFix implements AndroidLintQuickFix { + private final Issue myIssue; + private final String myMessage; + + public TypographyQuickFix(@NotNull Issue issue, @NotNull String message) { + myIssue = issue; + myMessage = message; + } + + @Override + public void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor) { + final XmlTag tag = PsiTreeUtil.getParentOfType(startElement, XmlTag.class); + if (tag == null) { + return; + } + + for (PsiElement child : tag.getChildren()) { + if (child instanceof XmlText) { + final XmlText xmlText = (XmlText)child; + final String value = xmlText.getValue(); + + if (value != null) { + final List edits = TypographyDetector.getEdits(myIssue.getId(), myMessage, new FakeNode(value)); + final StringBuilder builder = new StringBuilder(value); + + for (TypographyDetector.ReplaceEdit edit : edits) { + builder.replace(edit.offset, edit.offset + edit.length, edit.replaceWith); + } + + final String newValue = builder.toString(); + if (!newValue.equals(value)) { + xmlText.setValue(newValue); + } + } + } + } + } + + @Override + public boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, boolean inBatchMode) { + return PsiTreeUtil.getParentOfType(startElement, XmlTag.class) != null; + } + + @NotNull + @Override + public String getName() { + return AndroidBundle.message("android.lint.inspections.replace.with.suggested.characters"); + } +} diff --git a/plugins/android/src/org/jetbrains/android/intentions/AndroidAddStringResourceAction.java b/plugins/android/src/org/jetbrains/android/intentions/AndroidAddStringResourceAction.java index 9826b6d9aabe..08e641d87637 100644 --- a/plugins/android/src/org/jetbrains/android/intentions/AndroidAddStringResourceAction.java +++ b/plugins/android/src/org/jetbrains/android/intentions/AndroidAddStringResourceAction.java @@ -16,42 +16,57 @@ package org.jetbrains.android.intentions; +import com.android.resources.ResourceType; import com.intellij.CommonBundle; import com.intellij.codeInsight.intention.AbstractIntentionAction; +import com.intellij.codeInsight.intention.HighPriorityAction; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.codeInsight.template.*; import com.intellij.codeInsight.template.impl.*; import com.intellij.codeInsight.template.macro.VariableOfTypeMacro; +import com.intellij.ide.actions.CreateElementActionBase; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.undo.UndoUtil; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.InputValidatorEx; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.psi.xml.XmlFile; import com.intellij.refactoring.util.RefactoringUtil; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.xml.Converter; +import com.intellij.util.xml.DomManager; +import com.intellij.util.xml.GenericAttributeValue; +import org.jetbrains.android.AndroidFileTemplateProvider; +import org.jetbrains.android.actions.CreateXmlResourceDialog; +import org.jetbrains.android.dom.converters.ResourceReferenceConverter; import org.jetbrains.android.dom.manifest.Manifest; import org.jetbrains.android.dom.resources.ResourceElement; +import org.jetbrains.android.dom.resources.ResourceValue; +import org.jetbrains.android.dom.resources.Resources; import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.resourceManagers.LocalResourceManager; import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.android.util.AndroidResourceUtil; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.File; import java.util.LinkedHashSet; import java.util.Set; -import static com.intellij.openapi.ui.Messages.showInputDialog; import static org.jetbrains.android.util.AndroidUtils.VIEW_CLASS_NAME; /** @@ -61,7 +76,9 @@ import static org.jetbrains.android.util.AndroidUtils.VIEW_CLASS_NAME; * Time: 5:02:31 PM * To change this template use File | Settings | File Templates. */ -public class AndroidAddStringResourceAction extends AbstractIntentionAction { +public class AndroidAddStringResourceAction extends AbstractIntentionAction implements HighPriorityAction { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.intentions.AndroidAddStringResourceAction"); + private static final String CONTEXT = AndroidUtils.ANDROID_PACKAGE + ".content.Context"; private static final String RESOURCES = AndroidUtils.ANDROID_PACKAGE + ".content.res.Resources"; @@ -94,6 +111,31 @@ public class AndroidAddStringResourceAction extends AbstractIntentionAction { return (String)value; } } + else if (file instanceof XmlFile && element instanceof XmlAttributeValue) { + final XmlAttribute attribute = PsiTreeUtil.getParentOfType(element, XmlAttribute.class); + + if (attribute != null) { + final GenericAttributeValue domAttribute = DomManager.getDomManager(element.getProject()).getDomElement(attribute); + + if (domAttribute != null) { + final Converter converter = domAttribute.getConverter(); + + if (converter instanceof ResourceReferenceConverter) { + final ResourceValue value = (ResourceValue)domAttribute.getValue(); + + if (value != null && !value.isReference()) { + final Set types = ((ResourceReferenceConverter)converter).getResourceTypes(domAttribute); + + for (String type : types) { + if (ResourceType.STRING.getName().equals(type)) { + return ((XmlAttributeValue)element).getValue(); + } + } + } + } + } + } + } return null; } @@ -124,50 +166,70 @@ public class AndroidAddStringResourceAction extends AbstractIntentionAction { } static void doInvoke(Project project, Editor editor, PsiFile file, @Nullable String resName) { - AndroidFacet facet = AndroidFacet.getInstance(file); - assert facet != null; - PsiElement element = getPsiElement(file, editor); + final PsiElement element = getPsiElement(file, editor); assert element != null; + + doInvoke(project, editor, file, resName, element); + } + + protected static void doInvoke(Project project, Editor editor, PsiFile file, @Nullable String resName, PsiElement element) { String value = getStringLiteralValue(element, file); assert value != null; + + final AndroidFacet facet = AndroidFacet.getInstance(file); + assert facet != null; + value = value.replace("'", "\\'").replace("\"", "\\\""); - String aPackage = getPackage(facet); + + final String aPackage = getPackage(facet); if (aPackage == null) { Messages.showErrorDialog(project, AndroidBundle.message("package.not.found.error"), CommonBundle.getErrorTitle()); return; } + if (resName == null) { - resName = - showInputDialog(project, AndroidBundle.message("resource.name"), AndroidBundle.message("add.string.resource.intention.text"), - Messages.getQuestionIcon(), "", new InputValidatorEx() { - public String getErrorText(String inputString) { - if (inputString == null || inputString.length() == 0) return null; - String[] ids = inputString.split("."); - for (String id : ids) { - if (!StringUtil.isJavaIdentifier(id)) { - return AndroidBundle.message("android.identifier.expected", id); - } - } - return null; - } + final CreateXmlResourceDialog dialog = new CreateXmlResourceDialog(facet.getModule(), ResourceType.STRING); + dialog.setTitle("Extract String Resource"); + dialog.show(); - public boolean checkInput(String inputString) { - return inputString != null && AndroidResourceUtil.isCorrectAndroidResourceName(inputString); - } + if (!dialog.isOK()) { + return; + } - public boolean canClose(String inputString) { - return checkInput(inputString); - } - }); + final Module module = dialog.getModule(); + if (module == null) { + return; + } + + final ResourceElement createdElement = + doCreate(module, dialog.getResourceName(), ResourceType.STRING, dialog.getFileName(), dialog.getDirectoryName(), value); + + if (createdElement != null) { + resName = createdElement.getName().getValue(); + } } - if (resName == null) return; - LocalResourceManager manager = facet.getLocalResourceManager(); - String resType = "string"; - ResourceElement resElement = manager.addValueResource(resType, resName, value); - assert resElement != null; - createJavaResourceReference(project, editor, file, element, aPackage, resName, resType); + else { + assert ApplicationManager.getApplication().isUnitTestMode(); + doCreate(facet.getModule(), resName, ResourceType.STRING, "strings.xml", "values", value); + } + + if (resName == null) { + return; + } + + if (file instanceof PsiJavaFile) { + createJavaResourceReference(project, editor, file, element, aPackage, resName, ResourceType.STRING.getName()); + } + else { + final XmlAttribute attribute = PsiTreeUtil.getParentOfType(element, XmlAttribute.class); + if (attribute != null) { + attribute.setValue(ResourceValue.referenceTo('@', null, ResourceType.STRING.getName(), resName).toString()); + } + } + PsiDocumentManager.getInstance(project).commitAllDocuments(); UndoUtil.markPsiFileForUndo(file); + ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { ApplicationManager.getApplication().saveAll(); @@ -188,14 +250,25 @@ public class AndroidAddStringResourceAction extends AbstractIntentionAction { assert methodName != null; final TemplateImpl template; final boolean inStaticContext = RefactoringUtil.isInStaticContext(element, null); + if (extendsContext && !inStaticContext) { - template = new TemplateImpl("", "$resources$." + methodName + "(" + field + ")", ""); - MacroCallNode node = new MacroCallNode(new MyVarOfTypeExpression("getResources()")); - node.addParameter(new ConstantNode(RESOURCES)); - template.addVariable("resources", node, new ConstantNode(""), true); + if (ResourceType.STRING.getName().equals(resType)) { + template = new TemplateImpl("", methodName + '(' + field + ')', ""); + } + else { + template = new TemplateImpl("", "$resources$." + methodName + "(" + field + ")", ""); + MacroCallNode node = new MacroCallNode(new MyVarOfTypeExpression("getResources()")); + node.addParameter(new ConstantNode(RESOURCES)); + template.addVariable("resources", node, new ConstantNode(""), true); + } } else { - template = new TemplateImpl("", "$context$.getResources()." + methodName + "(" + field + ")", ""); + if (ResourceType.STRING.getName().equals(resType)) { + template = new TemplateImpl("", "$context$." + methodName + "(" + field + ")", ""); + } + else { + template = new TemplateImpl("", "$context$.getResources()." + methodName + "(" + field + ")", ""); + } final boolean extendsView = getContainingInheritorOf(element, VIEW_CLASS_NAME) != null; MacroCallNode node = new MacroCallNode(extendsView && !inStaticContext ? new MyVarOfTypeExpression("getContext()") : new VariableOfTypeMacro()); @@ -242,6 +315,104 @@ public class AndroidAddStringResourceAction extends AbstractIntentionAction { return true; } + @Nullable + private static ResourceElement doCreate(@NotNull Module module, + @NotNull String resourceName, + @NotNull ResourceType resourceType, + @NotNull String fileName, + @NotNull String dirName, + @NotNull String value) { + final Project project = module.getProject(); + final AndroidFacet facet = AndroidFacet.getInstance(module); + assert facet != null; + + try { + return addValueResource(facet, resourceName, resourceType, fileName, dirName, value); + } + catch (Exception e) { + final String message = CreateElementActionBase.filterMessage(e.getMessage()); + + if (message == null || message.length() == 0) { + LOG.error(e); + } + else { + LOG.info(e); + reportError(project, message); + } + return null; + } + } + + @Nullable + public static ResourceElement addValueResource(@NotNull AndroidFacet facet, + @NotNull String resourceName, + @NotNull ResourceType resourceType, + @NotNull String fileName, + @NotNull String dirName, + @NotNull String value) throws Exception { + final VirtualFile resFile = findOrCreateResourceFile(facet, fileName, dirName); + if (resFile == null) { + return null; + } + + final Resources resources = AndroidUtils.loadDomElement(facet.getModule(), resFile, Resources.class); + if (resources == null) { + reportError(facet.getModule().getProject(), AndroidBundle.message("not.resource.file.error", fileName)); + return null; + } + + final ResourceElement element = AndroidResourceUtil.addValueResource(resourceType.getName(), resources); + element.getName().setValue(resourceName); + + if (value.length() > 0) { + element.setStringValue(value); + } + return element; + } + + @Nullable + private static VirtualFile findOrCreateResourceFile(@NotNull AndroidFacet facet, + @NotNull final String fileName, + @NotNull String dirName) throws Exception { + final Module module = facet.getModule(); + final Project project = module.getProject(); + final VirtualFile resDir = facet.getLocalResourceManager().getResourceDir(); + + if (resDir == null) { + reportError(project, AndroidBundle.message("check.resource.dir.error", module.getName())); + return null; + } + final VirtualFile dir = AndroidUtils.createChildDirectoryIfNotExist(project, resDir, dirName); + final String dirPath = FileUtil.toSystemDependentName(resDir.getPath() + '/' + dirName); + + if (dir == null) { + reportError(project, AndroidBundle.message("android.cannot.create.dir.error", dirPath)); + return null; + } + + final VirtualFile file = dir.findChild(fileName); + if (file != null) { + return file; + } + + AndroidFileTemplateProvider + .createFromTemplate(project, dir, AndroidFileTemplateProvider.VALUE_RESOURCE_FILE_TEMPLATE, fileName); + final VirtualFile result = dir.findChild(fileName); + if (result == null) { + reportError(project, AndroidBundle.message("android.cannot.create.file.error", dirPath + File.separatorChar + fileName)); + } + return result; + } + + private static void reportError(@NotNull Project project, @NotNull String message) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + throw new IncorrectOperationException(message); + } + else { + Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle()); + } + } + private static class MyVarOfTypeExpression extends VariableOfTypeMacro { private final String myDefaultValue; diff --git a/plugins/android/src/org/jetbrains/android/resourceManagers/LocalResourceManager.java b/plugins/android/src/org/jetbrains/android/resourceManagers/LocalResourceManager.java index fc34714eefeb..3722ec9a371b 100644 --- a/plugins/android/src/org/jetbrains/android/resourceManagers/LocalResourceManager.java +++ b/plugins/android/src/org/jetbrains/android/resourceManagers/LocalResourceManager.java @@ -208,7 +208,8 @@ public class LocalResourceManager extends ResourceManager { private VirtualFile findOrCreateResourceFile(@NotNull final String fileName) { VirtualFile dir = getResourceDir(); if (dir == null) { - Messages.showErrorDialog(myModule.getProject(), AndroidBundle.message("check.resource.dir.error"), CommonBundle.getErrorTitle()); + Messages.showErrorDialog(myModule.getProject(), AndroidBundle.message("check.resource.dir.error", myModule.getName()), + CommonBundle.getErrorTitle()); return null; } final VirtualFile valuesDir = findOrCreateChildDir(dir, AndroidConstants.FD_RES_VALUES); @@ -240,7 +241,8 @@ public class LocalResourceManager extends ResourceManager { VirtualFile resDir = getResourceDir(); Project project = myModule.getProject(); if (resDir == null) { - Messages.showErrorDialog(project, AndroidBundle.message("check.resource.dir.error"), CommonBundle.getErrorTitle()); + Messages + .showErrorDialog(project, AndroidBundle.message("check.resource.dir.error", myModule.getName()), CommonBundle.getErrorTitle()); return null; } PsiElement[] createdElements = CreateResourceFileAction.createResourceFile(project, resDir, resType, fileOrResourceName); @@ -269,7 +271,7 @@ public class LocalResourceManager extends ResourceManager { CommonBundle.getErrorTitle()); return null; } - ResourceElement element = addValueResource(type, resources); + ResourceElement element = AndroidResourceUtil.addValueResource(type, resources); element.getName().setValue(name); if (value != null) { element.setStringValue(value); @@ -277,41 +279,6 @@ public class LocalResourceManager extends ResourceManager { return element; } - @NotNull - private static ResourceElement addValueResource(@NotNull final String type, @NotNull final Resources resources) { - if (type.equals("string")) { - return resources.addString(); - } - else if (type.equals("dimen")) { - return resources.addDimen(); - } - else if (type.equals("color")) { - return resources.addColor(); - } - else if (type.equals("drawable")) { - return resources.addDrawable(); - } - else if (type.equals("style")) { - return resources.addStyle(); - } - else if (type.equals("array")) { - // todo: choose among string-array, integer-array and array - return resources.addStringArray(); - } - else if (type.equals("integer")) { - return resources.addInteger(); - } - else if (type.equals("bool")) { - return resources.addBool(); - } - else if (type.equals("id")) { - Item item = resources.addItem(); - item.getType().setValue("id"); - return item; - } - throw new IllegalArgumentException("Incorrect resource type"); - } - @Nullable private VirtualFile findOrCreateChildDir(@NotNull final VirtualFile dir, @NotNull final String name) { try { diff --git a/plugins/android/src/org/jetbrains/android/uipreview/DeviceConfiguratorPanel.java b/plugins/android/src/org/jetbrains/android/uipreview/DeviceConfiguratorPanel.java index 19738d60d394..ebb4303166f4 100644 --- a/plugins/android/src/org/jetbrains/android/uipreview/DeviceConfiguratorPanel.java +++ b/plugins/android/src/org/jetbrains/android/uipreview/DeviceConfiguratorPanel.java @@ -252,7 +252,7 @@ public abstract class DeviceConfiguratorPanel extends JPanel { applyEditors(); } - protected void doApplyEditors() throws InvalidOptionValueException { + public void doApplyEditors() throws InvalidOptionValueException { try { final FolderConfiguration newConfig = new FolderConfiguration(); diff --git a/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java b/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java index b3c0a380d7a8..4d95bb5dc5b1 100644 --- a/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java +++ b/plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java @@ -35,9 +35,7 @@ import com.intellij.util.containers.HashSet; import com.intellij.util.indexing.FileBasedIndex; import org.jetbrains.android.AndroidIdIndex; import org.jetbrains.android.dom.manifest.Manifest; -import org.jetbrains.android.dom.resources.Attr; -import org.jetbrains.android.dom.resources.DeclareStyleable; -import org.jetbrains.android.dom.resources.ResourceElement; +import org.jetbrains.android.dom.resources.*; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.resourceManagers.LocalResourceManager; import org.jetbrains.android.resourceManagers.ResourceManager; @@ -402,4 +400,39 @@ public class AndroidResourceUtil { List targets = findResourcesByField(manager, resField); return PsiUtilBase.toPsiElementArray(targets); } + + @NotNull + public static ResourceElement addValueResource(@NotNull final String type, @NotNull final Resources resources) { + if (type.equals("string")) { + return resources.addString(); + } + else if (type.equals("dimen")) { + return resources.addDimen(); + } + else if (type.equals("color")) { + return resources.addColor(); + } + else if (type.equals("drawable")) { + return resources.addDrawable(); + } + else if (type.equals("style")) { + return resources.addStyle(); + } + else if (type.equals("array")) { + // todo: choose among string-array, integer-array and array + return resources.addStringArray(); + } + else if (type.equals("integer")) { + return resources.addInteger(); + } + else if (type.equals("bool")) { + return resources.addBool(); + } + else if (type.equals("id")) { + Item item = resources.addItem(); + item.getType().setValue("id"); + return item; + } + throw new IllegalArgumentException("Incorrect resource type"); + } } diff --git a/plugins/android/testData/addStringRes/Class10_after.java b/plugins/android/testData/addStringRes/Class10_after.java index f4fa6807e3f1..baf3c102a5e4 100644 --- a/plugins/android/testData/addStringRes/Class10_after.java +++ b/plugins/android/testData/addStringRes/Class10_after.java @@ -6,6 +6,6 @@ import p1.p2.R; public class Class extends View { public static void f(Context context) { - String s = context.getResources().getString(R.string.hello); + String s = context.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class11_after.java b/plugins/android/testData/addStringRes/Class11_after.java index 8b6382d95d97..9382074c0fb6 100644 --- a/plugins/android/testData/addStringRes/Class11_after.java +++ b/plugins/android/testData/addStringRes/Class11_after.java @@ -6,6 +6,6 @@ import p1.p2.R; public class Class extends View { public void f(Context context) { - String s = context.getResources().getString(R.string.hello); + String s = context.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class12_after.java b/plugins/android/testData/addStringRes/Class12_after.java index 83e438df6bc1..7e60cc7a2b4e 100644 --- a/plugins/android/testData/addStringRes/Class12_after.java +++ b/plugins/android/testData/addStringRes/Class12_after.java @@ -6,6 +6,6 @@ import p1.p2.R; public class Class extends View { public void f() { - String s = getContext().getResources().getString(R.string.hello); + String s = getContext().getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class13.java b/plugins/android/testData/addStringRes/Class13.java index 88386f469397..9e041435687c 100644 --- a/plugins/android/testData/addStringRes/Class13.java +++ b/plugins/android/testData/addStringRes/Class13.java @@ -4,7 +4,7 @@ import android.content.Context; import android.content.res.Resources; public class Class extends Context { - public void f(Resources resources) { + public void f(Context resources) { String s = "hello"; } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class13_after.java b/plugins/android/testData/addStringRes/Class13_after.java index 5ec2a470edd8..09500911aac2 100644 --- a/plugins/android/testData/addStringRes/Class13_after.java +++ b/plugins/android/testData/addStringRes/Class13_after.java @@ -5,7 +5,7 @@ import android.content.res.Resources; import p1.p2.R; public class Class extends Context { - public void f(Resources resources) { - String s = resources.getString(R.string.hello); + public void f(Context resources) { + String s = getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class14.java b/plugins/android/testData/addStringRes/Class14.java index a878406b7b5e..712cfb75b390 100644 --- a/plugins/android/testData/addStringRes/Class14.java +++ b/plugins/android/testData/addStringRes/Class14.java @@ -4,7 +4,7 @@ import android.content.Context; import android.content.res.Resources; public class Class extends Context { - public void f(Resources resources) { + public void f(Context resources) { String s = "hello"; } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class1_after.java b/plugins/android/testData/addStringRes/Class1_after.java index 9ff6724619a7..97da0a51a00a 100644 --- a/plugins/android/testData/addStringRes/Class1_after.java +++ b/plugins/android/testData/addStringRes/Class1_after.java @@ -5,6 +5,6 @@ import p1.p2.R; public class Class { public void f(Context context) { - String s = context.getResources().getString(R.string.hello); + String s = context.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class2_after.java b/plugins/android/testData/addStringRes/Class2_after.java index 9d15d2e96123..ed96dfac98e1 100644 --- a/plugins/android/testData/addStringRes/Class2_after.java +++ b/plugins/android/testData/addStringRes/Class2_after.java @@ -4,6 +4,6 @@ import p1.p2.R; public class Class { public void f() { - String s = .getResources().getString(R.string.hello); + String s = .getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class3_after.java b/plugins/android/testData/addStringRes/Class3_after.java index 91ccbec6b974..e673d41f2bc3 100644 --- a/plugins/android/testData/addStringRes/Class3_after.java +++ b/plugins/android/testData/addStringRes/Class3_after.java @@ -4,6 +4,6 @@ import android.content.Context; public class Class { public static void f(Context context) { - String s = context.getResources().getString(R.string.hello); + String s = context.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class4_after.java b/plugins/android/testData/addStringRes/Class4_after.java index 9103cc7e9188..bd29df958460 100644 --- a/plugins/android/testData/addStringRes/Class4_after.java +++ b/plugins/android/testData/addStringRes/Class4_after.java @@ -5,6 +5,6 @@ import p1.p2.R; public class Class extends Context { public void f() { - String s = getResources().getString(R.string.hello); + String s = getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class5_after.java b/plugins/android/testData/addStringRes/Class5_after.java index 1e236c780eb5..da79fab7bb17 100644 --- a/plugins/android/testData/addStringRes/Class5_after.java +++ b/plugins/android/testData/addStringRes/Class5_after.java @@ -5,6 +5,6 @@ import p1.p2.R; public class Class extends Context { public void f(Context context) { - String s = r.getString(R.string.hello); + String s = getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class6_after.java b/plugins/android/testData/addStringRes/Class6_after.java index b7c6406839cb..a93565254d49 100644 --- a/plugins/android/testData/addStringRes/Class6_after.java +++ b/plugins/android/testData/addStringRes/Class6_after.java @@ -4,6 +4,6 @@ import android.content.Context; public class Class extends Context { public static void f(Context context) { - String s = context.getResources().getString(R.string.hello); + String s = context.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class7_after.java b/plugins/android/testData/addStringRes/Class7_after.java index e86ce36b99d8..00b2c250ff40 100644 --- a/plugins/android/testData/addStringRes/Class7_after.java +++ b/plugins/android/testData/addStringRes/Class7_after.java @@ -5,6 +5,6 @@ import p1.p2.R; public class Class extends Context { public static void f() { - String s = c.getResources().getString(R.string.hello); + String s = c.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class8.java b/plugins/android/testData/addStringRes/Class8.java index 693f55baf78e..6fc5467ab4e8 100644 --- a/plugins/android/testData/addStringRes/Class8.java +++ b/plugins/android/testData/addStringRes/Class8.java @@ -4,7 +4,7 @@ import android.content.res.Resources; import android.content.Context; public class Class extends Context { - public static void f(Resources resources) { + public static void f(Context resources) { String s = "hello"; } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class8_after.java b/plugins/android/testData/addStringRes/Class8_after.java index 2e14b4212485..f6f6d1ce2364 100644 --- a/plugins/android/testData/addStringRes/Class8_after.java +++ b/plugins/android/testData/addStringRes/Class8_after.java @@ -4,7 +4,7 @@ import android.content.res.Resources; import android.content.Context; public class Class extends Context { - public static void f(Resources resources) { - String s = .getResources().getString(R.string.hello); + public static void f(Context resources) { + String s = resources.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class9.java b/plugins/android/testData/addStringRes/Class9.java index 1e6f044c4f45..47817692eb10 100644 --- a/plugins/android/testData/addStringRes/Class9.java +++ b/plugins/android/testData/addStringRes/Class9.java @@ -4,7 +4,7 @@ import android.content.res.Resources; import android.content.Context; public class Class extends Context { - public void f(Resources resources) { + public void f(Context context) { String s = "hello"; } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/Class9_after.java b/plugins/android/testData/addStringRes/Class9_after.java index 11dd486a73cd..f5529bd3b025 100644 --- a/plugins/android/testData/addStringRes/Class9_after.java +++ b/plugins/android/testData/addStringRes/Class9_after.java @@ -4,7 +4,7 @@ import android.content.res.Resources; import android.content.Context; public class Class extends Context { - public void f(Resources resources) { - String s = getResources().getString(R.string.hello); + public void f(Context context) { + String s = getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/ClassEscape_after.java b/plugins/android/testData/addStringRes/ClassEscape_after.java index 0658a8a53f82..f92cf49160a9 100644 --- a/plugins/android/testData/addStringRes/ClassEscape_after.java +++ b/plugins/android/testData/addStringRes/ClassEscape_after.java @@ -4,6 +4,6 @@ import android.content.Context; public class ClassEscape { public static void f(Context context) { - String s = context.getResources().getString(R.string.hello); + String s = context.getString(R.string.hello); } } \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/fromLayout.xml b/plugins/android/testData/addStringRes/fromLayout.xml new file mode 100644 index 000000000000..1b496cc10ad8 --- /dev/null +++ b/plugins/android/testData/addStringRes/fromLayout.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/fromLayout1.xml b/plugins/android/testData/addStringRes/fromLayout1.xml new file mode 100644 index 000000000000..f152f913e39d --- /dev/null +++ b/plugins/android/testData/addStringRes/fromLayout1.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/fromLayout_after.xml b/plugins/android/testData/addStringRes/fromLayout_after.xml new file mode 100644 index 000000000000..c074c981ecad --- /dev/null +++ b/plugins/android/testData/addStringRes/fromLayout_after.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/addStringRes/fromManifest.xml b/plugins/android/testData/addStringRes/fromManifest.xml new file mode 100644 index 000000000000..2997ac33cd72 --- /dev/null +++ b/plugins/android/testData/addStringRes/fromManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/plugins/android/testData/addStringRes/fromManifest1.xml b/plugins/android/testData/addStringRes/fromManifest1.xml new file mode 100644 index 000000000000..3b6843287cb9 --- /dev/null +++ b/plugins/android/testData/addStringRes/fromManifest1.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/plugins/android/testData/addStringRes/fromManifest1_after.xml b/plugins/android/testData/addStringRes/fromManifest1_after.xml new file mode 100644 index 000000000000..e7ed000835ae --- /dev/null +++ b/plugins/android/testData/addStringRes/fromManifest1_after.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/plugins/android/testData/addStringRes/fromManifest2.xml b/plugins/android/testData/addStringRes/fromManifest2.xml new file mode 100644 index 000000000000..1f1431a0ecb7 --- /dev/null +++ b/plugins/android/testData/addStringRes/fromManifest2.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/plugins/android/testData/lint/adapterViewChildren.xml b/plugins/android/testData/lint/adapterViewChildren.xml new file mode 100644 index 000000000000..9f92c86e6ecf --- /dev/null +++ b/plugins/android/testData/lint/adapterViewChildren.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/plugins/android/testData/lint/baselineWeights.xml b/plugins/android/testData/lint/baselineWeights.xml new file mode 100644 index 000000000000..a9bb155680e6 --- /dev/null +++ b/plugins/android/testData/lint/baselineWeights.xml @@ -0,0 +1,7 @@ + + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + + diff --git a/plugins/android/testData/lint/baselineWeights_after.xml b/plugins/android/testData/lint/baselineWeights_after.xml new file mode 100644 index 000000000000..65a523c3c9a8 --- /dev/null +++ b/plugins/android/testData/lint/baselineWeights_after.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/plugins/android/testData/lint/contentDescription.xml b/plugins/android/testData/lint/contentDescription.xml new file mode 100644 index 000000000000..f99ee50e6262 --- /dev/null +++ b/plugins/android/testData/lint/contentDescription.xml @@ -0,0 +1,11 @@ + + +geView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + /> + + diff --git a/plugins/android/testData/lint/contentDescription1.xml b/plugins/android/testData/lint/contentDescription1.xml new file mode 100644 index 000000000000..3966e0b9e16a --- /dev/null +++ b/plugins/android/testData/lint/contentDescription1.xml @@ -0,0 +1,11 @@ + + +android:contentDescription=""/> + + diff --git a/plugins/android/testData/lint/contentDescription_after.xml b/plugins/android/testData/lint/contentDescription_after.xml new file mode 100644 index 000000000000..f64caa929953 --- /dev/null +++ b/plugins/android/testData/lint/contentDescription_after.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/plugins/android/testData/lint/convertToDp.xml b/plugins/android/testData/lint/convertToDp.xml new file mode 100644 index 000000000000..6bc4c0dc8d8e --- /dev/null +++ b/plugins/android/testData/lint/convertToDp.xml @@ -0,0 +1,6 @@ + + + android:width="100px"/> + \ No newline at end of file diff --git a/plugins/android/testData/lint/convertToDp_after.xml b/plugins/android/testData/lint/convertToDp_after.xml new file mode 100644 index 000000000000..17c930637291 --- /dev/null +++ b/plugins/android/testData/lint/convertToDp_after.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/lint/duplicatedIds.xml b/plugins/android/testData/lint/duplicatedIds.xml new file mode 100644 index 000000000000..3f200a1b60bf --- /dev/null +++ b/plugins/android/testData/lint/duplicatedIds.xml @@ -0,0 +1,17 @@ + + + + android:id="@+id/id123" + /> + + diff --git a/plugins/android/testData/lint/editText.xml b/plugins/android/testData/lint/editText.xml new file mode 100644 index 000000000000..afd1ab54fc0d --- /dev/null +++ b/plugins/android/testData/lint/editText.xml @@ -0,0 +1,6 @@ + + + ayout_height="wrap_content"/> + \ No newline at end of file diff --git a/plugins/android/testData/lint/editText_after.xml b/plugins/android/testData/lint/editText_after.xml new file mode 100644 index 000000000000..f2c4ad49423f --- /dev/null +++ b/plugins/android/testData/lint/editText_after.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/lint/exportedService.xml b/plugins/android/testData/lint/exportedService.xml new file mode 100644 index 000000000000..b62fd1fc5b0a --- /dev/null +++ b/plugins/android/testData/lint/exportedService.xml @@ -0,0 +1,9 @@ + + + + android:exported="true"/> + + diff --git a/plugins/android/testData/lint/exportedService_after.xml b/plugins/android/testData/lint/exportedService_after.xml new file mode 100644 index 000000000000..014758a117f9 --- /dev/null +++ b/plugins/android/testData/lint/exportedService_after.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/plugins/android/testData/lint/hardcodedQuickfix.xml b/plugins/android/testData/lint/hardcodedQuickfix.xml new file mode 100644 index 000000000000..85cc775f1f5a --- /dev/null +++ b/plugins/android/testData/lint/hardcodedQuickfix.xml @@ -0,0 +1,12 @@ + + +android:text="Hello World, MyActivity" + /> + + diff --git a/plugins/android/testData/lint/hardcodedQuickfix1.xml b/plugins/android/testData/lint/hardcodedQuickfix1.xml new file mode 100644 index 000000000000..dc34d4a6e764 --- /dev/null +++ b/plugins/android/testData/lint/hardcodedQuickfix1.xml @@ -0,0 +1,12 @@ + + +android:text="Hello World, MyActivity" + /> + + diff --git a/plugins/android/testData/lint/hardcodedQuickfix1_after.xml b/plugins/android/testData/lint/hardcodedQuickfix1_after.xml new file mode 100644 index 000000000000..97da65a0b131 --- /dev/null +++ b/plugins/android/testData/lint/hardcodedQuickfix1_after.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/plugins/android/testData/lint/hardcodedQuickfix_after.xml b/plugins/android/testData/lint/hardcodedQuickfix_after.xml new file mode 100644 index 000000000000..97da65a0b131 --- /dev/null +++ b/plugins/android/testData/lint/hardcodedQuickfix_after.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/plugins/android/testData/lint/inefficientWeight.xml b/plugins/android/testData/lint/inefficientWeight.xml new file mode 100644 index 000000000000..65ebfc8958cf --- /dev/null +++ b/plugins/android/testData/lint/inefficientWeight.xml @@ -0,0 +1,12 @@ + + + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="10" + /> + + diff --git a/plugins/android/testData/lint/inefficientWeight_after.xml b/plugins/android/testData/lint/inefficientWeight_after.xml new file mode 100644 index 000000000000..ae8f06788487 --- /dev/null +++ b/plugins/android/testData/lint/inefficientWeight_after.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/plugins/android/testData/lint/missingPrefix.xml b/plugins/android/testData/lint/missingPrefix.xml new file mode 100644 index 000000000000..6934bcbf6f10 --- /dev/null +++ b/plugins/android/testData/lint/missingPrefix.xml @@ -0,0 +1,12 @@ + + + id="@+id/id123" + /> + + diff --git a/plugins/android/testData/lint/missingPrefix1.xml b/plugins/android/testData/lint/missingPrefix1.xml new file mode 100644 index 000000000000..3db882a6da7b --- /dev/null +++ b/plugins/android/testData/lint/missingPrefix1.xml @@ -0,0 +1,12 @@ + + + id="@+id/id123" + /> + + diff --git a/plugins/android/testData/lint/missingPrefix1_after.xml b/plugins/android/testData/lint/missingPrefix1_after.xml new file mode 100644 index 000000000000..6c736a2de094 --- /dev/null +++ b/plugins/android/testData/lint/missingPrefix1_after.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/plugins/android/testData/lint/missingPrefix_after.xml b/plugins/android/testData/lint/missingPrefix_after.xml new file mode 100644 index 000000000000..e007df753563 --- /dev/null +++ b/plugins/android/testData/lint/missingPrefix_after.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/plugins/android/testData/lint/obsoleteLayoutParams.xml b/plugins/android/testData/lint/obsoleteLayoutParams.xml new file mode 100644 index 000000000000..4c57fe6e7006 --- /dev/null +++ b/plugins/android/testData/lint/obsoleteLayoutParams.xml @@ -0,0 +1,6 @@ + + + android:layout_row="10"/> + \ No newline at end of file diff --git a/plugins/android/testData/lint/obsoleteLayoutParams_after.xml b/plugins/android/testData/lint/obsoleteLayoutParams_after.xml new file mode 100644 index 000000000000..1f0a1aed7c07 --- /dev/null +++ b/plugins/android/testData/lint/obsoleteLayoutParams_after.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/lint/scrollViewChildren.xml b/plugins/android/testData/lint/scrollViewChildren.xml new file mode 100644 index 000000000000..55660b7e40b5 --- /dev/null +++ b/plugins/android/testData/lint/scrollViewChildren.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/android/testData/lint/scrollViewSize.xml b/plugins/android/testData/lint/scrollViewSize.xml new file mode 100644 index 000000000000..a5ee7b2c3647 --- /dev/null +++ b/plugins/android/testData/lint/scrollViewSize.xml @@ -0,0 +1,8 @@ + + + + android:layout_height="fill_parent"/> + + \ No newline at end of file diff --git a/plugins/android/testData/lint/scrollViewSize_after.xml b/plugins/android/testData/lint/scrollViewSize_after.xml new file mode 100644 index 000000000000..eb95fc26ed31 --- /dev/null +++ b/plugins/android/testData/lint/scrollViewSize_after.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/plugins/android/testData/lint/typographyDashes.xml b/plugins/android/testData/lint/typographyDashes.xml new file mode 100644 index 000000000000..7b68d3f92e0e --- /dev/null +++ b/plugins/android/testData/lint/typographyDashes.xml @@ -0,0 +1,4 @@ + + + 10-20 + \ No newline at end of file diff --git a/plugins/android/testData/lint/typographyDashes_after.xml b/plugins/android/testData/lint/typographyDashes_after.xml new file mode 100644 index 000000000000..35404e3362b2 --- /dev/null +++ b/plugins/android/testData/lint/typographyDashes_after.xml @@ -0,0 +1,4 @@ + + + 10–20 + \ No newline at end of file diff --git a/plugins/android/testData/lint/typographyQuotes.xml b/plugins/android/testData/lint/typographyQuotes.xml new file mode 100644 index 000000000000..cfe2294a0685 --- /dev/null +++ b/plugins/android/testData/lint/typographyQuotes.xml @@ -0,0 +1,5 @@ + + + untitled303 + aba 'aba' + diff --git a/plugins/android/testData/lint/typographyQuotes_after.xml b/plugins/android/testData/lint/typographyQuotes_after.xml new file mode 100644 index 000000000000..f0539ff7ece6 --- /dev/null +++ b/plugins/android/testData/lint/typographyQuotes_after.xml @@ -0,0 +1,5 @@ + + + untitled303 + aba ‘aba’ + diff --git a/plugins/android/testData/lint/uselessLeaf.xml b/plugins/android/testData/lint/uselessLeaf.xml new file mode 100644 index 000000000000..ae483929b35d --- /dev/null +++ b/plugins/android/testData/lint/uselessLeaf.xml @@ -0,0 +1,6 @@ + + + h="wrap_content" android:layout_height="wrap_content"/> + \ No newline at end of file diff --git a/plugins/android/testData/lint/uselessLeaf_after.xml b/plugins/android/testData/lint/uselessLeaf_after.xml new file mode 100644 index 000000000000..68d85c5e154b --- /dev/null +++ b/plugins/android/testData/lint/uselessLeaf_after.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/plugins/android/testData/lint/uselessParent.xml b/plugins/android/testData/lint/uselessParent.xml new file mode 100644 index 000000000000..54a49cc510f5 --- /dev/null +++ b/plugins/android/testData/lint/uselessParent.xml @@ -0,0 +1,8 @@ + + + r android:layout_width="wrap_content" android:layout_height="wrap_content"> + + + \ No newline at end of file diff --git a/plugins/android/testSrc/org/jetbrains/android/AndroidLintTest.java b/plugins/android/testSrc/org/jetbrains/android/AndroidLintTest.java new file mode 100644 index 000000000000..186b291dd2b4 --- /dev/null +++ b/plugins/android/testSrc/org/jetbrains/android/AndroidLintTest.java @@ -0,0 +1,217 @@ +package org.jetbrains.android; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.android.inspections.lint.*; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidLintTest extends AndroidTestCase { + @NonNls private static final String BASE_PATH = "/lint/"; + + public AndroidLintTest() { + super(false); + } + + public void testHardcodedQuickfix() throws Exception { + doTestHardcodedQuickfix(); + } + + public void testHardcodedQuickfix1() throws Exception { + doTestHardcodedQuickfix(); + } + + private void doTestHardcodedQuickfix() throws IOException { + final IntentionAction action = doTestHighlightingAndGetQuickfix( + new AndroidLintInspectionToolProvider.AndroidLintHardcodedTextInspection(), + AndroidBundle.message("add.string.resource.intention.text"), false ? "AndroidManifest.xml" : "/res/layout/layout.xml", "xml"); + assertNotNull(action); + assertTrue(action.isAvailable(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile())); + + new WriteCommandAction(myFixture.getProject(), "") { + @Override + protected void run(Result result) throws Throwable { + ((AndroidAddStringResourceQuickFix)action) + .invokeIntention(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile(), "hello"); + } + }.execute(); + + myFixture.checkResultByFile(BASE_PATH + getTestName(true) + "_after.xml"); + } + + public void testContentDescription() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintContentDescriptionInspection(), + AndroidBundle.message("android.lint.inspections.add.content.description"), + "/res/layout/layout.xml", "xml"); + } + + public void testContentDescription1() throws Exception { + doTestNoFix(new AndroidLintInspectionToolProvider.AndroidLintContentDescriptionInspection(), + "/res/layout/layout.xml", "xml"); + } + + public void testAdapterViewChildren() throws Exception { + doTestNoFix(new AndroidLintInspectionToolProvider.AndroidLintAdapterViewChildrenInspection(), + "/res/layout/layout.xml", "xml"); + } + + public void testScrollViewChildren() throws Exception { + doTestNoFix(new AndroidLintInspectionToolProvider.AndroidLintScrollViewCountInspection(), + "/res/layout/layout.xml", "xml"); + } + + public void testMissingPrefix() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintMissingPrefixInspection(), + AndroidBundle.message("android.lint.inspections.add.android.prefix"), + "/res/layout/layout.xml", "xml"); + } + + public void testMissingPrefix1() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintMissingPrefixInspection(), + AndroidBundle.message("android.lint.inspections.add.android.prefix"), + "/res/layout/layout.xml", "xml"); + } + + public void testDuplicatedIds() throws Exception { + doTestNoFix(new AndroidLintInspectionToolProvider.AndroidLintDuplicateIdsInspection(), + "/res/layout/layout.xml", "xml"); + } + + public void testInefficientWeight() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintInefficientWeightInspection(), + AndroidBundle.message("android.lint.inspections.replace.with.zero.dp"), + "/res/layout/layout.xml", "xml"); + } + + public void testBaselineWeights() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintDisableBaselineAlignmentInspection(), + AndroidBundle.message("android.lint.inspections.set.baseline.attribute"), + "/res/layout/layout.xml", "xml"); + } + + public void testObsoleteLayoutParams() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintObsoleteLayoutParamInspection(), + AndroidBundle.message("android.lint.inspections.remove.attribute"), + "/res/layout/layout.xml", "xml"); + } + + public void testConvertToDp() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintPxUsageInspection(), + AndroidBundle.message("android.lint.inspections.convert.to.dp"), + "/res/layout/layout.xml", "xml"); + } + + public void testScrollViewSize() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintScrollViewSizeInspection(), + AndroidBundle.message("android.lint.inspections.set.to.wrap.content"), + "/res/layout/layout.xml", "xml"); + } + + public void testExportedService() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintExportedServiceInspection(), + AndroidBundle.message("android.lint.inspections.add.permission.attribute"), + "AndroidManifest.xml", "xml"); + } + + public void testEditText() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintTextFieldsInspection(), + AndroidBundle.message("android.lint.inspections.add.input.type.attribute"), + "/res/layout/layout.xml", "xml"); + } + + public void testUselessLeaf() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintUselessLeafInspection(), + AndroidBundle.message("android.lint.inspections.remove.unnecessary.view"), + "/res/layout/layout.xml", "xml"); + } + + public void testUselessParent() throws Exception { + doTestNoFix(new AndroidLintInspectionToolProvider.AndroidLintUselessParentInspection(), + "/res/layout/layout.xml", "xml"); + } + + public void testTypographyDashes() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintTypographyDashesInspection(), + AndroidBundle.message("android.lint.inspections.replace.with.suggested.characters"), + "/res/values/typography.xml", "xml"); + } + + public void testTypographyQuotes() throws Exception { + doTestWithFix(new AndroidLintInspectionToolProvider.AndroidLintTypographyQuotesInspection(), + AndroidBundle.message("android.lint.inspections.replace.with.suggested.characters"), + "/res/values/typography.xml", "xml"); + } + + private void doTestNoFix(@NotNull AndroidLintInspectionBase inspection, @NotNull String copyTo, @NotNull String extension) + throws IOException { + doTestHighlighting(inspection, copyTo, extension); + + IntentionAction action = null; + + for (IntentionAction a : myFixture.getAvailableIntentions()) { + if (a instanceof AndroidLintExternalAnnotator.MyFixingIntention) { + action = a; + } + } + assertNull(action); + } + + private void doTestWithFix(@NotNull AndroidLintInspectionBase inspection, + @NotNull String message, + @NotNull String copyTo, + @NotNull String extension) + throws IOException { + final IntentionAction action = doTestHighlightingAndGetQuickfix(inspection, message, copyTo, extension); + assertNotNull(action); + assertTrue(action.isAvailable(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile())); + + new WriteCommandAction(myFixture.getProject(), "") { + @Override + protected void run(Result result) throws Throwable { + action.invoke(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile()); + } + }.execute(); + + myFixture.checkResultByFile(BASE_PATH + getTestName(true) + "_after." + extension); + } + + @Nullable + private IntentionAction doTestHighlightingAndGetQuickfix(@NotNull AndroidLintInspectionBase inspection, + @NotNull String message, + @NotNull String copyTo, + @NotNull String extension) throws IOException { + doTestHighlighting(inspection, copyTo, extension); + + IntentionAction action = null; + + for (IntentionAction a : myFixture.getAvailableIntentions()) { + if (message.equals(a.getText())) { + action = a; + } + } + return action; + } + + private void doTestHighlighting(@NotNull AndroidLintInspectionBase inspection, @NotNull String copyTo, @NotNull String extension) + throws IOException { + if (!"AndroidManifest.xml".equals(copyTo)) { + createManifest(); + } + + myFixture.enableInspections(new GlobalInspectionToolWrapper(inspection)); + final VirtualFile file = myFixture.copyFileToProject(BASE_PATH + getTestName(true) + "." + extension, copyTo); + myFixture.configureFromExistingVirtualFile(file); + myFixture.doHighlighting(); + myFixture.checkHighlighting(true, false, false); + } +} diff --git a/plugins/android/testSrc/org/jetbrains/android/intentions/AndroidAddStringResourceActionTest.java b/plugins/android/testSrc/org/jetbrains/android/intentions/AndroidAddStringResourceActionTest.java index b58ff1cd2633..c2970ace69c0 100644 --- a/plugins/android/testSrc/org/jetbrains/android/intentions/AndroidAddStringResourceActionTest.java +++ b/plugins/android/testSrc/org/jetbrains/android/intentions/AndroidAddStringResourceActionTest.java @@ -11,12 +11,18 @@ import com.intellij.util.IncorrectOperationException; import org.jetbrains.android.AndroidTestCase; import org.jetbrains.annotations.Nullable; +import java.io.IOException; + /** * @author Eugene.Kudelevsky */ public class AndroidAddStringResourceActionTest extends AndroidTestCase { private static final String BASE_PATH = "addStringRes/"; + public AndroidAddStringResourceActionTest() { + super(false); + } + @Override public void setUp() throws Exception { super.setUp(); @@ -29,37 +35,31 @@ public class AndroidAddStringResourceActionTest extends AndroidTestCase { super.tearDown(); } - public void test1() { + public void test1() throws IOException { doTest(); } - public void test2() { + public void test2() throws IOException { doTest(); } - public void test3() { + public void test3() throws IOException { doTest(); } - public void test4() { + public void test4() throws IOException { doTest(); } - public void test5() { - doTest(new Runnable() { - @Override - public void run() { - myFixture.type('r'); - TemplateManagerImpl.getTemplateState(myFixture.getEditor()).nextTab(); - } - }); - } - - public void test6() { + public void test5() throws IOException { doTest(); } - public void test7() { + public void test6() throws IOException { + doTest(); + } + + public void test7() throws IOException { doTest(new Runnable() { @Override public void run() { @@ -69,24 +69,19 @@ public class AndroidAddStringResourceActionTest extends AndroidTestCase { }); } - public void test8() { - doTest(new Runnable() { - @Override - public void run() { - myFixture.performEditorAction(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM); - } - }); - } - - public void test9() { + public void test8() throws IOException { doTest(); } - public void test10() { + public void test9() throws IOException { doTest(); } - public void test11() { + public void test10() throws IOException { + doTest(); + } + + public void test11() throws IOException { doTest(new Runnable() { @Override public void run() { @@ -96,18 +91,12 @@ public class AndroidAddStringResourceActionTest extends AndroidTestCase { }); } - public void test12() { + public void test12() throws IOException { doTest(); } - public void test13() { - doTest(new Runnable() { - @Override - public void run() { - myFixture.type("r"); - myFixture.performEditorAction(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM); - } - }); + public void test13() throws IOException { + doTest(); } public void test14() { @@ -117,36 +106,68 @@ public class AndroidAddStringResourceActionTest extends AndroidTestCase { assertFalse(new AndroidAddStringResourceAction().isAvailable(myFixture.getProject(), myFixture.getEditor(), javaPsiFile)); } - public void testEscape() { + public void testEscape() throws IOException { doTest(getTestName(false), "strings.xml", null, true, "strings_escape_after.xml"); } - public void testNewFile() { + public void testNewFile() throws IOException { doTest("1", null, null, true); } - public void testInvalidStringsXml() { + public void testInvalidStringsXml() throws IOException { try { doTest("1", "strings_invalid.xml", null, true); fail(); } catch (IncorrectOperationException e) { // in normal mode error dialog will be shown - assertEquals("invalid strings.xml", e.getMessage()); + assertEquals("File strings.xml is not resource file", e.getMessage()); } myFixture.checkResultByFile(BASE_PATH + "Class1.java"); myFixture.checkResultByFile("res/values/strings.xml", BASE_PATH + "strings_invalid.xml", false); } - private void doTest() { + public void testFromLayout() throws IOException { + createManifest(); + final VirtualFile file = myFixture.copyFileToProject(BASE_PATH + getTestName(true) + ".xml", "res/layout/layout.xml"); + doExtractAndCheckStringsXml("strings.xml", null, true, "strings_after.xml", file); + myFixture.checkResultByFile(BASE_PATH + getTestName(true) + "_after.xml"); + } + + public void testFromLayout1() throws IOException { + createManifest(); + final VirtualFile file = myFixture.copyFileToProject(BASE_PATH + getTestName(true) + ".xml", "res/layout/layout.xml"); + myFixture.configureFromExistingVirtualFile(file); + assertFalse(new AndroidAddStringResourceAction().isAvailable(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile())); + } + + public void testFromManifest() { + final VirtualFile file = myFixture.copyFileToProject(BASE_PATH + getTestName(true) + ".xml", "AndroidManifest.xml"); + myFixture.configureFromExistingVirtualFile(file); + assertFalse(new AndroidAddStringResourceAction().isAvailable(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile())); + } + + public void testFromManifest1() { + final VirtualFile file = myFixture.copyFileToProject(BASE_PATH + getTestName(true) + ".xml", "AndroidManifest.xml"); + doExtractAndCheckStringsXml("strings.xml", null, true, "strings_after.xml", file); + myFixture.checkResultByFile(BASE_PATH + getTestName(true) + "_after.xml"); + } + + public void testFromManifest2() { + final VirtualFile file = myFixture.copyFileToProject(BASE_PATH + getTestName(true) + ".xml", "AndroidManifest.xml"); + myFixture.configureFromExistingVirtualFile(file); + assertFalse(new AndroidAddStringResourceAction().isAvailable(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile())); + } + + private void doTest() throws IOException { doTest(getTestName(false), "strings.xml", null, true); } - private void doTest(Runnable invokeAfterTemplate) { + private void doTest(Runnable invokeAfterTemplate) throws IOException { doTest(getTestName(false), "strings.xml", invokeAfterTemplate, false); } - private void doTest(String testName, String stringsXml, final Runnable invokeAfterTemplate, final boolean closePopup) { + private void doTest(String testName, String stringsXml, final Runnable invokeAfterTemplate, final boolean closePopup) throws IOException { doTest(testName, stringsXml, invokeAfterTemplate, closePopup, "strings_after.xml"); } @@ -154,12 +175,22 @@ public class AndroidAddStringResourceActionTest extends AndroidTestCase { String stringsXml, @Nullable final Runnable invokeAfterTemplate, final boolean closePopup, - String stringsAfter) { + String stringsAfter) throws IOException { + createManifest(); + VirtualFile javaFile = myFixture.copyFileToProject(BASE_PATH + "Class" + testName + ".java", "src/p1/p2/Class.java"); + doExtractAndCheckStringsXml(stringsXml, invokeAfterTemplate, closePopup, stringsAfter, javaFile); + myFixture.checkResultByFile(BASE_PATH + "Class" + testName + "_after.java"); + } + + private void doExtractAndCheckStringsXml(String stringsXml, + final Runnable invokeAfterTemplate, + final boolean closePopup, + String stringsAfter, + VirtualFile javaFile) { if (stringsXml != null) { myFixture.copyFileToProject(BASE_PATH + stringsXml, "res/values/strings.xml"); } myFixture.copyFileToProject(BASE_PATH + "R.java", "src/p1/p2/R.java"); - VirtualFile javaFile = myFixture.copyFileToProject(BASE_PATH + "Class" + testName + ".java", "src/p1/p2/Class.java"); myFixture.configureFromExistingVirtualFile(javaFile); final PsiFile javaPsiFile = myFixture.getFile(); assertTrue(new AndroidAddStringResourceAction().isAvailable(myFixture.getProject(), myFixture.getEditor(), javaPsiFile)); @@ -180,7 +211,6 @@ public class AndroidAddStringResourceActionTest extends AndroidTestCase { } } }, "", ""); - myFixture.checkResultByFile(BASE_PATH + "Class" + testName + "_after.java"); myFixture.checkResultByFile("res/values/strings.xml", BASE_PATH + stringsAfter, false); } }