KogenCredo.SafetySupport (KogenCredo v0.1.0)

View Source

Shared AST helpers for the safety checks and the diff rules.

Everything here is syntactic. Aliases are resolved from alias directives found anywhere in the parsed tree (not by scope), and a Repo receiver is recognised by name, never by compiling the project. That is a heuristic: a Repo that is really something else, or a repo hidden behind a variable or a macro, is invisible.

Repo receivers

repo_call/3 returns {function_name, args} when a call node looks like Repo.all(...). A receiver counts as a repo when its alias-expanded module either equals one of the configured :repo_modules (full names such as MyApp.Repo) or its last segment is in :repo_names (default [:Repo]).

Summary

Functions

Collects alias directives into a map from the local name (first segment) to the expanded segment list. Handles alias A.B, alias A.B, as: C and alias A.{B, C}.

Counts nodes of ast matching pred.

Expands the first segment of an alias through aliases/1 output.

True when the expanded module segments equal the given module (e.g. Enum).

Strips metadata so two ASTs compare structurally.

Matches a source file path against glob-like patterns.

Splits a |> chain into its stages, left to right.

If node is Mod.fun(...), returns {expanded_module_segments, fun, args}.

Returns {fun, args} when node is a call on a repo receiver (Repo.all(q)), else nil. Pipe-stage calls come in without the piped first argument, so args may be one shorter than the real arity.

Repo functions that read rows.

True when receiver_ast is a module alias that denotes a repo.

Repo functions that write rows.

Default test-file patterns.

Types

aliases()

@type aliases() :: %{optional(atom()) => [atom()]}

Functions

aliases(ast)

@spec aliases(Macro.t()) :: aliases()

Collects alias directives into a map from the local name (first segment) to the expanded segment list. Handles alias A.B, alias A.B, as: C and alias A.{B, C}.

count(ast, pred)

@spec count(Macro.t(), (Macro.t() -> boolean())) :: non_neg_integer()

Counts nodes of ast matching pred.

expand(parts, aliases)

@spec expand([atom()], aliases()) :: [atom()]

Expands the first segment of an alias through aliases/1 output.

module?(segments, module)

@spec module?([atom()], module()) :: boolean()

True when the expanded module segments equal the given module (e.g. Enum).

normalize(ast)

@spec normalize(Macro.t()) :: Macro.t()

Strips metadata so two ASTs compare structurally.

path_matches?(filename, patterns)

@spec path_matches?(String.t(), [String.t()]) :: boolean()

Matches a source file path against glob-like patterns.

** matches any characters including /, * matches anything but /. Patterns are matched against the path as a suffix, so lib/** also matches /abs/project/lib/a.ex. An empty pattern list matches everything.

pipeline_stages(node)

@spec pipeline_stages(Macro.t()) :: [Macro.t()]

Splits a |> chain into its stages, left to right.

remote_call(arg1, aliases)

@spec remote_call(Macro.t(), aliases()) :: {[atom()], atom(), list()} | nil

If node is Mod.fun(...), returns {expanded_module_segments, fun, args}.

repo_call(arg1, aliases, opts)

@spec repo_call(Macro.t(), aliases(), keyword()) :: {atom(), list()} | nil

Returns {fun, args} when node is a call on a repo receiver (Repo.all(q)), else nil. Pipe-stage calls come in without the piped first argument, so args may be one shorter than the real arity.

repo_reads()

@spec repo_reads() :: [atom()]

Repo functions that read rows.

repo_receiver?(arg1, aliases, opts)

@spec repo_receiver?(Macro.t(), aliases(), keyword()) :: boolean()

True when receiver_ast is a module alias that denotes a repo.

Options: :repo_names (last segments, default [:Repo]) and :repo_modules (full module names, default []). Both take atoms or strings and are compared by name, so a repo that is not loaded (or does not exist) is matched like any other.

repo_writes()

@spec repo_writes() :: [atom()]

Repo functions that write rows.

test_patterns()

@spec test_patterns() :: [String.t()]

Default test-file patterns.