Skip to content

Commit

Permalink
Merge pull request #1074 from frerich/detect_compositions_of_utc_now_…
Browse files Browse the repository at this point in the history
…and_truncate

Detect compositions of {Naive}DateTime.utc_now and {Naive}DateTime.truncate
  • Loading branch information
rrrene committed Feb 8, 2024
2 parents e766b41 + 301397d commit dd69577
Show file tree
Hide file tree
Showing 3 changed files with 508 additions and 0 deletions.
1 change: 1 addition & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
{Credo.Check.Refactor.RedundantWithClauseResult, []},
{Credo.Check.Refactor.RejectReject, []},
{Credo.Check.Refactor.UnlessWithElse, []},
{Credo.Check.Refactor.UtcNowTruncate, []},
{Credo.Check.Refactor.WithClauses, []},

#
Expand Down
186 changes: 186 additions & 0 deletions lib/credo/check/refactor/utc_now_truncate.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
defmodule Credo.Check.Refactor.UtcNowTruncate do
use Credo.Check,
id: "EX4032",
base_priority: :high,
explanations: [
check: """
`DateTime.utc_now/1` is more efficient than `DateTime.utc_now/0 |> DateTime.truncate/1`.
For example, the code here ...
DateTime.utc_now() |> DateTime.truncate(:second)
NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
... can be refactored to look like this:
DateTime.utc_now(:second)
NaiveDateTime.utc_now(:second)
The reason for this is not just performance, because no separate function
call is required, but also brevity of the resulting code.
"""
]

@doc false
def run(source_file, params \\ []) do
issue_meta = IssueMeta.for(source_file, params)

Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
end

# DateTime.truncate(DateTime.utc_now(), _)
# DateTime.truncate(DateTime.utc_now(_), _)
# DateTime.truncate(DateTime.utc_now(_, _), _)
defp traverse(
{{:., meta, [{:__aliases__, _, [:DateTime]}, :truncate]}, _,
[
{{:., _, [{:__aliases__, _, [:DateTime]}, :utc_now]}, _, _},
_
]} =
ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "DateTime")
{ast, issues ++ List.wrap(new_issue)}
end

# DateTime.utc_now() |> DateTime.truncate(_)
# DateTime.utc_now(_) |> DateTime.truncate(_)
# DateTime.utc_now(_, _) |> DateTime.truncate(_)
defp traverse(
{:|>, _,
[
{{:., _, [{:__aliases__, _, [:DateTime]}, :utc_now]}, _, _},
{{:., meta, [{:__aliases__, _, [:DateTime]}, :truncate]}, _, [_]}
]} = ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "DateTime")
{ast, issues ++ List.wrap(new_issue)}
end

# DateTime.truncate(_ |> DateTime.utc_now(), _)
# DateTime.truncate(_ |> DateTime.utc_now(_), _)
defp traverse(
{{:., meta, [{:__aliases__, _, [:DateTime]}, :truncate]}, _,
[
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, [:DateTime]}, :utc_now]}, _, _}
]},
_
]} = ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "DateTime")
{ast, issues ++ List.wrap(new_issue)}
end

# _ |> DateTime.utc_now() |> DateTime.truncate(_)
# _ |> DateTime.utc_now(_) |> DateTime.truncate(_)
defp traverse(
{:|>, _,
[
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, [:DateTime]}, :utc_now]}, _, _}
]},
{{:., meta, [{:__aliases__, _, [:DateTime]}, :truncate]}, _, [_]}
]} = ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "DateTime")
{ast, issues ++ List.wrap(new_issue)}
end

# NaiveDateTime.truncate(NaiveDateTime.utc_now(), _)
# NaiveDateTime.truncate(NaiveDateTime.utc_now(_), _)
# NaiveDateTime.truncate(NaiveDateTime.utc_now(_, _), _)
defp traverse(
{{:., meta, [{:__aliases__, _, [:NaiveDateTime]}, :truncate]}, _,
[
{{:., _, [{:__aliases__, _, [:NaiveDateTime]}, :utc_now]}, _, _},
_
]} =
ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "NaiveDateTime")
{ast, issues ++ List.wrap(new_issue)}
end

# NaiveDateTime.utc_now() |> NaiveDateTime.truncate(_)
# NaiveDateTime.utc_now(_) |> NaiveDateTime.truncate(_)
# NaiveDateTime.utc_now(_, _) |> NaiveDateTime.truncate(_)
defp traverse(
{:|>, _,
[
{{:., _, [{:__aliases__, _, [:NaiveDateTime]}, :utc_now]}, _, _},
{{:., meta, [{:__aliases__, _, [:NaiveDateTime]}, :truncate]}, _, [_]}
]} = ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "NaiveDateTime")
{ast, issues ++ List.wrap(new_issue)}
end

# NaiveDateTime.truncate(_ |> NaiveDateTime.utc_now(), _)
# NaiveDateTime.truncate(_ |> NaiveDateTime.utc_now(_), _)
defp traverse(
{{:., meta, [{:__aliases__, _, [:NaiveDateTime]}, :truncate]}, _,
[
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, [:NaiveDateTime]}, :utc_now]}, _, _}
]},
_
]} = ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "NaiveDateTime")
{ast, issues ++ List.wrap(new_issue)}
end

# _ |> NaiveDateTime.utc_now() |> NaiveDateTime.truncate(_)
# _ |> NaiveDateTime.utc_now(_) |> NaiveDateTime.truncate(_)
defp traverse(
{:|>, _,
[
{:|>, _,
[
_,
{{:., _, [{:__aliases__, _, [:NaiveDateTime]}, :utc_now]}, _, _}
]},
{{:., meta, [{:__aliases__, _, [:NaiveDateTime]}, :truncate]}, _, [_]}
]} = ast,
issues,
issue_meta
) do
new_issue = issue_for(issue_meta, meta[:line], "NaiveDateTime")
{ast, issues ++ List.wrap(new_issue)}
end

defp traverse(ast, issues, _issue_meta) do
{ast, issues}
end

defp issue_for(issue_meta, line_no, module) do
format_issue(
issue_meta,
message:
"Pass time unit to `#{module}.utc_now` instead of composing with `#{module}.truncate/2`.",
trigger: "#{module}.truncate",
line_no: line_no
)
end
end
Loading

0 comments on commit dd69577

Please sign in to comment.