Current section
4 Versions
Jump to
Current section
4 Versions
Compare versions
5
files changed
+102
additions
-9
deletions
| @@ -0,0 +1 @@ | |
| 1 | + 0.2.0 |
| @@ -4,6 +4,24 @@ Completed roadmap tasks. For upcoming work, see [ROADMAP.md](ROADMAP.md). | |
| 4 4 | |
| 5 5 | --- |
| 6 6 | |
| 7 | + ## [0.2.0] - 2026-03-21 |
| 8 | + |
| 9 | + ### Added |
| 10 | + - `.dialyzer_ignore.exs` support — warnings matching the project's ignore file are now filtered out, matching dialyxir's behavior |
| 11 | + - `skipped` field in summary output showing how many warnings were filtered by the ignore file |
| 12 | + - Respects dialyxir's `dialyzer_flags` and `dialyzer_removed_defaults` configuration for warning flags |
| 13 | + |
| 14 | + ### Changed |
| 15 | + - Warning flags now match dialyxir's logic exactly instead of hardcoding `[:unknown]` |
| 16 | + - Added tidewave alias for local MCP development (port 4002) |
| 17 | + |
| 18 | + ## [0.1.1] - 2026-02-02 |
| 19 | + |
| 20 | + ### Added |
| 21 | + - Initial hex.pm release with full feature set (see Phase 1-5 below) |
| 22 | + |
| 23 | + --- |
| 24 | + |
| 7 25 | ## Phase 1: Core Foundation |
| 8 26 | |
| 9 27 | ### Initial Implementation |
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/ZenHive/dialyzer_json">>}]}. |
| 2 2 | {<<"name">>,<<"dialyzer_json">>}. |
| 3 | - {<<"version">>,<<"0.1.1">>}. |
| 3 | + {<<"version">>,<<"0.2.0">>}. |
| 4 4 | {<<"description">>, |
| 5 5 | <<"AI-friendly JSON output for Dialyzer warnings. Structured output for Claude Code and similar AI editors.">>}. |
| 6 6 | {<<"elixir">>,<<"~> 1.19">>}. |
| @@ -11,7 +11,8 @@ | |
| 11 11 | <<"lib/mix/tasks/dialyzer_json.ex">>,<<"lib/dialyzer_json">>, |
| 12 12 | <<"lib/dialyzer_json/warning_encoder.ex">>, |
| 13 13 | <<"lib/dialyzer_json/fix_hint.ex">>,<<".formatter.exs">>,<<"mix.exs">>, |
| 14 | - <<"README.md">>,<<"CHANGELOG.md">>,<<"LICENSE">>,<<"AGENTS.md">>]}. |
| 14 | + <<".version">>,<<"README.md">>,<<"CHANGELOG.md">>,<<"LICENSE">>, |
| 15 | + <<"AGENTS.md">>]}. |
| 15 16 | {<<"requirements">>, |
| 16 17 | [[{<<"name">>,<<"jason">>}, |
| 17 18 | {<<"app">>,<<"jason">>}, |
| @@ -54,6 +54,7 @@ defmodule Mix.Tasks.Dialyzer.Json do | |
| 54 54 | "warnings": [...], |
| 55 55 | "summary": { |
| 56 56 | "total": 5, |
| 57 | + "skipped": 0, |
| 57 58 | "by_type": {"no_return": 2, "call": 3}, |
| 58 59 | "by_fix_hint": {"code": 4, "spec": 1} |
| 59 60 | } |
| @@ -154,10 +155,14 @@ defmodule Mix.Tasks.Dialyzer.Json do | |
| 154 155 | ensure_plt!() |
| 155 156 | |
| 156 157 | # Run dialyzer and get warnings |
| 157 | - warnings = run_dialyzer() |
| 158 | + raw_warnings = run_dialyzer() |
| 159 | + |
| 160 | + # Filter ignored warnings (.dialyzer_ignore.exs) |
| 161 | + filter_map = load_filter_map() |
| 162 | + {warnings, skipped} = apply_ignore_filter(raw_warnings, filter_map) |
| 158 163 | |
| 159 164 | # Encode to JSON |
| 160 | - encoded = encode_output(warnings, opts) |
| 165 | + encoded = encode_output(warnings, Keyword.put(opts, :skipped, skipped)) |
| 161 166 | |
| 162 167 | # Output |
| 163 168 | output_json(encoded, opts) |
| @@ -252,20 +257,76 @@ defmodule Mix.Tasks.Dialyzer.Json do | |
| 252 257 | :ok |
| 253 258 | end |
| 254 259 | |
| 260 | + @doc false |
| 261 | + # Filters raw warnings against the project's ignore file (.dialyzer_ignore.exs). |
| 262 | + # Returns {kept_warnings, skipped_count}. |
| 263 | + @spec apply_ignore_filter([WarningEncoder.warning()], struct()) :: |
| 264 | + {[WarningEncoder.warning()], non_neg_integer()} |
| 265 | + def apply_ignore_filter(warnings, filter_map) do |
| 266 | + # credo:disable-for-next-line Credo.Check.Refactor.Apply |
| 267 | + if apply(Dialyxir.FilterMap, :filters, [filter_map]) == [] do |
| 268 | + {warnings, 0} |
| 269 | + else |
| 270 | + {kept, skipped} = |
| 271 | + Enum.reduce(warnings, {[], 0}, &partition_warning(&1, &2, filter_map)) |
| 272 | + |
| 273 | + {Enum.reverse(kept), skipped} |
| 274 | + end |
| 275 | + end |
| 276 | + |
| 277 | + @doc false |
| 278 | + # Checks a single warning against the filter map and partitions into kept/skipped |
| 279 | + @spec partition_warning( |
| 280 | + WarningEncoder.warning(), |
| 281 | + {[WarningEncoder.warning()], non_neg_integer()}, |
| 282 | + struct() |
| 283 | + ) :: |
| 284 | + {[WarningEncoder.warning()], non_neg_integer()} |
| 285 | + defp partition_warning(warning, {kept_acc, skipped_acc}, filter_map) do |
| 286 | + # credo:disable-for-next-line Credo.Check.Refactor.Apply |
| 287 | + {should_skip, _matching} = apply(Dialyxir.Project, :filter_warning?, [warning, filter_map]) |
| 288 | + |
| 289 | + if should_skip do |
| 290 | + {kept_acc, skipped_acc + 1} |
| 291 | + else |
| 292 | + {[warning | kept_acc], skipped_acc} |
| 293 | + end |
| 294 | + end |
| 295 | + |
| 296 | + @doc false |
| 297 | + # Loads the FilterMap from the project's ignore_warnings config |
| 298 | + @spec load_filter_map() :: struct() |
| 299 | + defp load_filter_map do |
| 300 | + # credo:disable-for-next-line Credo.Check.Refactor.Apply |
| 301 | + apply(Dialyxir.Project, :filter_map, [[]]) |
| 302 | + end |
| 303 | + |
| 304 | + @doc false |
| 305 | + # Computes warning flags matching dialyxir's logic: |
| 306 | + # transform(raw_opts) ++ (@default_warnings -- removed_defaults) |
| 307 | + @spec build_warning_flags([atom()], [atom()]) :: [atom()] |
| 308 | + def build_warning_flags(flags, removed_defaults) do |
| 309 | + flags ++ ([:unknown] -- removed_defaults) |
| 310 | + end |
| 311 | + |
| 255 312 | @doc false |
| 256 313 | # Runs dialyzer and returns the list of warnings |
| 257 314 | @spec run_dialyzer() :: [WarningEncoder.warning()] |
| 258 315 | defp run_dialyzer do |
| 259 316 | # Runtime calls to avoid compile-time warnings when used as a path dependency |
| 260 | - # credo:disable-for-lines:2 Credo.Check.Refactor.Apply |
| 317 | + # credo:disable-for-lines:4 Credo.Check.Refactor.Apply |
| 261 318 | plt_file = apply(Dialyxir.Project, :plt_file, []) |
| 262 319 | files = apply(Dialyxir.Project, :dialyzer_files, []) |
| 320 | + flags = apply(Dialyxir.Project, :dialyzer_flags, []) |
| 321 | + removed_defaults = apply(Dialyxir.Project, :dialyzer_removed_defaults, []) |
| 322 | + |
| 323 | + warnings = build_warning_flags(flags, removed_defaults) |
| 263 324 | |
| 264 325 | args = [ |
| 265 326 | check_plt: false, |
| 266 327 | init_plt: String.to_charlist(plt_file), |
| 267 328 | files: files, |
| 268 | - warnings: [:unknown] |
| 329 | + warnings: warnings |
| 269 330 | ] |
| 270 331 | |
| 271 332 | try do |
| @@ -288,6 +349,7 @@ defmodule Mix.Tasks.Dialyzer.Json do | |
| 288 349 | |
| 289 350 | summary = %{ |
| 290 351 | total: length(encoded_warnings), |
| 352 | + skipped: Keyword.get(opts, :skipped, 0), |
| 291 353 | by_type: count_by_type(encoded_warnings), |
| 292 354 | by_fix_hint: count_by_fix_hint(encoded_warnings) |
| 293 355 | } |
| @@ -2,18 +2,20 @@ defmodule DialyzerJson.MixProject do | |
| 2 2 | use Mix.Project |
| 3 3 | |
| 4 4 | @source_url "https://github.com/ZenHive/dialyzer_json" |
| 5 | + @version ".version" |> File.read!() |> String.trim() |
| 5 6 | |
| 6 7 | def project do |
| 7 8 | [ |
| 8 9 | app: :dialyzer_json, |
| 9 | - version: "0.1.1", |
| 10 | + version: @version, |
| 10 11 | elixir: "~> 1.19", |
| 11 12 | start_permanent: Mix.env() == :prod, |
| 12 13 | deps: deps(), |
| 13 14 | dialyzer: dialyzer(), |
| 14 15 | description: description(), |
| 15 16 | package: package(), |
| 16 | - source_url: @source_url |
| 17 | + source_url: @source_url, |
| 18 | + aliases: aliases() |
| 17 19 | ] |
| 18 20 | end |
| 19 21 | |
| @@ -35,7 +37,7 @@ defmodule DialyzerJson.MixProject do | |
| 35 37 | [ |
| 36 38 | licenses: ["MIT"], |
| 37 39 | links: %{"GitHub" => @source_url}, |
| 38 | - files: ~w(lib .formatter.exs mix.exs README.md CHANGELOG.md LICENSE AGENTS.md) |
| 40 | + files: ~w(lib .formatter.exs mix.exs .version README.md CHANGELOG.md LICENSE AGENTS.md) |
| 39 41 | ] |
| 40 42 | end |
| 41 43 | |
| @@ -46,6 +48,15 @@ defmodule DialyzerJson.MixProject do | |
| 46 48 | ] |
| 47 49 | end |
| 48 50 | |
| 51 | + defp aliases do |
| 52 | + [ |
| 53 | + # Port 4002 to avoid conflict with consumer projects on 4001 |
| 54 | + tidewave: [ |
| 55 | + "run --no-halt -e 'Agent.start(fn -> Bandit.start_link(plug: Tidewave, port: 4002) end)'" |
| 56 | + ] |
| 57 | + ] |
| 58 | + end |
| 59 | + |
| 49 60 | # Run "mix help deps" to learn about dependencies. |
| 50 61 | defp deps do |
| 51 62 | [ |