feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
package rule
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"go/ast"
|
|
|
|
|
"go/token"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2025-05-27 12:25:01 +02:00
|
|
|
"time"
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
|
2025-05-26 13:18:38 +02:00
|
|
|
"github.com/mgechev/revive/internal/astutils"
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
"github.com/mgechev/revive/lint"
|
|
|
|
|
"github.com/mgechev/revive/logging"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TimeDateRule lints the way time.Date is used.
|
|
|
|
|
type TimeDateRule struct{}
|
|
|
|
|
|
|
|
|
|
// Apply applies the rule to given file.
|
|
|
|
|
func (*TimeDateRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
|
|
|
|
var failures []lint.Failure
|
|
|
|
|
|
|
|
|
|
onFailure := func(failure lint.Failure) {
|
|
|
|
|
failures = append(failures, failure)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w := &lintTimeDate{file, onFailure}
|
|
|
|
|
|
|
|
|
|
ast.Walk(w, file.AST)
|
|
|
|
|
return failures
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Name returns the rule name.
|
|
|
|
|
func (*TimeDateRule) Name() string {
|
|
|
|
|
return "time-date"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type lintTimeDate struct {
|
|
|
|
|
file *lint.File
|
|
|
|
|
onFailure func(lint.Failure)
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
// timeDateArgument is a type for the arguments of time.Date function.
|
|
|
|
|
type timeDateArgument string
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
timeDateArgYear timeDateArgument = "year"
|
|
|
|
|
timeDateArgMonth timeDateArgument = "month"
|
|
|
|
|
timeDateArgDay timeDateArgument = "day"
|
|
|
|
|
timeDateArgHour timeDateArgument = "hour"
|
|
|
|
|
timeDateArgMinute timeDateArgument = "minute"
|
|
|
|
|
timeDateArgSecond timeDateArgument = "second"
|
|
|
|
|
timeDateArgNanosecond timeDateArgument = "nanosecond"
|
|
|
|
|
timeDateArgTimezone timeDateArgument = "timezone"
|
|
|
|
|
)
|
|
|
|
|
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
var (
|
2025-05-27 08:44:24 +03:00
|
|
|
// timeDateArgumentNames are the names of the arguments of time.Date.
|
2025-05-27 12:25:01 +02:00
|
|
|
timeDateArgumentNames = []timeDateArgument{
|
|
|
|
|
timeDateArgYear,
|
|
|
|
|
timeDateArgMonth,
|
|
|
|
|
timeDateArgDay,
|
|
|
|
|
timeDateArgHour,
|
|
|
|
|
timeDateArgMinute,
|
|
|
|
|
timeDateArgSecond,
|
|
|
|
|
timeDateArgNanosecond,
|
|
|
|
|
timeDateArgTimezone,
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
}
|
|
|
|
|
|
2025-05-27 08:44:24 +03:00
|
|
|
// timeDateArity is the number of arguments of time.Date.
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
timeDateArity = len(timeDateArgumentNames)
|
|
|
|
|
)
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
var timeDateArgumentBoundaries = map[timeDateArgument][2]int64{
|
|
|
|
|
// year is not validated
|
|
|
|
|
timeDateArgMonth: {1, 12},
|
|
|
|
|
timeDateArgDay: {1, 31}, // there is a special check for this field, this is just a fallback
|
|
|
|
|
timeDateArgHour: {0, 23},
|
|
|
|
|
timeDateArgMinute: {0, 59},
|
|
|
|
|
timeDateArgSecond: {0, 60}, // 60 is for leap second
|
|
|
|
|
timeDateArgNanosecond: {0, 1e9 - 1}, // 1e9 is not allowed, as it means 1 second
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type timeDateMonthYear struct {
|
|
|
|
|
year, month int64
|
|
|
|
|
}
|
|
|
|
|
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
func (w lintTimeDate) Visit(n ast.Node) ast.Visitor {
|
|
|
|
|
ce, ok := n.(*ast.CallExpr)
|
|
|
|
|
if !ok || len(ce.Args) != timeDateArity {
|
|
|
|
|
return w
|
|
|
|
|
}
|
2025-05-26 13:18:38 +02:00
|
|
|
if !astutils.IsPkgDotName(ce.Fun, "time", "Date") {
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
return w
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-26 14:32:55 +02:00
|
|
|
// The last argument is a timezone, check it
|
|
|
|
|
tzArg := ce.Args[timeDateArity-1]
|
|
|
|
|
if astutils.IsIdent(tzArg, "nil") {
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: tzArg,
|
|
|
|
|
Confidence: 1,
|
|
|
|
|
Failure: "time.Date timezone argument cannot be nil, it would panic on runtime",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
var parsedDate timeDateMonthYear
|
2025-05-26 14:32:55 +02:00
|
|
|
// All the other arguments should be decimal integers.
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
for pos, arg := range ce.Args[:timeDateArity-1] {
|
2025-05-27 12:25:01 +02:00
|
|
|
fieldName := timeDateArgumentNames[pos]
|
|
|
|
|
|
|
|
|
|
bl, ok := w.checkArgSign(arg, fieldName)
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
if !ok {
|
2025-05-27 12:25:01 +02:00
|
|
|
// either it is not a basic literal
|
|
|
|
|
// or it is a unary expression with a sign that was reported as a failure
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
parsedValue, err := parseDecimalInteger(bl)
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
if err == nil {
|
2025-05-27 12:25:01 +02:00
|
|
|
if fieldName == timeDateArgYear {
|
|
|
|
|
// store the year value for further checks with day
|
|
|
|
|
parsedDate.year = parsedValue
|
|
|
|
|
|
|
|
|
|
// no checks for year, as it can be any value
|
|
|
|
|
// a year can be negative, zero, or positive
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boundaries, ok := timeDateArgumentBoundaries[fieldName]
|
|
|
|
|
if !ok {
|
|
|
|
|
// no boundaries for this field, skip it
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
minValue, maxValue := boundaries[0], boundaries[1]
|
|
|
|
|
|
|
|
|
|
switch fieldName {
|
|
|
|
|
case timeDateArgMonth:
|
|
|
|
|
parsedDate.month = parsedValue
|
|
|
|
|
|
|
|
|
|
if parsedValue == 0 {
|
|
|
|
|
// Special case: month is 0.
|
|
|
|
|
// Go treats it as January, but we still report it as a failure.
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: arg,
|
|
|
|
|
Confidence: 1,
|
|
|
|
|
Failure: "time.Date month argument should not be zero",
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case timeDateArgDay:
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case parsedValue == 0:
|
|
|
|
|
// Special case: day is 0.
|
|
|
|
|
// Go treats it as the first day of the month, but we still report it as a failure.
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: arg,
|
|
|
|
|
Confidence: 1,
|
|
|
|
|
Failure: "time.Date day argument should not be zero",
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
// the month is valid, check the day
|
|
|
|
|
case parsedDate.month >= 1 && parsedDate.month <= 12:
|
|
|
|
|
month := time.Month(parsedDate.month)
|
|
|
|
|
|
|
|
|
|
maxValue = w.daysInMonth(parsedDate.year, month)
|
|
|
|
|
|
|
|
|
|
monthName := month.String()
|
|
|
|
|
if month == time.February {
|
|
|
|
|
// because of leap years, we need to provide the year in the error message
|
|
|
|
|
monthName += " " + strconv.FormatInt(parsedDate.year, 10)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if parsedValue > maxValue {
|
|
|
|
|
// we can provide a more detailed error message
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: arg,
|
|
|
|
|
Confidence: 0.8,
|
|
|
|
|
Failure: fmt.Sprintf(
|
|
|
|
|
"time.Date day argument is %d, but %s has only %d days",
|
|
|
|
|
parsedValue, monthName, maxValue,
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We know, the month is >12, let's try to detect possible day and month swap in arguments.
|
|
|
|
|
// for example: time.Date(2023, 31, 6, 0, 0, 0, 0, time.UTC)
|
|
|
|
|
case parsedDate.month > 12 && parsedDate.month <= 31 && parsedValue <= 12:
|
|
|
|
|
|
|
|
|
|
// Invert the month and day values
|
|
|
|
|
realMonth, realDay := parsedValue, parsedDate.month
|
|
|
|
|
|
|
|
|
|
// Check if the real month is valid.
|
|
|
|
|
if realDay <= w.daysInMonth(parsedDate.year, time.Month(realMonth)) {
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: arg,
|
|
|
|
|
Confidence: 0.5,
|
|
|
|
|
Failure: fmt.Sprintf(
|
|
|
|
|
"time.Date month and day arguments appear to be swapped: %d-%02d-%02d vs %d-%02d-%02d",
|
|
|
|
|
parsedDate.year, realMonth, realDay,
|
|
|
|
|
parsedDate.year, parsedDate.month, parsedValue,
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if parsedValue < minValue || parsedValue > maxValue {
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: arg,
|
|
|
|
|
Confidence: 0.8,
|
|
|
|
|
Failure: fmt.Sprintf(
|
|
|
|
|
"time.Date %s argument should be between %d and %d: %s",
|
|
|
|
|
fieldName, minValue, maxValue, astutils.GoFmt(arg),
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if errors.Is(err, errParsedInvalid) {
|
|
|
|
|
// This is not supposed to happen, let's be defensive
|
|
|
|
|
// log the error, but continue
|
|
|
|
|
|
|
|
|
|
logger, errLogger := logging.GetLogger()
|
|
|
|
|
if errLogger != nil {
|
|
|
|
|
// This is not supposed to happen, discard both errors
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
logger.With(
|
|
|
|
|
"value", bl.Value,
|
|
|
|
|
"kind", bl.Kind,
|
|
|
|
|
"error", err.Error(),
|
|
|
|
|
).Error("failed to parse time.Date argument")
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
confidence := 0.8 // default confidence
|
|
|
|
|
errMessage := err.Error()
|
2025-05-27 12:25:01 +02:00
|
|
|
replacedValue := strconv.FormatInt(parsedValue, 10)
|
|
|
|
|
instructions := fmt.Sprintf("use %s instead of %s", replacedValue, astutils.GoFmt(arg))
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
switch {
|
|
|
|
|
case errors.Is(err, errParsedOctalWithZero):
|
|
|
|
|
// people can use 00, 01, 02, 03, 04, 05, 06, and 07 if they want.
|
|
|
|
|
confidence = 0.5
|
|
|
|
|
|
|
|
|
|
case errors.Is(err, errParsedOctalWithPaddingZeroes):
|
|
|
|
|
// This is a clear mistake.
|
|
|
|
|
// example with 000123456 (octal) is about 123456 or 42798 ?
|
|
|
|
|
confidence = 1
|
|
|
|
|
|
|
|
|
|
strippedValue := strings.TrimLeft(bl.Value, "0")
|
|
|
|
|
if strippedValue == "" {
|
|
|
|
|
// avoid issue with 00000000
|
|
|
|
|
strippedValue = "0"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strippedValue != replacedValue {
|
|
|
|
|
instructions = fmt.Sprintf(
|
|
|
|
|
"choose between %s and %s (decimal value of %s octal value)",
|
|
|
|
|
strippedValue, replacedValue, strippedValue,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: bl,
|
|
|
|
|
Confidence: confidence,
|
|
|
|
|
Failure: fmt.Sprintf(
|
|
|
|
|
"use decimal digits for time.Date %s argument: %s found: %s",
|
2025-05-27 12:25:01 +02:00
|
|
|
fieldName, errMessage, instructions),
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return w
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
func (w *lintTimeDate) checkArgSign(arg ast.Node, fieldName timeDateArgument) (*ast.BasicLit, bool) {
|
|
|
|
|
if bl, ok := arg.(*ast.BasicLit); ok {
|
|
|
|
|
// it is an unsigned basic literal
|
|
|
|
|
// we can use it as is
|
|
|
|
|
return bl, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We can have an unary expression like -1, -a, +a, +1...
|
|
|
|
|
node, ok := arg.(*ast.UnaryExpr)
|
|
|
|
|
if !ok {
|
|
|
|
|
// Any other expression is not supported.
|
|
|
|
|
// It could be something like this:
|
|
|
|
|
// time.Date(2023, 2 * a, 3 + b, 4, 5, 6, 7, time.UTC)
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// But we expect the unary expression to be followed by a basic literal
|
|
|
|
|
bl, ok := node.X.(*ast.BasicLit)
|
|
|
|
|
if !ok {
|
|
|
|
|
// This is not a basic literal, it could be an identifier, a function call, etc.
|
|
|
|
|
// -a
|
|
|
|
|
// ^b
|
|
|
|
|
// -foo()
|
|
|
|
|
//
|
|
|
|
|
// It's out of scope of this rule.
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
// So now, we have an unary expression like -500, +2023, -0x1234 ...
|
|
|
|
|
|
|
|
|
|
if fieldName == timeDateArgYear && node.Op == token.SUB {
|
|
|
|
|
// The year can be negative, like referring to BC years.
|
|
|
|
|
// We can return it as is, without reporting a failure
|
|
|
|
|
return bl, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch node.Op {
|
|
|
|
|
case token.SUB:
|
|
|
|
|
// This is a negative number, it is supported, but it's uncommon.
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: arg,
|
|
|
|
|
Confidence: 0.5,
|
|
|
|
|
Failure: fmt.Sprintf(
|
|
|
|
|
"time.Date %s argument is negative: %s",
|
|
|
|
|
fieldName, astutils.GoFmt(arg),
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
case token.ADD:
|
|
|
|
|
// There is a positive sign, but it is not necessary to have a positive sign
|
|
|
|
|
w.onFailure(lint.Failure{
|
|
|
|
|
Category: "time",
|
|
|
|
|
Node: arg,
|
|
|
|
|
Confidence: 0.8,
|
|
|
|
|
Failure: fmt.Sprintf(
|
|
|
|
|
"time.Date %s argument contains a useless plus sign: %s",
|
|
|
|
|
fieldName, astutils.GoFmt(arg),
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
default:
|
|
|
|
|
// Other unary expressions are not supported.
|
|
|
|
|
//
|
|
|
|
|
// It could be something like this:
|
|
|
|
|
// ^1, ^0x1234
|
|
|
|
|
// but these are unlikely to be used with time.Date
|
|
|
|
|
// We ignore them, to avoid false positives.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// isLeapYear checks if the year is a leap year.
|
|
|
|
|
// This is used to check if the date is valid according to Go implementation.
|
|
|
|
|
func (lintTimeDate) isLeapYear(year int64) bool {
|
|
|
|
|
// We cannot use the classic formula of
|
|
|
|
|
// year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
|
|
|
|
// because we want to ensure what time.Date will compute
|
|
|
|
|
|
|
|
|
|
return time.Date(int(year), 2, 29, 0, 0, 0, 0, time.UTC).Format("01-02") == "02-29"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w lintTimeDate) daysInMonth(year int64, month time.Month) int64 {
|
|
|
|
|
switch month {
|
|
|
|
|
case time.April, time.June, time.September, time.November:
|
|
|
|
|
return 30
|
|
|
|
|
case time.February:
|
|
|
|
|
if w.isLeapYear(year) {
|
|
|
|
|
return 29
|
|
|
|
|
}
|
|
|
|
|
return 28
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 31
|
|
|
|
|
}
|
|
|
|
|
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
var (
|
|
|
|
|
errParsedOctal = errors.New("octal notation")
|
|
|
|
|
errParsedOctalWithZero = errors.New("octal notation with leading zero")
|
|
|
|
|
errParsedOctalWithPaddingZeroes = errors.New("octal notation with padding zeroes")
|
|
|
|
|
errParsedHexadecimal = errors.New("hexadecimal notation")
|
|
|
|
|
errParseBinary = errors.New("binary notation")
|
|
|
|
|
errParsedFloat = errors.New("float literal")
|
|
|
|
|
errParsedExponential = errors.New("exponential notation")
|
|
|
|
|
errParsedAlternative = errors.New("alternative notation")
|
|
|
|
|
errParsedInvalid = errors.New("invalid notation")
|
|
|
|
|
)
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
func parseDecimalInteger(bl *ast.BasicLit) (int64, error) {
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
currentValue := strings.ToLower(bl.Value)
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
if currentValue == "0" {
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
// skip 0 as it is a valid value for all the arguments
|
2025-05-27 12:25:01 +02:00
|
|
|
return 0, nil
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch bl.Kind {
|
|
|
|
|
case token.FLOAT:
|
|
|
|
|
// someone used a float literal, while they should have used an integer literal.
|
|
|
|
|
parsedValue, err := strconv.ParseFloat(currentValue, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// This is not supposed to happen
|
2025-05-27 12:25:01 +02:00
|
|
|
return 0, fmt.Errorf(
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
"%w: %s: %w",
|
|
|
|
|
errParsedInvalid,
|
|
|
|
|
"failed to parse number as float",
|
|
|
|
|
err,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this will convert back the number to a string
|
|
|
|
|
if strings.Contains(currentValue, "e") {
|
2025-05-27 12:25:01 +02:00
|
|
|
return int64(parsedValue), errParsedExponential
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
return int64(parsedValue), errParsedFloat
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
|
|
|
|
|
case token.INT:
|
|
|
|
|
// we expect this format
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
// This is not supposed to happen
|
2025-05-27 12:25:01 +02:00
|
|
|
return 0, fmt.Errorf(
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
"%w: %s",
|
|
|
|
|
errParsedInvalid,
|
|
|
|
|
"unexpected kind of literal",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the number with base=0 that allows to accept all number formats and base
|
|
|
|
|
parsedValue, err := strconv.ParseInt(currentValue, 0, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// This is not supposed to happen
|
2025-05-27 12:25:01 +02:00
|
|
|
return 0, fmt.Errorf(
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
"%w: %s: %w",
|
|
|
|
|
errParsedInvalid,
|
|
|
|
|
"failed to parse number as integer",
|
|
|
|
|
err,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Let's figure out the notation to return an error
|
|
|
|
|
switch {
|
|
|
|
|
case strings.HasPrefix(currentValue, "0b"):
|
2025-05-27 12:25:01 +02:00
|
|
|
return parsedValue, errParseBinary
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
case strings.HasPrefix(currentValue, "0x"):
|
2025-05-27 12:25:01 +02:00
|
|
|
return parsedValue, errParsedHexadecimal
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
case strings.HasPrefix(currentValue, "0"):
|
|
|
|
|
// this matches both "0" and "0o" octal notation.
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
switch currentValue {
|
|
|
|
|
// people can use 00, 01, 02, 03, 04, 05, 06, 07, if they want
|
|
|
|
|
case "00", "01", "02", "03", "04", "05", "06", "07":
|
|
|
|
|
return parsedValue, errParsedOctalWithZero
|
|
|
|
|
}
|
|
|
|
|
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
if strings.HasPrefix(currentValue, "00") {
|
|
|
|
|
// 00123456 (octal) is about 123456 or 42798 ?
|
2025-05-27 12:25:01 +02:00
|
|
|
return parsedValue, errParsedOctalWithPaddingZeroes
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
return parsedValue, errParsedOctal
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert back the number to a string, and compare it with the original one
|
|
|
|
|
formattedValue := strconv.FormatInt(parsedValue, 10)
|
|
|
|
|
if formattedValue != currentValue {
|
|
|
|
|
// This can catch some edge cases like: 1_0 ...
|
2025-05-27 12:25:01 +02:00
|
|
|
return parsedValue, errParsedAlternative
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:25:01 +02:00
|
|
|
return parsedValue, nil
|
feature: add rule time-date to check for time.Date usage (#1327)
This commit introduces a new rule to check for the usage of time.Date
The rule is added to report the usage of time.Date with non-decimal literals
Here the leading zeros that seems OK, forces the value to be octal literals.
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
gofumpt formats the code like this when it encounters leading zeroes.
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
The rule reports anything that is not a decimal literal.
2025-05-22 21:44:04 +02:00
|
|
|
}
|