defmodule OeditusCredo.Check.Warning.MissingThrottle do
use Credo.Check,
base_priority: :low,
category: :warning,
explanations: [
check: """
Form inputs with phx-change, phx-keyup, or phx-input should include phx-debounce or phx-throttle.
This prevents excessive server events and improves performance.
Bad:
Good:
""",
params: [
exclude_test_files: "Set to true to skip test files (default: false)"
]
]
@trigger_events ["phx-change", "phx-keyup", "phx-input"]
@throttle_attrs ["phx-debounce", "phx-throttle"]
import OeditusCredo.Helpers, only: [test_file?: 1]
@doc false
@impl true
def run(%SourceFile{} = source_file, params) do
issue_meta = IssueMeta.for(source_file, params)
if Params.get(params, :exclude_test_files, __MODULE__) and
test_file?(source_file.filename) do
[]
else
if heex_file?(source_file) do
source_file
|> Credo.Code.to_lines()
|> check_for_missing_throttle(issue_meta)
else
[]
end
end
end
@doc false
@impl true
def param_defaults, do: [exclude_test_files: false]
defp heex_file?(%SourceFile{filename: filename}) do
String.ends_with?(filename, [".heex", ".leex"])
end
defp check_for_missing_throttle(lines, issue_meta) do
lines
|> Enum.with_index(1)
|> Enum.flat_map(fn {{_, line}, line_no} ->
if has_trigger_event?(line) and not has_throttle?(line) do
[issue_for(issue_meta, line_no)]
else
[]
end
end)
end
defp has_trigger_event?(line) do
Enum.any?(@trigger_events, &String.contains?(line, &1))
end
defp has_throttle?(line) do
Enum.any?(@throttle_attrs, &String.contains?(line, &1))
end
defp issue_for(issue_meta, line_no) do
format_issue(
issue_meta,
message: "Add phx-debounce or phx-throttle to prevent excessive server events",
trigger: "phx-change",
line_no: line_no
)
end
end