use of com.android.ide.common.resources.ResourceUrl in project kotlin by JetBrains.
the class ResourceEvaluator method getResource.
/**
* Evaluates the given node and returns the resource reference (type and name) it
* points to, if any
*
* @param element the node to compute the constant value for
* @return the corresponding constant value - a String, an Integer, a Float, and so on
*/
@Nullable
public ResourceUrl getResource(@Nullable PsiElement element) {
if (element == null) {
return null;
}
if (element instanceof PsiConditionalExpression) {
PsiConditionalExpression expression = (PsiConditionalExpression) element;
Object known = ConstantEvaluator.evaluate(null, expression.getCondition());
if (known == Boolean.TRUE && expression.getThenExpression() != null) {
return getResource(expression.getThenExpression());
} else if (known == Boolean.FALSE && expression.getElseExpression() != null) {
return getResource(expression.getElseExpression());
}
} else if (element instanceof PsiParenthesizedExpression) {
PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) element;
return getResource(parenthesizedExpression.getExpression());
} else if (element instanceof PsiMethodCallExpression && mAllowDereference) {
PsiMethodCallExpression call = (PsiMethodCallExpression) element;
PsiReferenceExpression expression = call.getMethodExpression();
PsiMethod method = call.resolveMethod();
if (method != null && method.getContainingClass() != null) {
String qualifiedName = method.getContainingClass().getQualifiedName();
String name = expression.getReferenceName();
if ((CLASS_RESOURCES.equals(qualifiedName) || CLASS_CONTEXT.equals(qualifiedName) || CLASS_FRAGMENT.equals(qualifiedName) || CLASS_V4_FRAGMENT.equals(qualifiedName) || CLS_TYPED_ARRAY.equals(qualifiedName)) && name != null && name.startsWith("get")) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length > 0) {
return getResource(args[0]);
}
}
}
} else if (element instanceof PsiReference) {
ResourceUrl url = getResourceConstant(element);
if (url != null) {
return url;
}
PsiElement resolved = ((PsiReference) element).resolve();
if (resolved instanceof PsiField) {
url = getResourceConstant(resolved);
if (url != null) {
return url;
}
PsiField field = (PsiField) resolved;
if (field.getInitializer() != null) {
return getResource(field.getInitializer());
}
return null;
} else if (resolved instanceof PsiLocalVariable) {
PsiLocalVariable variable = (PsiLocalVariable) resolved;
PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class, false);
if (statement != null) {
PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, PsiStatement.class);
String targetName = variable.getName();
if (targetName == null) {
return null;
}
while (prev != null) {
if (prev instanceof PsiDeclarationStatement) {
PsiDeclarationStatement prevStatement = (PsiDeclarationStatement) prev;
for (PsiElement e : prevStatement.getDeclaredElements()) {
if (variable.equals(e)) {
return getResource(variable.getInitializer());
}
}
} else if (prev instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement) prev).getExpression();
if (expression instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assign = (PsiAssignmentExpression) expression;
PsiExpression lhs = assign.getLExpression();
if (lhs instanceof PsiReferenceExpression) {
PsiReferenceExpression reference = (PsiReferenceExpression) lhs;
if (targetName.equals(reference.getReferenceName()) && reference.getQualifier() == null) {
return getResource(assign.getRExpression());
}
}
}
}
prev = PsiTreeUtil.getPrevSiblingOfType(prev, PsiStatement.class);
}
}
}
}
return null;
}
use of com.android.ide.common.resources.ResourceUrl in project kotlin by JetBrains.
the class ResourceEvaluator method getResourceTypes.
/**
* Evaluates the given node and returns the resource types applicable to the
* node, if any.
*
* @param element the element to compute the types for
* @return the corresponding resource types
*/
@Nullable
public EnumSet<ResourceType> getResourceTypes(@Nullable PsiElement element) {
if (element == null) {
return null;
}
if (element instanceof PsiConditionalExpression) {
PsiConditionalExpression expression = (PsiConditionalExpression) element;
Object known = ConstantEvaluator.evaluate(null, expression.getCondition());
if (known == Boolean.TRUE && expression.getThenExpression() != null) {
return getResourceTypes(expression.getThenExpression());
} else if (known == Boolean.FALSE && expression.getElseExpression() != null) {
return getResourceTypes(expression.getElseExpression());
} else {
EnumSet<ResourceType> left = getResourceTypes(expression.getThenExpression());
EnumSet<ResourceType> right = getResourceTypes(expression.getElseExpression());
if (left == null) {
return right;
} else if (right == null) {
return left;
} else {
EnumSet<ResourceType> copy = EnumSet.copyOf(left);
copy.addAll(right);
return copy;
}
}
} else if (element instanceof PsiParenthesizedExpression) {
PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) element;
return getResourceTypes(parenthesizedExpression.getExpression());
} else if (element instanceof PsiMethodCallExpression && mAllowDereference) {
PsiMethodCallExpression call = (PsiMethodCallExpression) element;
PsiReferenceExpression expression = call.getMethodExpression();
PsiMethod method = call.resolveMethod();
if (method != null && method.getContainingClass() != null) {
EnumSet<ResourceType> types = getTypesFromAnnotations(method);
if (types != null) {
return types;
}
String qualifiedName = method.getContainingClass().getQualifiedName();
String name = expression.getReferenceName();
if ((CLASS_RESOURCES.equals(qualifiedName) || CLASS_CONTEXT.equals(qualifiedName) || CLASS_FRAGMENT.equals(qualifiedName) || CLASS_V4_FRAGMENT.equals(qualifiedName) || CLS_TYPED_ARRAY.equals(qualifiedName)) && name != null && name.startsWith("get")) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length > 0) {
types = getResourceTypes(args[0]);
if (types != null) {
return types;
}
}
}
}
} else if (element instanceof PsiReference) {
ResourceUrl url = getResourceConstant(element);
if (url != null) {
return EnumSet.of(url.type);
}
PsiElement resolved = ((PsiReference) element).resolve();
if (resolved instanceof PsiField) {
url = getResourceConstant(resolved);
if (url != null) {
return EnumSet.of(url.type);
}
PsiField field = (PsiField) resolved;
if (field.getInitializer() != null) {
return getResourceTypes(field.getInitializer());
}
return null;
} else if (resolved instanceof PsiParameter) {
return getTypesFromAnnotations((PsiParameter) resolved);
} else if (resolved instanceof PsiLocalVariable) {
PsiLocalVariable variable = (PsiLocalVariable) resolved;
PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class, false);
if (statement != null) {
PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, PsiStatement.class);
String targetName = variable.getName();
if (targetName == null) {
return null;
}
while (prev != null) {
if (prev instanceof PsiDeclarationStatement) {
PsiDeclarationStatement prevStatement = (PsiDeclarationStatement) prev;
for (PsiElement e : prevStatement.getDeclaredElements()) {
if (variable.equals(e)) {
return getResourceTypes(variable.getInitializer());
}
}
} else if (prev instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement) prev).getExpression();
if (expression instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assign = (PsiAssignmentExpression) expression;
PsiExpression lhs = assign.getLExpression();
if (lhs instanceof PsiReferenceExpression) {
PsiReferenceExpression reference = (PsiReferenceExpression) lhs;
if (targetName.equals(reference.getReferenceName()) && reference.getQualifier() == null) {
return getResourceTypes(assign.getRExpression());
}
}
}
}
prev = PsiTreeUtil.getPrevSiblingOfType(prev, PsiStatement.class);
}
}
}
}
return null;
}
use of com.android.ide.common.resources.ResourceUrl in project kotlin by JetBrains.
the class LintUtils method getStyleAttributes.
/**
* Looks up the resource values for the given attribute given a style. Note that
* this only looks project-level style values, it does not resume into the framework
* styles.
*/
@Nullable
public static List<ResourceValue> getStyleAttributes(@NonNull Project project, @NonNull LintClient client, @NonNull String styleUrl, @NonNull String namespace, @NonNull String attribute) {
if (!client.supportsProjectResources()) {
return null;
}
AbstractResourceRepository resources = client.getProjectResources(project, true);
if (resources == null) {
return null;
}
ResourceUrl style = ResourceUrl.parse(styleUrl);
if (style == null || style.framework) {
return null;
}
List<ResourceValue> result = null;
Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>();
queue.add(new ResourceValue(style.type, style.name, false));
Set<String> seen = Sets.newHashSet();
int count = 0;
boolean isFrameworkAttribute = ANDROID_URI.equals(namespace);
while (count < 30 && !queue.isEmpty()) {
ResourceValue front = queue.remove();
String name = front.getName();
seen.add(name);
List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name);
if (items != null) {
for (ResourceItem item : items) {
ResourceValue rv = item.getResourceValue(false);
if (rv instanceof StyleResourceValue) {
StyleResourceValue srv = (StyleResourceValue) rv;
ItemResourceValue value = srv.getItem(attribute, isFrameworkAttribute);
if (value != null) {
if (result == null) {
result = Lists.newArrayList();
}
if (!result.contains(value)) {
result.add(value);
}
}
String parent = srv.getParentStyle();
if (parent != null && !parent.startsWith(ANDROID_PREFIX)) {
ResourceUrl p = ResourceUrl.parse(parent);
if (p != null && !p.framework && !seen.contains(p.name)) {
seen.add(p.name);
queue.add(new ResourceValue(ResourceType.STYLE, p.name, false));
}
}
int index = name.lastIndexOf('.');
if (index > 0) {
String parentName = name.substring(0, index);
if (!seen.contains(parentName)) {
seen.add(parentName);
queue.add(new ResourceValue(ResourceType.STYLE, parentName, false));
}
}
}
}
}
count++;
}
return result;
}
use of com.android.ide.common.resources.ResourceUrl in project kotlin by JetBrains.
the class StringFormatDetector method checkStringFormatCall.
/**
* Check the given String.format call (with the given arguments) to see if the string format is
* being used correctly
* @param context the context to report errors to
* @param calledMethod the method being called
* @param call the AST node for the {@link String#format}
* @param specifiesLocale whether the first parameter is a locale string, shifting the
*/
private void checkStringFormatCall(JavaContext context, PsiMethod calledMethod, UCallExpression call, boolean specifiesLocale) {
int argIndex = specifiesLocale ? 1 : 0;
List<UExpression> args = call.getValueArguments();
if (args.size() <= argIndex) {
return;
}
UExpression argument = args.get(argIndex);
ResourceUrl resource = ResourceEvaluator.getResource(context, argument);
if (resource == null || resource.framework || resource.type != ResourceType.STRING) {
return;
}
String name = resource.name;
if (mIgnoreStrings != null && mIgnoreStrings.contains(name)) {
return;
}
boolean passingVarArgsArray = false;
int callCount = args.size() - 1 - argIndex;
if (callCount == 1) {
// If instead of a varargs call like
// getString(R.string.foo, arg1, arg2, arg3)
// the code is calling the varargs method with a packed Object array, as in
// getString(R.string.foo, new Object[] { arg1, arg2, arg3 })
// we'll need to handle that such that we don't think this is a single
// argument
UExpression lastArg = args.get(args.size() - 1);
PsiParameterList parameterList = calledMethod.getParameterList();
int parameterCount = parameterList.getParametersCount();
if (parameterCount > 0 && parameterList.getParameters()[parameterCount - 1].isVarArgs()) {
boolean knownArity = false;
boolean argWasReference = false;
if (lastArg instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) lastArg).resolve();
if (resolved instanceof PsiVariable) {
UExpression initializer = context.getUastContext().getInitializerBody((PsiVariable) resolved);
if (initializer != null && (UastExpressionUtils.isNewArray(initializer) || UastExpressionUtils.isArrayInitializer(initializer))) {
argWasReference = true;
// Now handled by check below
lastArg = initializer;
}
}
}
if (UastExpressionUtils.isNewArray(lastArg) || UastExpressionUtils.isArrayInitializer(lastArg)) {
UCallExpression arrayInitializer = (UCallExpression) lastArg;
if (UastExpressionUtils.isNewArrayWithInitializer(lastArg) || UastExpressionUtils.isArrayInitializer(lastArg)) {
callCount = arrayInitializer.getValueArgumentCount();
knownArity = true;
} else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) {
List<UExpression> arrayDimensions = arrayInitializer.getValueArguments();
if (arrayDimensions.size() == 1) {
UExpression first = arrayDimensions.get(0);
if (first instanceof ULiteralExpression) {
Object o = ((ULiteralExpression) first).getValue();
if (o instanceof Integer) {
callCount = (Integer) o;
knownArity = true;
}
}
}
}
if (!knownArity) {
if (!argWasReference) {
return;
}
} else {
passingVarArgsArray = true;
}
}
}
}
if (callCount > 0 && mNotFormatStrings.containsKey(name)) {
checkNotFormattedHandle(context, call, name, mNotFormatStrings.get(name));
return;
}
List<Pair<Handle, String>> list = mFormatStrings != null ? mFormatStrings.get(name) : null;
if (list == null) {
LintClient client = context.getClient();
if (client.supportsProjectResources() && !context.getScope().contains(Scope.RESOURCE_FILE)) {
AbstractResourceRepository resources = client.getProjectResources(context.getMainProject(), true);
List<ResourceItem> items;
if (resources != null) {
items = resources.getResourceItem(ResourceType.STRING, name);
} else {
// Must be a non-Android module
items = null;
}
if (items != null) {
for (final ResourceItem item : items) {
ResourceValue v = item.getResourceValue(false);
if (v != null) {
String value = v.getRawXmlValue();
if (value != null) {
// Make sure it's really a formatting string,
// not for example "Battery remaining: 90%"
boolean isFormattingString = value.indexOf('%') != -1;
for (int j = 0, m = value.length(); j < m && isFormattingString; j++) {
char c = value.charAt(j);
if (c == '\\') {
j++;
} else if (c == '%') {
Matcher matcher = FORMAT.matcher(value);
if (!matcher.find(j)) {
isFormattingString = false;
} else {
String conversion = matcher.group(6);
int conversionClass = getConversionClass(conversion.charAt(0));
if (conversionClass == CONVERSION_CLASS_UNKNOWN || matcher.group(5) != null) {
// Some date format etc - don't process
return;
}
}
// Don't process second % in a %%
j++;
}
// If the user marked the string with
}
Handle handle = client.createResourceItemHandle(item);
if (isFormattingString) {
if (list == null) {
list = Lists.newArrayList();
if (mFormatStrings == null) {
mFormatStrings = Maps.newHashMap();
}
mFormatStrings.put(name, list);
}
list.add(Pair.of(handle, value));
} else if (callCount > 0) {
checkNotFormattedHandle(context, call, name, handle);
}
}
}
}
}
} else {
return;
}
}
if (list != null) {
Set<String> reported = null;
for (Pair<Handle, String> pair : list) {
String s = pair.getSecond();
if (reported != null && reported.contains(s)) {
continue;
}
int count = getFormatArgumentCount(s, null);
Handle handle = pair.getFirst();
if (count != callCount) {
Location location = context.getUastLocation(call);
Location secondary = handle.resolve();
secondary.setMessage(String.format("This definition requires %1$d arguments", count));
location.setSecondary(secondary);
String message = String.format("Wrong argument count, format string `%1$s` requires `%2$d` but format " + "call supplies `%3$d`", name, count, callCount);
context.report(ARG_TYPES, call, location, message);
if (reported == null) {
reported = Sets.newHashSet();
}
reported.add(s);
} else {
if (passingVarArgsArray) {
// flag parameters on the Object[] instead of the wrapped parameters
return;
}
for (int i = 1; i <= count; i++) {
int argumentIndex = i + argIndex;
PsiType type = args.get(argumentIndex).getExpressionType();
if (type != null) {
boolean valid = true;
String formatType = getFormatArgumentType(s, i);
if (formatType == null) {
continue;
}
char last = formatType.charAt(formatType.length() - 1);
if (formatType.length() >= 2 && Character.toLowerCase(formatType.charAt(formatType.length() - 2)) == 't') {
// TODO
continue;
}
switch(last) {
// unusual and probably not intended.
case 'b':
case 'B':
valid = isBooleanType(type);
break;
// Numeric: integer and floats in various formats
case 'x':
case 'X':
case 'd':
case 'o':
case 'e':
case 'E':
case 'f':
case 'g':
case 'G':
case 'a':
case 'A':
valid = isNumericType(type, true);
break;
case 'c':
case 'C':
// Unicode character
valid = isCharacterType(type);
break;
case 'h':
// Hex print of hash code of objects
case 'H':
case 's':
case 'S':
// String. Can pass anything, but warn about
// numbers since you may have meant more
// specific formatting. Use special issue
// explanation for this?
valid = !isBooleanType(type) && !isNumericType(type, false);
break;
}
if (!valid) {
Location location = context.getUastLocation(args.get(argumentIndex));
Location secondary = handle.resolve();
secondary.setMessage("Conflicting argument declaration here");
location.setSecondary(secondary);
String suggestion = null;
if (isBooleanType(type)) {
suggestion = "`b`";
} else if (isCharacterType(type)) {
suggestion = "'c'";
} else if (PsiType.INT.equals(type) || PsiType.LONG.equals(type) || PsiType.BYTE.equals(type) || PsiType.SHORT.equals(type)) {
suggestion = "`d`, 'o' or `x`";
} else if (PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) {
suggestion = "`e`, 'f', 'g' or `a`";
} else if (type instanceof PsiClassType) {
String fqn = type.getCanonicalText();
if (TYPE_INTEGER_WRAPPER.equals(fqn) || TYPE_LONG_WRAPPER.equals(fqn) || TYPE_BYTE_WRAPPER.equals(fqn) || TYPE_SHORT_WRAPPER.equals(fqn)) {
suggestion = "`d`, 'o' or `x`";
} else if (TYPE_FLOAT_WRAPPER.equals(fqn) || TYPE_DOUBLE_WRAPPER.equals(fqn)) {
suggestion = "`d`, 'o' or `x`";
} else if (TYPE_OBJECT.equals(fqn)) {
suggestion = "'s' or 'h'";
}
}
if (suggestion != null) {
suggestion = " (Did you mean formatting character " + suggestion + "?)";
} else {
suggestion = "";
}
String canonicalText = type.getCanonicalText();
canonicalText = canonicalText.substring(canonicalText.lastIndexOf('.') + 1);
String message = String.format("Wrong argument type for formatting argument '#%1$d' " + "in `%2$s`: conversion is '`%3$s`', received `%4$s` " + "(argument #%5$d in method call)%6$s", i, name, formatType, canonicalText, argumentIndex + 1, suggestion);
context.report(ARG_TYPES, call, location, message);
if (reported == null) {
reported = Sets.newHashSet();
}
reported.add(s);
}
}
}
}
}
}
}
use of com.android.ide.common.resources.ResourceUrl in project kotlin by JetBrains.
the class PrivateResourceDetector method visitAttribute.
/** Check resource references: accessing a private resource from an upstream library? */
@Override
public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
String value = attribute.getNodeValue();
if (context.getProject().isGradleProject()) {
ResourceUrl url = ResourceUrl.parse(value);
if (isPrivate(context, url)) {
String message = createUsageErrorMessage(context, url.type, url.name);
context.report(ISSUE, attribute, context.getValueLocation(attribute), message);
}
}
}
Aggregations