Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ruleKey": "S8695",
"hasTruePositives": true,
"falseNegatives": 0,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package checks;

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;

public class SimpleTemporalInstantiationCheckSample {

static Temporal[] method(Instant instant, ZoneId zoneId, Clock clock) {
return new Temporal[]{

LocalDate.now(), // Compliant
LocalDate.from(Instant.now()), // Noncompliant
LocalDate.from(LocalDate.now()), // Noncompliant [[quickfixes=qf0]]
// ^^^^
// fix@qf0 {{Replace with now()}}
// edit@qf0 [[sc=17;ec=38]] {{now()}}
LocalDate.from(LocalTime.now()), // Noncompliant
LocalDate.from(YearMonth.now()), // Noncompliant
LocalDate.from(Year.now()), // Noncompliant


LocalDate.from(Instant.now(clock)), // Noncompliant
LocalDate.from(ZonedDateTime.now(clock)), // Noncompliant
LocalDate.from(OffsetDateTime.now(clock)), // Noncompliant
LocalDate.from(clock.instant()), // Compliant, limitation of the rule
LocalDate.now(clock), // Compliant
LocalTime.now(clock), // Compliant
YearMonth.now(clock), // Compliant
Year.now(clock), // Compliant

LocalDate.from(LocalDate.now(clock)), // Noncompliant [[quickfixes=qf1]]
// ^^^^
// fix@qf1 {{Replace with now(clock)}}
// edit@qf1 [[sc=17;ec=43]] {{now(clock)}}
LocalDate.from(LocalTime.now(clock)), // Noncompliant
LocalDate.from(YearMonth.now(clock)), // Noncompliant
LocalDate.from(Year.now(clock)), // Noncompliant

LocalDate.from(instant), // Compliant
LocalDate.from(instant.atZone(ZoneId.of("UTC"))), // Compliant
LocalDate.from(zonedDateTime()), // Compliant

LocalDate.now(), // Compliant
LocalDate.from(LocalDate.now()), // Noncompliant {{Replace with "now()".}}

LocalDate.now(ZoneId.of("UTC")), // Compliant
LocalDate.from(Instant.now().atZone(ZoneId.of("UTC"))), // Noncompliant {{Replace with "now(ZoneId.of("UTC"))".}} [[quickfixes=qf2]]
// ^^^^
// fix@qf2 {{Replace with now(ZoneId.of("UTC"))}}
// edit@qf2 [[sc=17;ec=61]] {{now(ZoneId.of("UTC"))}}

LocalDate.now(ZoneId.of("UTC")), // Compliant
LocalDate.from(ZonedDateTime.now(zoneId)), // Noncompliant {{Replace with "now(zoneId)".}} [[quickfixes=qf3]]
// ^^^^
// fix@qf3 {{Replace with now(zoneId)}}
// edit@qf3 [[sc=17;ec=48]] {{now(zoneId)}}

LocalDate.now(ZoneId.of("UTC")), // Compliant
LocalDate.from(OffsetDateTime.now(ZoneId.of("Asia/Tokyo"))), // Noncompliant

LocalTime.now(), // Compliant
LocalTime.from(Instant.now()), // Noncompliant

LocalTime.now(), // Compliant
LocalTime.from(LocalTime.now()), // Noncompliant

LocalTime.now(zoneId), // Compliant
LocalTime.from(LocalTime.now(zoneId)), // Noncompliant

LocalTime.now(ZoneId.of("UTC")), // Compliant
LocalTime.from(Instant.now().atZone(ZoneId.of("UTC"))), // Noncompliant

LocalTime.now(ZoneId.of("UTC")), // Compliant
LocalTime.from(ZonedDateTime.now(ZoneId.of("UTC"))), // Noncompliant

LocalTime.now(ZoneId.of("UTC")), // Compliant
LocalTime.from(OffsetDateTime.now(ZoneId.of("UTC"))), // Noncompliant

YearMonth.now(), // Compliant
YearMonth.from(Instant.now()), // Noncompliant

YearMonth.now(), // Compliant
YearMonth.from(YearMonth.now()), // Noncompliant

YearMonth.now(ZoneId.of("UTC")), // Compliant
YearMonth.from(Instant.now().atZone(ZoneId.of("UTC"))), // Noncompliant

YearMonth.now(ZoneId.of("UTC")), // Compliant
YearMonth.from(ZonedDateTime.now(ZoneId.of("UTC"))), // Noncompliant

YearMonth.now(ZoneId.of("UTC")), // Compliant
YearMonth.from(OffsetDateTime.now(ZoneId.of("UTC"))), // Noncompliant

YearMonth.now(ZoneId.of("UTC")), // Compliant
YearMonth.from(LocalDate.now(ZoneId.of("UTC"))), // Noncompliant

Year.now(), // Compliant
Year.from(Instant.now()), // Noncompliant

Year.now(), // Compliant
Year.from(Year.now()), // Noncompliant

Year.now(zoneId), // Compliant
Year.from(Year.now(zoneId)), // Noncompliant

Year.now(ZoneId.of("UTC")), // Compliant
Year.from(Instant.now().atZone(ZoneId.of("UTC"))), // Noncompliant

Year.now(ZoneId.of("UTC")), // Compliant
Year.from(ZonedDateTime.now(ZoneId.of("UTC"))), // Noncompliant

Year.now(ZoneId.of("UTC")), // Compliant
Year.from(OffsetDateTime.now(ZoneId.of("UTC"))), // Noncompliant

Year.now(ZoneId.of("UTC")), // Compliant
Year.from(LocalDate.now(ZoneId.of("UTC"))), // Noncompliant

Year.now(ZoneId.of("UTC")), // Compliant
Year.from(YearMonth.now(ZoneId.of("UTC"))) // Noncompliant
};
}

public static ZonedDateTime zonedDateTime() {
return ZonedDateTime.now(ZoneId.of("UTC"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* 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 org.sonar.check.Rule;
import org.sonar.java.checks.helpers.QuickFixHelper;
import org.sonar.java.checks.methods.AbstractMethodDetection;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.reporting.JavaQuickFix;
import org.sonar.java.reporting.JavaTextEdit;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.Arguments;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;

import static org.sonar.java.reporting.AnalyzerMessage.textSpanBetween;

@Rule(key = "S8695")
public class SimpleTemporalInstantiationCheck extends AbstractMethodDetection {

private static final MethodMatchers NOW_METHODS = MethodMatchers.create()
.ofTypes("java.time.ZonedDateTime", "java.time.OffsetDateTime", "java.time.LocalDate", "java.time.YearMonth", "java.time.LocalTime", "java.time.Year")
.names("now")
.addParametersMatcher("java.time.ZoneId")
.addParametersMatcher("java.time.Clock")
.addWithoutParametersMatcher()
.build();

private static final MethodMatchers AT_ZONE_METHOD = MethodMatchers.create()
.ofTypes("java.time.Instant")
.names("atZone")
.addParametersMatcher("java.time.ZoneId")
.build();

private static final MethodMatchers INSTANT_NOW = MethodMatchers.create()
.ofTypes("java.time.Instant")
.names("now")
.addWithoutParametersMatcher()
.addParametersMatcher("java.time.Clock")
.build();

@Override
protected MethodMatchers getMethodInvocationMatchers() {
return MethodMatchers.create()
.ofTypes("java.time.LocalDate", "java.time.LocalTime", "java.time.YearMonth", "java.time.Year")
.names("from")
.addParametersMatcher("java.time.temporal.TemporalAccessor")
.build();
}

@Override
protected void onMethodInvocationFound(MethodInvocationTree mit) {
Arguments arguments = mit.arguments();
if (arguments.size() == 1 && arguments.get(0) instanceof MethodInvocationTree method && isNonCompliantMethod(method)) {
String argument = method.arguments().size() == 1 ? QuickFixHelper.contentForTree(method.arguments().get(0), context) : "";
String replacement = "now(" + argument + ")";
IdentifierTree methodName = ExpressionUtils.methodName(mit);
QuickFixHelper.newIssue(context)
.forRule(this)
.onTree(methodName)
Comment thread
gitar-bot[bot] marked this conversation as resolved.
.withMessage("Replace with \"%s\".", replacement)
.withQuickFix(JavaQuickFix.newQuickFix("Replace with %s", replacement)
.addTextEdit(JavaTextEdit.replaceTextSpan(textSpanBetween(methodName, true, mit.arguments(), true), replacement))::build)
.report();
}
}

private static boolean isNonCompliantMethod(MethodInvocationTree method) {
if (method.methodSymbol().isStatic()) {
return INSTANT_NOW.matches(method) || NOW_METHODS.matches(method);
} else {
return AT_ZONE_METHOD.matches(method) &&
method.methodSelect() instanceof MemberSelectExpressionTree methodSelect &&
methodSelect.expression() instanceof MethodInvocationTree callee &&
callee.methodSymbol().isStatic() && INSTANT_NOW.matches(callee);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* 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 org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class SimpleTemporalInstantiationCheckTest {
@Test
void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/SimpleTemporalInstantiationCheckSample.java"))
.withCheck(new SimpleTemporalInstantiationCheck())
.verifyIssues();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<p>Using <code>.from()</code> to instantiate <code>LocalDate</code>, <code>LocalTime</code>, <code>YearMonth</code>, or <code>Year</code> from a
<em>current time</em> expression is unnecessarily verbose when a direct <code>.now()</code> factory method is available.</p>
<h2>Why is this an issue?</h2>
<p>Some <code>.from( )</code> patterns are unnecessarily verbose and can be simplified using the <code>now( )</code> method.</p>
<p>For example <code>LocalDate.from(Instant.now().atZone(zone))</code> can be replaced with <code>LocalDate.now(zone)</code></p>
<p>Calling <code>.now()</code> directly on the target type is more concise, and makes the intent clearer.</p>
<h2>How to fix it</h2>
<p>Call <code>.now()</code>, <code>.now(ZoneId)</code>, or <code>.now(Clock)</code> directly on the target type instead of using <code>.from()</code>
with a current time expression.</p>
<h3>Code examples</h3>
<h4>Noncompliant code example</h4>
<pre data-diff-id="1" data-diff-type="noncompliant">
import java.time.*;

void execute(Instant instant, ZoneId zoneId, Clock clock) {
LocalDate date = LocalDate.from(Instant.now().atZone(ZoneId.of("UTC"))); // Noncompliant
LocalTime time = LocalTime.from(LocalTime.now(zoneId)); // Noncompliant
YearMonth yearMonth = YearMonth.from(ZonedDateTime.now(clock)); // Noncompliant
// ...
}
</pre>
<h4>Compliant solution</h4>
<pre data-diff-id="1" data-diff-type="compliant">
import java.time.*;

void execute(Instant instant, ZoneId zoneId, Clock clock) {
LocalDate date = LocalDate.now(ZoneId.of("UTC"));
LocalTime time = LocalTime.now(zoneId);
YearMonth yearMonth = YearMonth.now(clock);
// ...
}
</pre>
<h2>Resources</h2>
<h3>Documentation</h3>
<ul>
<li>Java SE Documentation - <a href="https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html">java.time package</a></li>
<li>Java SE Documentation - <a href="https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#now-java.time.ZoneId-">LocalDate.now(ZoneId)
factory method</a></li>
</ul>

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"title": "Redundant time instantiation patterns should be simplified",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"datetime",
"pitfall"
],
"defaultSeverity": "Minor",
"ruleSpecification": "RSPEC-8695",
"sqKey": "S8695",
"scope": "All",
"quickfix": "covered",
"code": {
"impacts": {
"MAINTAINABILITY": "LOW"
},
"attribute": "CLEAR"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@
"S8688",
"S8692",
"S8694",
"S8695",
"S8696",
"S8700"
]
Expand Down
Loading