KogenCredo.Diff (KogenCredo v0.1.0)
View SourceDiff-aware AST rules: judge each changed function against its own base version.
A lost LIMIT, a dropped validation or a deleted owner scope is only visible this way: the
head alone looks like ordinary code.
Pure API
No git history is needed to use the engine:
{:ok, changes} = KogenCredo.Diff.changes("lib/a.ex", base_source, head_source)
findings = KogenCredo.Diff.check(changes)changes/3 returns {:error, {:invalid_source, :base | :head, message}} when either side does
not parse, never an empty success. run/2 drives the same engine over a git range.
Indexing
Functions are indexed by {module, name, arity} for def and defp. All clauses of a function
(including guarded ones) are grouped, nested defmodules extend the module name, and a
base counterpart is the same key, or else, within that module, the one base function of that
name whose arity vanished when exactly one function of that name appeared in the head (an added
or removed parameter). Any other arity change is ambiguous and the head function counts as new
instead of being compared with the wrong base. A change of visibility alone counts as changed. A function counts as changed when its
clauses differ with metadata stripped, so formatting-only edits are invisible. Removed
functions are not evaluated.
Known gaps: defdelegate, macro-generated functions (unquote heads, quote bodies),
defimpl and defprotocol bodies (only defmodule is descended into, so a function there is
never compared), modules whose name is not a literal alias (defmodule unquote(name);
__MODULE__.Child is supported), functions defined in if/for blocks, module attributes used as limits, and logic
moved into another module. Functions with default arguments are indexed under their full
arity only.
Git runs
run/2 reads --repo (default .), which may be a subdirectory of the work tree.
:paths/--path (default lib/) are relative to that directory and every path must match a
file in the base or head tree, otherwise the run fails (exit 2) instead of reporting
0 file(s) analysed. Umbrella projects: pass --path apps/NAME/lib/ once per app, or run
with --repo apps/NAME. A range that changes nothing under existing paths still succeeds.
Inherited GIT_DIR, GIT_WORK_TREE and similar variables are ignored.
Tiers
Every rule defaults to :advisory. A prototype of QueryLostLimit was swept in earlier
research; those numbers say nothing about this implementation, which has not been swept,
and no other rule has any measured precision. Promote a rule with the :severity option (%{"rule-id" => :error}) or
--error-rules only after sweeping it on your own history.
Options (shared by all rules)
:repo_names (default [:Repo]), :repo_modules, :sensitive_fields (query scope fields,
default [:user_id, :tenant_id, :account_id, :organization_id]),
:cast_sensitive_fields (fields that must not be added to cast), :severity.
Repo, module and field options take atoms or strings (the CLI passes strings). They are
compared by name with the analysed code, so a name that no loaded code mentions is as good as
any other. Change.module is the enclosing module's name, a string such as "MyApp.Search".
Summary
Functions
Parses both versions and returns the changed functions. base_src is nil for a new file.
Runs rules over changes and returns findings with their severity assigned.
Looks a rule module up by its id, e.g. "query-lost-limit".
All rule modules.
Runs the diff over a git range. Options and static limits are described in the module documentation.
Functions
@spec changes(String.t(), String.t() | nil, String.t()) :: {:ok, [KogenCredo.Diff.Change.t()]} | {:error, {:invalid_source, :base | :head, String.t()}}
Parses both versions and returns the changed functions. base_src is nil for a new file.
check(changes, rules \\ [KogenCredo.Diff.Rules.QueryLostLimit, KogenCredo.Diff.Rules.QueryLostScope, KogenCredo.Diff.Rules.NewRepoInLoop, KogenCredo.Diff.Rules.NewSilentFallback, KogenCredo.Diff.Rules.ChangesetWeakened, KogenCredo.Diff.Rules.LostTransaction, KogenCredo.Diff.Rules.WidenedApi], opts \\ [])
@spec check([KogenCredo.Diff.Change.t()], [module()], keyword()) :: [ KogenCredo.Diff.Finding.t() ]
Runs rules over changes and returns findings with their severity assigned.
Looks a rule module up by its id, e.g. "query-lost-limit".
@spec rules() :: [module()]
All rule modules.
@spec run( String.t(), keyword() ) :: {:ok, KogenCredo.Diff.Report.t()} | {:error, String.t()}
Runs the diff over a git range. Options and static limits are described in the module documentation.