| package org.checkerframework.checker.i18nformatter; |
| |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.Tree; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.type.TypeMirror; |
| import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; |
| import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; |
| import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; |
| import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil.FormatType; |
| import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil.I18nFormatCall; |
| import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; |
| import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor; |
| import org.checkerframework.common.basetype.BaseTypeChecker; |
| import org.checkerframework.common.basetype.BaseTypeVisitor; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.TreeUtils; |
| |
| /** |
| * Whenever a method with {@link I18nFormatFor} annotation is invoked, it will perform the format |
| * string verification. |
| * |
| * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker |
| */ |
| public class I18nFormatterVisitor extends BaseTypeVisitor<I18nFormatterAnnotatedTypeFactory> { |
| |
| public I18nFormatterVisitor(BaseTypeChecker checker) { |
| super(checker); |
| } |
| |
| @Override |
| public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { |
| I18nFormatterTreeUtil tu = atypeFactory.treeUtil; |
| I18nFormatCall fc = tu.createFormatForCall(tree, atypeFactory); |
| if (fc != null) { |
| checkInvocationFormatFor(fc); |
| return p; |
| } else { |
| return super.visitMethodInvocation(tree, p); |
| } |
| } |
| |
| private void checkInvocationFormatFor(I18nFormatCall fc) { |
| I18nFormatterTreeUtil tu = atypeFactory.treeUtil; |
| Result<FormatType> type = fc.getFormatType(); |
| |
| switch (type.value()) { |
| case I18NINVALID: |
| tu.failure(type, "i18nformat.string", fc.getInvalidError()); |
| break; |
| case I18NFORMATFOR: |
| if (!fc.isValidFormatForInvocation()) { |
| Result<FormatType> failureType = fc.getInvalidInvocationType(); |
| tu.failure(failureType, "i18nformat.formatfor"); |
| } |
| break; |
| case I18NFORMAT: |
| Result<InvocationType> invc = fc.getInvocationType(); |
| I18nConversionCategory[] formatCats = fc.getFormatCategories(); |
| switch (invc.value()) { |
| case VARARG: |
| Result<TypeMirror>[] paramTypes = fc.getParamTypes(); |
| int paraml = paramTypes.length; |
| int formatl = formatCats.length; |
| |
| // For assignments, i18nformat.missing.arguments and i18nformat.excess.arguments are |
| // issued from commonAssignmentCheck. |
| if (paraml < formatl) { |
| tu.warning(invc, "i18nformat.missing.arguments", formatl, paraml); |
| } |
| if (paraml > formatl) { |
| tu.warning(invc, "i18nformat.excess.arguments", formatl, paraml); |
| } |
| for (int i = 0; i < formatl && i < paraml; ++i) { |
| I18nConversionCategory formatCat = formatCats[i]; |
| Result<TypeMirror> param = paramTypes[i]; |
| TypeMirror paramType = param.value(); |
| switch (formatCat) { |
| case UNUSED: |
| tu.warning(param, "i18nformat.argument.unused", " " + (1 + i)); |
| break; |
| case GENERAL: |
| break; |
| default: |
| if (!fc.isValidParameter(formatCat, paramType)) { |
| ExecutableElement method = TreeUtils.elementFromUse(fc.getTree()); |
| CharSequence methodName = ElementUtils.getSimpleNameOrDescription(method); |
| tu.failure( |
| param, |
| "argument", |
| "", // parameter name is not useful |
| methodName, |
| paramType, |
| formatCat); |
| } |
| } |
| } |
| break; |
| case NULLARRAY: |
| // fall-through |
| case ARRAY: |
| for (I18nConversionCategory cat : formatCats) { |
| if (cat == I18nConversionCategory.UNUSED) { |
| tu.warning(invc, "i18nformat.argument.unused", ""); |
| } |
| } |
| tu.warning(invc, "i18nformat.indirect.arguments"); |
| break; |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| @Override |
| protected void commonAssignmentCheck( |
| AnnotatedTypeMirror varType, |
| AnnotatedTypeMirror valueType, |
| Tree valueTree, |
| @CompilerMessageKey String errorKey, |
| Object... extraArgs) { |
| AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); |
| AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); |
| |
| // i18nformat.missing.arguments and i18nformat.excess.arguments are issued here for |
| // assignments. |
| // For method calls, they are issued in checkInvocationFormatFor. |
| if (rhs != null |
| && lhs != null |
| && AnnotationUtils.areSameByName(rhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME) |
| && AnnotationUtils.areSameByName(lhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME)) { |
| I18nConversionCategory[] rhsArgTypes = |
| atypeFactory.treeUtil.formatAnnotationToCategories(rhs); |
| I18nConversionCategory[] lhsArgTypes = |
| atypeFactory.treeUtil.formatAnnotationToCategories(lhs); |
| |
| if (rhsArgTypes.length < lhsArgTypes.length) { |
| // From the manual: |
| // It is legal to use a format string with fewer format specifiers |
| // than required, but a warning is issued. |
| checker.reportWarning( |
| valueTree, "i18nformat.missing.arguments", varType.toString(), valueType.toString()); |
| } else if (rhsArgTypes.length > lhsArgTypes.length) { |
| // Since it is known that too many conversion categories were provided, issue a more |
| // specific error message to that effect than assignment. |
| checker.reportError( |
| valueTree, "i18nformat.excess.arguments", varType.toString(), valueType.toString()); |
| } |
| } |
| |
| // By calling super.commonAssignmentCheck last, any i18nformat.excess.arguments message |
| // issued for a given line of code will take precedence over the |
| // assignment |
| // issued by super.commonAssignmentCheck. |
| super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); |
| } |
| } |