/** * Wire * Copyright (C) 2016 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.waz.lintrules.issues; import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Implementation; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Attr; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; public class LayoutXmlValueDetector extends LayoutDetector { public static final Issue ISSUE = Issue.create( "com.waz.LayoutXmlValue", "All values in the layout should be a separate XML value", "To make maintaining easier, all resource values should not be hardcoded", Category.CORRECTNESS, 4, Severity.WARNING, new Implementation(LayoutXmlValueDetector.class, Scope.RESOURCE_FILE_SCOPE)); private final static List<String> COLOR_ATTRIBUTES = Arrays.asList(SdkConstants.ATTR_COLOR, SdkConstants.ATTR_BACKGROUND, SdkConstants.ATTR_FOREGROUND, "textColorHint", "textColor"); private final static List<String> DIMEN_ATTRIBUTES = Arrays.asList(SdkConstants.ATTR_PADDING, SdkConstants.ATTR_PADDING_BOTTOM, SdkConstants.ATTR_PADDING_LEFT, SdkConstants.ATTR_PADDING_RIGHT, SdkConstants.ATTR_PADDING_TOP, SdkConstants.ATTR_PADDING_START, SdkConstants.ATTR_PADDING_END, SdkConstants.ATTR_TEXT_SIZE, SdkConstants.ATTR_LAYOUT_WIDTH, SdkConstants.ATTR_LAYOUT_HEIGHT, SdkConstants.ATTR_LAYOUT_MARGIN, SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM, SdkConstants.ATTR_LAYOUT_MARGIN_END, SdkConstants.ATTR_LAYOUT_MARGIN_LEFT, SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT, SdkConstants.ATTR_LAYOUT_MARGIN_START, SdkConstants.ATTR_LAYOUT_MARGIN_TOP, "alpha" ); public LayoutXmlValueDetector() { } @NonNull @Override public Speed getSpeed() { return Speed.FAST; } @Override public Collection<String> getApplicableAttributes() { final List<String> attributes = new ArrayList<>(COLOR_ATTRIBUTES); attributes.addAll(DIMEN_ATTRIBUTES); return attributes; } @Override public void visitAttribute(XmlContext context, Attr attribute) { String value = attribute.getValue(); if (value.length() <= 0 || ((value.charAt(0) == '@' || value.charAt(0) == '?'))) { return; } // Make sure this is really one of the android: attributes if (!SdkConstants.ANDROID_URI.equals(attribute.getNamespaceURI())) { return; } String attributeName = attribute.getName(); // clean out any prefix if (attributeName.contains(":")) { attributeName = attributeName.split(":")[1]; } // match_parent, wrap_content and 0dp are fine in width and height if (attributeName.equals(SdkConstants.ATTR_LAYOUT_WIDTH) || attributeName.equals(SdkConstants.ATTR_LAYOUT_HEIGHT)) { if (value.equals(SdkConstants.VALUE_MATCH_PARENT) || value.equals(SdkConstants.VALUE_WRAP_CONTENT) || value.equals(SdkConstants.VALUE_FILL_PARENT)) { return; } if (value.startsWith("0")) { return; } } if (COLOR_ATTRIBUTES.contains(attributeName)) { context.report(ISSUE, attribute, context.getLocation(attribute), String.format("Hardcoded color value \"%1$s\", should use @color resource", value)); } else if (DIMEN_ATTRIBUTES.contains(attributeName)) { context.report(ISSUE, attribute, context.getLocation(attribute), String.format("Hardcoded dimen value \"%1$s\", should use @dimen resource", value)); } else { context.report(ISSUE, attribute, context.getLocation(attribute), String.format("Hardcoded value \"%1$s\", should use @ resource", value)); } } }