
org.sonar.java.checks.StringPrimitiveConstructorCheck Maven / Gradle / Ivy
/*
* SonarQube Java
* Copyright (C) 2012-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.QuickFixHelper;
import org.sonar.java.model.LiteralUtils;
import org.sonar.java.reporting.JavaQuickFix;
import org.sonar.java.reporting.JavaTextEdit;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
@Rule(key = "S2129")
public class StringPrimitiveConstructorCheck extends IssuableSubscriptionVisitor {
private static final String QUICK_FIX_MESSAGE = "Replace this \"%s\" constructor with %s";
private static final String REPLACEMENT_MESSAGE = "the %s literal passed as parameter";
private static final String ISSUE_MESSAGE = "Remove this \"%s\" constructor";
private static final String STRING = "java.lang.String";
private static final BigInteger MIN_BIG_INTEGER_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
private static final BigInteger MAX_BIG_INTEGER_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
private static final MethodMatchers EMPTY_STRING_MATCHER = MethodMatchers.create().ofTypes(STRING).constructor().addWithoutParametersMatcher().build();
private static final MethodMatchers BIG_INT_MATCHER = primitiveConstructorMatcher("java.math.BigInteger", STRING);
private static final MethodMatchers matchers = MethodMatchers.or(
EMPTY_STRING_MATCHER,
BIG_INT_MATCHER,
primitiveConstructorMatcher(STRING, STRING),
primitiveConstructorMatcher("java.lang.Byte", "byte"),
primitiveConstructorMatcher("java.lang.Character", "char"),
primitiveConstructorMatcher("java.lang.Short", "short"),
primitiveConstructorMatcher("java.lang.Integer", "int"),
primitiveConstructorMatcher("java.lang.Long", "long"),
primitiveConstructorMatcher("java.lang.Float", "float"),
primitiveConstructorMatcher("java.lang.Double", "double"),
primitiveConstructorMatcher("java.lang.Boolean", "boolean"));
private static MethodMatchers primitiveConstructorMatcher(String constructor, String param) {
return MethodMatchers.create().ofTypes(constructor).constructor().addParametersMatcher(param).build();
}
private static final Map classToLiteral = Map.of(
"String", "string",
"Double", "double",
"Integer", "int",
"Boolean", "boolean",
"Byte", "byte",
"Character", "char",
"Short", "short",
"Long", "long",
"Float", "float"
);
@Override
public List nodesToVisit() {
return Collections.singletonList(Tree.Kind.NEW_CLASS);
}
@Override
public void visitNode(Tree tree) {
NewClassTree newClassTree = (NewClassTree) tree;
if (newClassTree.classBody() != null) {
return;
}
if (isBigIntegerPotentiallyBiggerThanLong(newClassTree)) {
return;
}
if(matchers.matches(newClassTree)) {
QuickFixHelper.newIssue(context)
.forRule(this)
.onTree(newClassTree.identifier())
.withMessage(ISSUE_MESSAGE, newClassTree.symbolType().name())
.withQuickFix(() -> computeQuickFix(newClassTree))
.report();
}
}
private static boolean isBigIntegerPotentiallyBiggerThanLong(NewClassTree newClassTree) {
if (!newClassTree.symbolType().is("java.math.BigInteger")) {
return false;
}
ExpressionTree argument = newClassTree.arguments().get(0);
if (!argument.is(Tree.Kind.STRING_LITERAL)) {
return true;
}
try {
BigInteger value = new BigInteger(LiteralUtils.trimQuotes(((LiteralTree)argument).value()));
return value.compareTo(MIN_BIG_INTEGER_VALUE) < 0 || value.compareTo(MAX_BIG_INTEGER_VALUE) > 0;
} catch (NumberFormatException e) {
return true;
}
}
private JavaQuickFix computeQuickFix(NewClassTree newClassTree) {
String message;
JavaTextEdit textEdit;
String className = newClassTree.symbolType().name();
if (EMPTY_STRING_MATCHER.matches(newClassTree)) {
message = formatQuickFixMessage(className, "an empty string \"\"");
textEdit = JavaTextEdit.replaceTree(newClassTree, "\"\"");
} else if (BIG_INT_MATCHER.matches(newClassTree)) {
String arg = getFirstArgumentAsString(newClassTree).replace("\"", "") + "L";
String replacement = String.format("BigInteger.valueOf(%s)", arg);
message = formatQuickFixMessage(className, "\"BigInteger.valueOf()\" static method");
textEdit = JavaTextEdit.replaceTree(newClassTree, replacement);
} else {
message = formatQuickFixMessage(className, String.format(REPLACEMENT_MESSAGE, classToLiteral.get(className)));
String replacement = getFirstArgumentAsString(newClassTree);
textEdit = JavaTextEdit.replaceTree(newClassTree, replacement);
}
return JavaQuickFix.newQuickFix(message).addTextEdit(textEdit).build();
}
private static String formatQuickFixMessage(String constructor, String replacement) {
return String.format(QUICK_FIX_MESSAGE, constructor, replacement);
}
private String getFirstArgumentAsString(NewClassTree newClassTree) {
ExpressionTree expr = newClassTree.arguments().get(0);
return QuickFixHelper.contentForTree(expr, context);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy