/** * Check that the currentTimeMillis() came from the System package, if so raise an error. * * @param context * @param node * @param method */ @Override public void visitMethod(JavaContext context, UCallExpression node, PsiMethod method) { String message = "System.currentTimeMillis() should not be used as this can't be easily mocked and tested."; context.report(ISSUE, node, context.getLocation(node), message); } }
@Override public UElementHandler createUastHandler(final JavaContext context) { final JavaEvaluator evaluator = context.getEvaluator();
@Override public void visitAttribute(XmlContext xmlContext, Attr attr) { String fileName = xmlContext.file.getName(); if (!fileName.equals(ONLY_FILE_ALLOWED_TO_HAVE_HARDCODED_COLORS)) { String value = attr.getValue(); if (value.startsWith(HARDCODED_COLOR_PREFIX)) { xmlContext.report(ISSUE, attr, xmlContext.getLocation(attr), ISSUE.getBriefDescription(TextFormat.TEXT)); } } }
@Override public boolean visitMethodInvocation(MethodInvocation node) { Expression operand = node.astOperand(); if (node.astName().toString().equals("wait") && !context.isSuppressedWithComment(node, ISSUE)) { context.report(ISSUE, context.getLocation(node), "Don't wait on object. Use Timer's wait instead."); } return super.visitMethodInvocation(node); } };
@Override public void visitMethod(JavaContext javaContext, JavaElementVisitor visitor, PsiMethodCallExpression call, PsiMethod method) { PsiReferenceExpression methodExpression = call.getMethodExpression(); String fullyQualifiedMethodName = methodExpression.getQualifiedName(); if (fullyQualifiedMethodName.startsWith("android.util.Log.")) { javaContext.report(ISSUE, javaContext.getLocation(methodExpression), ISSUE.getBriefDescription(TextFormat.TEXT)); } }
Project project = context.getProject(); List<File> resourceFolder = project.getResourceFolders(); if (resourceFolder.isEmpty()) { return; if (line.contains("vector")) { context.report(ISSUE_JAVA_VECTOR_DRAWABLE, node, context.getLocation(node), expression.toString() + " 为 Vector Drawable,请使用 getVectorDrawable 方法获取,避免 4.0 及以下版本的系统产生 Crash");
private static void detectR2(JavaContext context, UElement node) { UFile sourceFile = context.getUastFile(); List<UClass> classes = sourceFile.getClasses(); if (!classes.isEmpty() && classes.get(0).getName() != null) { String qualifiedName = classes.get(0).getName(); if (qualifiedName.contains("_ViewBinder") || qualifiedName.contains("_ViewBinding") || qualifiedName.equals(R2)) { // skip generated files and R2 return; } } boolean isR2 = isR2Expression(node); if (isR2 && !context.isSuppressedWithComment(node, ISSUE)) { context.report(ISSUE, node, context.getLocation(node), LINT_ERROR_BODY); } }
@Override public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { // 判断资源文件夹是否存在 Project project = context.getProject(); List<File> resourceFolder = project.getResourceFolders(); if (resourceFolder.isEmpty()) { return; } // 获取项目资源文件夹路径 String resourcePath = resourceFolder.get(0).getAbsolutePath(); // 获取 drawable 名字 String drawableName = attribute.getValue().replace("@drawable/", ""); try { // 若 drawable 为 Vector Drawable,则文件后缀为 xml,根据 resource 路径,drawable 名字,文件后缀拼接出完整路径 FileInputStream fileInputStream = new FileInputStream(resourcePath + "/drawable/" + drawableName + ".xml"); BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream)); String line = reader.readLine(); if (line.contains("vector")) { // 若文件存在,并且包含首行包含 vector,则为 Vector Drawable,抛出警告 context.report(ISSUE_XML_VECTOR_DRAWABLE, attribute, context.getLocation(attribute), attribute.getValue() + " 为 Vector Drawable,请使用 Vector 属性进行设置,避免 4.0 及以下版本的系统产生 Crash"); } fileInputStream.close(); } catch (Exception ignored) { } } }
@Override public void visitAttribute(XmlContext context, Attr attribute) { if (attribute.getValue().contains(SdkConstants.ANDROID_COLOR_RESOURCE_PREFIX)) { context.report( ISSUE, attribute, context.getLocation(attribute), "Using Android color resources are not recommended. Manufacturers are overriding them with other colors."); } } }
int threePlotHeight = threePlotImage.getHeight(); if ((double) threePlotWidth / targetWidth != 1.5 || (double) threePlotHeight / targetHeight != 1.5) { Location fileLocation = Location.create(context.file); context.report(ISSUE_IMAGE_SCALE, fileLocation, "2倍图 " + filePath + " 与其3倍图宽高分别为 (" + targetWidth + ", " + targetHeight + ") 和 (" + threePlotWidth + ", " + threePlotHeight + "),不符合比例关系。");
private static boolean isR2Expression(UElement node) { UElement parentNode = node.getUastParent(); if (parentNode == null) { return false; } String text = node.asSourceString(); UElement parent = LintUtils.skipParentheses(parentNode); return (text.equals(R2) || text.contains(".R2")) && parent instanceof UExpression && endsWithAny(parent.asSourceString(), SUPPORTED_TYPES); }
@Override public boolean visitMethodInvocation(MethodInvocation node) { Expression operand = node.astOperand(); String methodName = node.astName().toString(); if (BAD_METHODS.contains(methodName) && !context.isSuppressedWithComment(node, ISSUE)) { context.report(ISSUE, context.getLocation(node), "Don't call " + methodName + " directly. Use" + " Timer instead."); } return super.visitMethodInvocation(node); } };
@Override public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) { VariableReference ref = (VariableReference) node.astOperand(); if (!"QMUILog".equals(ref.astIdentifier().astValue())) { return; } StrictListAccessor<Expression, MethodInvocation> args = node.astArguments(); if (args.isEmpty()) { return; } for (Expression expression : args) { String input = expression.toString(); if (input != null && input.contains("fuck")) { context.report( ISSUE_F_WORD, expression, context.getLocation(expression), "\uD83D\uDD95"); } } } }
@Override public void visitAttribute(XmlContext xmlContext, Attr attr) { String fileName = xmlContext.file.getName(); if (!fileName.equals(ALLOWED_COLORS_FILENAME)) { String value = attr.getValue(); if (value.startsWith(PALETTE_COLOR_PREFIX)) { xmlContext.report(ISSUE, attr, xmlContext.getLocation(attr), ISSUE.getBriefDescription(TextFormat.TEXT)); } } }
int height = targetImage.getHeight(); if (width % multiple != 0 || height % multiple != 0) { Location fileLocation = Location.create(context.file); context.report(ISSUE_IMAGE_SIZE, fileLocation, filePath + " 为" + trimZeroAndDot(multiple) + "倍图,其宽高应该是" + trimZeroAndDot(multiple) + "的倍数,目前宽高为 (" + width + ", " + height + ")。");
@Override public UElementHandler createUastHandler(final JavaContext context) { final JavaEvaluator evaluator = context.getEvaluator();
@Override public boolean visitMethodInvocation(MethodInvocation node) { Expression operand = node.astOperand(); if (node.astName().toString().equals("sleep") && operand.toString().equals("Thread") && !context.isSuppressedWithComment(node, ISSUE)) { context.report(ISSUE, node, context.getLocation(node), "Don't call sleep. Use MockTimer instead."); } return super.visitMethodInvocation(node); } };
context.report(ISSUE, node, context.getLocation((UElement) node), message); return; context.report(ISSUE, node, context.getLocation((UElement) node), message); return; String message = String.format( "This ControllerChangeHandler needs to have a public default constructor (`%1$s`)", node.getQualifiedName()); context.report(ISSUE, node, context.getLocation((UElement) node), message);
@Override public void visitElement(XmlContext xmlContext, Element element) { String fileName = xmlContext.file.getName(); if (fileName.equals(ALLOWED_COLORS_FILENAME)) { String elementValue = element.getFirstChild().getNodeValue(); if (xmlElementIsNotTheParentResourceTag(elementValue) && !elementValue.startsWith(PALETTE_COLOR_PREFIX)) { xmlContext.report(ISSUE, element, xmlContext.getLocation(element), ISSUE.getBriefDescription(TextFormat.TEXT)); } } }
@Override public boolean visitMethodInvocation(MethodInvocation node) { Expression operand = node.astOperand(); String methodName = node.astName().toString(); if (BAD_METHODS.contains(methodName) && operand.toString().equals("System") && !context.isSuppressedWithComment(node, ISSUE)) { context.report(ISSUE, context.getLocation(node), "Don't call " + methodName + " on system. Use" + " Timer instead."); } return super.visitMethodInvocation(node); } };