Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
38ae801
chore: bump Elixir pin to ~> 1.18
aspala Jun 1, 2026
982dff1
chore(deps): add num42_refactors as dev dep + config
aspala Jun 1, 2026
62113bb
refactor(naming): expand short-form bindings to long forms
aspala Jun 1, 2026
2497fe9
chore(refactor): skip ExpandShortFormBindings — unsafe shadowing
aspala Jun 1, 2026
18c0017
refactor(naming): expand short-form private function names
aspala Jun 1, 2026
7b678bd
refactor(naming): rename str_token to string_kind_token
aspala Jun 1, 2026
c6e5ade
refactor(aliases): lift multi-segment module references to aliases
aspala Jun 1, 2026
bfc8908
refactor(aliases): lift function-local alias directives to module top
aspala Jun 1, 2026
69b9359
refactor(aliases): expand multi-alias groups to one per line
aspala Jun 1, 2026
257c849
refactor(pipes): rewrite x = f(x, ...) as x = x |> f(...)
aspala Jun 1, 2026
15e251c
refactor(pipes): rewrite Enum/Stream calls as pipe form
aspala Jun 1, 2026
d26d6de
chore(combined-metrics): sync language coverage and scalar vectors [s…
github-actions[bot] Jun 1, 2026
a566406
refactor(pipes): second pass of Enum/Stream to pipe form
aspala Jun 1, 2026
23e85e3
refactor(maps): rewrite Map.new(coll, fn) to for-comprehension form
aspala Jun 1, 2026
9a6f24a
refactor(captures): convert single-call lambdas to &-capture form
aspala Jun 1, 2026
6b115e8
refactor(captures): accept multi-line &-capture for 2-of-N call
aspala Jun 1, 2026
f82fb38
refactor(enum): use Enum.sum_by/2 instead of reduce-plus
aspala Jun 1, 2026
13b1ddb
refactor(maps): use Map.new/2 instead of reduce-Map.put
aspala Jun 1, 2026
5ecc2e5
refactor(maps): use Map.new instead of Enum.into(%{}) with pipe prese…
aspala Jun 1, 2026
34b40d4
refactor(opts): drop redundant nil default in Keyword.get
aspala Jun 1, 2026
70949f2
refactor(maps): pipe form for Map.new(coll)
aspala Jun 1, 2026
0d12320
refactor(enum): fuse map+join into Enum.map_join
aspala Jun 1, 2026
8ff572c
refactor(pipes): pipe form for Enum.map_join calls in git_test
aspala Jun 1, 2026
0276bf2
refactor(control): rewrite case-on-boolean as if/else
aspala Jun 1, 2026
dfb6a62
refactor(dedup): extract shared helpers via ExtractParametricClone
aspala Jun 1, 2026
9f6fe9d
refactor(captures): capture form for count_severities_shared
aspala Jun 1, 2026
6d724d8
refactor(aliases): sort contiguous alias groups alphabetically
aspala Jun 1, 2026
70515d4
refactor(control): extract tail-case dispatches to multi-clause helpers
aspala Jun 1, 2026
1482426
refactor(control): second pass of ExtractCaseToHelper
aspala Jun 1, 2026
dfd96ba
refactor(control): third pass of ExtractCaseToHelper
aspala Jun 1, 2026
28ceb3f
refactor(dedup): move pr_summary_section into Formatter.Shared
aspala Jun 1, 2026
c2d22d9
refactor(imports): move imports after alias block
aspala Jun 1, 2026
ee4bbd0
chore(refactor): skip IfElseToCond — drops guard conditions
aspala Jun 1, 2026
33c3770
refactor(clauses): lift if/else bodies to pattern-matched clauses
aspala Jun 1, 2026
01ada81
refactor(defs): collapse single-expression def bodies to do: form
aspala Jun 1, 2026
4cdb07b
chore(refactor): skip SortFunctions — too aggressive for this codebase
aspala Jun 1, 2026
d5dea1d
refactor(maps): sort struct/map keyword keys alphabetically
aspala Jun 1, 2026
2deed5d
chore(combined-metrics): sync language coverage and scalar vectors [s…
github-actions[bot] Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .refactor.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
%{
inputs: ["lib/**/*.ex", "test/**/*.exs"],
configured_modules: [],
skipped_modules: [
# Re-renames identifiers without scope-awareness: collapses two distinct
# variables in the same scope (e.g. base_val + head_val) into a single
# shadowed name, silently breaking semantics. Also fights manual rename
# decisions on each rerun (avg_us → microseconds → microseconds collision).
# Track upstream issue before re-enabling.
Number42.Refactors.Ex.ExpandShortFormBindings,

# Loses guard conditions in transit. Example: rewrites
# if next == nil do
# if total > 0 and ratio > 0.6, do: vote, else: nothing
# else
# nothing
# end
# to a cond block whose first arm is `compute_total.() -> vote`
# (i.e. just "any positive total casts a vote") — the original
# guard `ratio > 0.6` and the outer `next == nil` precondition
# are simply dropped. Produced 2 test failures in
# data_signal_test.exs on first try.
Number42.Refactors.Ex.IfElseToCond,

# Sorts def/defp groups alphabetically across the entire module.
# On this codebase that touches 121 files at once, deletes
# `# --- GenServer callbacks ---` / `# --- Private helpers ---`
# section comments, mixes public-API/impl-callbacks/private helpers
# into one alphabetical block, and splits some same-name clauses
# apart (compile warning: "clauses ... should be grouped together").
# Tests still pass but the resulting diff is unreviewable and the
# imposed order disagrees with the project's prior structural
# organisation (public first, then impl callbacks, then private).
Number42.Refactors.Ex.SortFunctions
]
}
4 changes: 1 addition & 3 deletions lib/code_qa.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,5 @@ defmodule CodeQA do
:world

"""
def hello do
:world
end
def hello, do: :world
end
32 changes: 14 additions & 18 deletions lib/codeqa/analysis/behavior_config_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ defmodule CodeQA.Analysis.BehaviorConfigServer do
# --- Public API ---

@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts)
end
def start_link(opts \\ []), do: __MODULE__ |> GenServer.start_link(opts)

@doc "Returns the ETS table id. Callers may read directly from it."
@spec get_tid(pid()) :: :ets.tid()
Expand Down Expand Up @@ -74,23 +72,11 @@ defmodule CodeQA.Analysis.BehaviorConfigServer do
end

@impl true
def handle_call(:get_tid, _from, state) do
{:reply, state.tid, state}
end
def handle_call(:get_tid, _from, state), do: {:reply, state.tid, state}

# --- Private helpers ---

defp load_configs(tid) do
case File.ls(@yaml_dir) do
{:ok, files} ->
files
|> Enum.filter(&String.ends_with?(&1, ".yml"))
|> Enum.each(&load_yml_file(&1, tid))

{:error, _} ->
:ok
end
end
defp load_configs(tid), do: File.ls(@yaml_dir) |> handle_load_configs_ls(tid)

defp load_yml_file(yml_file, tid) do
category = String.trim_trailing(yml_file, ".yml")
Expand All @@ -109,11 +95,21 @@ defmodule CodeQA.Analysis.BehaviorConfigServer do
behavior_data
|> Enum.flat_map(fn
{group, keys} when is_map(keys) ->
Enum.map(keys, fn {key, scalar} -> {{group, key}, scalar / 1.0} end)
keys |> Enum.map(fn {key, scalar} -> {{group, key}, scalar / 1.0} end)

_ ->
[]
end)
|> Map.new()
end

# FIXME: extracted automatically by ExtractCaseToHelper — review
# the parameter list and consider a better name.
defp handle_load_configs_ls({:ok, files}, tid),
do:
files
|> Enum.filter(&String.ends_with?(&1, ".yml"))
|> Enum.each(&load_yml_file(&1, tid))

defp handle_load_configs_ls({:error, _}, _tid), do: :ok
end
21 changes: 9 additions & 12 deletions lib/codeqa/analysis/file_context_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ defmodule CodeQA.Analysis.FileContextServer do

use GenServer

alias CodeQA.Engine.{FileContext, Pipeline}
alias CodeQA.Engine.FileContext
alias CodeQA.Engine.Pipeline
alias CodeQA.Language
alias CodeQA.Languages.Unknown

# --- Public API ---

@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts)
end
def start_link(opts \\ []), do: __MODULE__ |> GenServer.start_link(opts)

@doc "Returns the ETS table id. Callers may read directly from it."
@spec get_tid(pid()) :: :ets.tid()
Expand All @@ -44,13 +43,13 @@ defmodule CodeQA.Analysis.FileContextServer do
key = {md5(content), language_name}

case :ets.lookup(tid, key) do
[{_, ctx}] ->
ctx
[{_, context}] ->
context

[] ->
ctx = Pipeline.build_file_context(content, opts)
:ets.insert(tid, {key, ctx})
ctx
context = Pipeline.build_file_context(content, opts)
:ets.insert(tid, {key, context})
context
end
end

Expand All @@ -63,9 +62,7 @@ defmodule CodeQA.Analysis.FileContextServer do
end

@impl true
def handle_call(:get_tid, _from, state) do
{:reply, state.tid, state}
end
def handle_call(:get_tid, _from, state), do: {:reply, state.tid, state}

# --- Private helpers ---

Expand Down
18 changes: 8 additions & 10 deletions lib/codeqa/analysis/file_metrics_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ defmodule CodeQA.Analysis.FileMetricsServer do
# --- Public API ---

@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts)
end
def start_link(opts \\ []), do: __MODULE__ |> GenServer.start_link(opts)

@doc "Returns the ETS table id. Callers may read directly from it."
@spec get_tid(pid()) :: :ets.tid()
Expand All @@ -37,12 +35,14 @@ defmodule CodeQA.Analysis.FileMetricsServer do
tid = get_tid(pid)
files_data = Map.get(pipeline_result, "files", %{})

Enum.each(files_data, fn {path, file_data} ->
files_data
|> Enum.each(fn {path, file_data} ->
metrics = Map.get(file_data, "metrics", %{})
:ets.insert(tid, {{:path, path}, metrics})
end)

Enum.each(files_map, fn {path, content} ->
files_map
|> Enum.each(fn {path, content} ->
hash = md5(content)

case :ets.lookup(tid, {:path, path}) do
Expand Down Expand Up @@ -81,8 +81,8 @@ defmodule CodeQA.Analysis.FileMetricsServer do
metrics

[] ->
ctx = Pipeline.build_file_context(content, opts)
metrics = Registry.run_file_metrics(registry, ctx)
context = Pipeline.build_file_context(content, opts)
metrics = Registry.run_file_metrics(registry, context)
:ets.insert(tid, {{:hash, hash}, metrics})
metrics
end
Expand All @@ -97,9 +97,7 @@ defmodule CodeQA.Analysis.FileMetricsServer do
end

@impl true
def handle_call(:get_tid, _from, state) do
{:reply, state.tid, state}
end
def handle_call(:get_tid, _from, state), do: {:reply, state.tid, state}

# --- Private helpers ---

Expand Down
10 changes: 5 additions & 5 deletions lib/codeqa/analysis/run_supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ defmodule CodeQA.Analysis.RunSupervisor do

use Supervisor

alias CodeQA.Analysis.{BehaviorConfigServer, FileContextServer, RunContext}
alias CodeQA.Analysis.BehaviorConfigServer
alias CodeQA.Analysis.FileContextServer
alias CodeQA.Analysis.RunContext

@spec start_link(keyword()) :: Supervisor.on_start()
def start_link(opts \\ []) do
Supervisor.start_link(__MODULE__, opts)
end
def start_link(opts \\ []), do: __MODULE__ |> Supervisor.start_link(opts)

@doc """
Queries child PIDs from `sup` and returns a `RunContext` struct.
Expand Down Expand Up @@ -45,7 +45,7 @@ defmodule CodeQA.Analysis.RunSupervisor do

defp find_pid(children, module) do
{_id, pid, _type, _modules} =
Enum.find(children, fn {id, _pid, _type, _modules} -> id == module end)
children |> Enum.find(fn {id, _pid, _type, _modules} -> id == module end)

pid
end
Expand Down
55 changes: 26 additions & 29 deletions lib/codeqa/ast/classification/node_classifier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,22 @@ defmodule CodeQA.AST.Classification.NodeClassifier do

alias CodeQA.AST.Enrichment.Node

alias CodeQA.AST.Nodes.{
AttributeNode,
CodeNode,
DocNode,
FunctionNode,
ImportNode,
ModuleNode,
TestNode
}
alias CodeQA.AST.Nodes.AttributeNode
alias CodeQA.AST.Nodes.CodeNode
alias CodeQA.AST.Nodes.DocNode
alias CodeQA.AST.Nodes.FunctionNode
alias CodeQA.AST.Nodes.ImportNode
alias CodeQA.AST.Nodes.ModuleNode
alias CodeQA.AST.Nodes.TestNode

alias CodeQA.AST.Parsing.SignalStream

alias CodeQA.AST.Signals.Classification.{
AttributeSignal,
DocSignal,
FunctionSignal,
ImportSignal,
ModuleSignal,
TestSignal
}
alias CodeQA.AST.Signals.Classification.AttributeSignal
alias CodeQA.AST.Signals.Classification.DocSignal
alias CodeQA.AST.Signals.Classification.FunctionSignal
alias CodeQA.AST.Signals.Classification.ImportSignal
alias CodeQA.AST.Signals.Classification.ModuleSignal
alias CodeQA.AST.Signals.Classification.TestSignal

@classification_signals [
%DocSignal{},
Expand All @@ -67,13 +63,13 @@ defmodule CodeQA.AST.Classification.NodeClassifier do
]

@type_modules %{
doc: DocNode,
attribute: AttributeNode,
code: CodeNode,
doc: DocNode,
function: FunctionNode,
module: ModuleNode,
import: ImportNode,
test: TestNode,
code: CodeNode
module: ModuleNode,
test: TestNode
}

@doc """
Expand Down Expand Up @@ -106,12 +102,12 @@ defmodule CodeQA.AST.Classification.NodeClassifier do
defp prepend_context(tokens, []), do: tokens
defp prepend_context(tokens, ctx) when is_list(ctx), do: ctx ++ tokens

defp vote(tokens, lang_mod) do
tokens
|> run_signals(lang_mod)
|> tally()
|> winner()
end
defp vote(tokens, lang_mod),
do:
tokens
|> run_signals(lang_mod)
|> tally()
|> winner()

defp run_signals(tokens, lang_mod) do
SignalStream.run(tokens, @classification_signals, lang_mod)
Expand All @@ -120,15 +116,16 @@ defmodule CodeQA.AST.Classification.NodeClassifier do
end

defp tally(emissions) do
Enum.reduce(emissions, %{}, fn {_src, _grp, name, weight}, acc ->
emissions
|> Enum.reduce(%{}, fn {_src, _grp, name, weight}, acc ->
Map.update(acc, name, weight, &(&1 + weight))
end)
end

defp winner(votes) when map_size(votes) == 0, do: :code

defp winner(votes) do
{vote_name, _weight} = Enum.max_by(votes, fn {_, w} -> w end)
{vote_name, _weight} = votes |> Enum.max_by(fn {_, w} -> w end)
vote_to_type(vote_name)
end

Expand Down
5 changes: 2 additions & 3 deletions lib/codeqa/ast/classification/node_type_detector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ defmodule CodeQA.AST.Classification.NodeTypeDetector do
Classify each node in the list into the most specific typed struct.
"""
@spec detect_types([Node.t()], module()) :: [term()]
def detect_types(blocks, lang_mod) do
Enum.map(blocks, &NodeClassifier.classify(&1, lang_mod))
end
def detect_types(blocks, lang_mod),
do: blocks |> Enum.map(&NodeClassifier.classify(&1, lang_mod))
end
16 changes: 7 additions & 9 deletions lib/codeqa/ast/classification/typed_node_kind.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
defmodule CodeQA.AST.Classification.TypedNodeKind do
@moduledoc "Maps a typed node struct from `NodeClassifier` to its kind atom."

alias CodeQA.AST.Nodes.{
AttributeNode,
CodeNode,
DocNode,
FunctionNode,
ImportNode,
ModuleNode,
TestNode
}
alias CodeQA.AST.Nodes.AttributeNode
alias CodeQA.AST.Nodes.CodeNode
alias CodeQA.AST.Nodes.DocNode
alias CodeQA.AST.Nodes.FunctionNode
alias CodeQA.AST.Nodes.ImportNode
alias CodeQA.AST.Nodes.ModuleNode
alias CodeQA.AST.Nodes.TestNode

@type kind :: :doc | :attribute | :function | :module | :import | :test | :code

Expand Down
22 changes: 11 additions & 11 deletions lib/codeqa/ast/enrichment/compound_node.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ defmodule CodeQA.AST.Enrichment.CompoundNode do
alias CodeQA.AST.Enrichment.Node
alias CodeQA.AST.Nodes.AttributeNode

defstruct docs: [],
typespecs: [],
code: [],
start_line: nil,
start_col: nil,
defstruct code: [],
docs: [],
end_col: nil,
end_line: nil,
end_col: nil
start_col: nil,
start_line: nil,
typespecs: []

@type t :: %__MODULE__{
docs: [Node.t()],
typespecs: [AttributeNode.t()],
code: [Node.t()],
start_line: non_neg_integer() | nil,
start_col: non_neg_integer() | nil,
docs: [Node.t()],
end_col: non_neg_integer() | nil,
end_line: non_neg_integer() | nil,
end_col: non_neg_integer() | nil
start_col: non_neg_integer() | nil,
start_line: non_neg_integer() | nil,
typespecs: [AttributeNode.t()]
}
end
Loading