Skip to content

linters(dart): parse the JSON analyze output and batch from the project root - #1164

Open
AndrewDongminYoo wants to merge 2 commits into
trunk-io:mainfrom
AndrewDongminYoo:linters/dart-analyze-json
Open

linters(dart): parse the JSON analyze output and batch from the project root#1164
AndrewDongminYoo wants to merge 2 commits into
trunk-io:mainfrom
AndrewDongminYoo:linters/dart-analyze-json

Conversation

@AndrewDongminYoo

Copy link
Copy Markdown
Contributor

Summary

  • Parse dart analyze --format=json instead of the default human-readable output.
  • Run the batched analyze command once from the project root instead of once per parent directory.
  • Rule codes, severities, issue URLs, and trunk-ignore directives are unchanged. Messages lose the trailing correction sentence, so the snapshot is regenerated here.

Motivation

Two problems share one cause.

The default output format is not a contract. dart analyze --help --verbose says of it: "The default output format. This format is intended to be user consumable. The format is not specified and can change between releases." The linter currently parses that format with a regex.

The default format also reports paths relative to a root that Dart itself chooses, so results are only addressable when the analyzer runs from the target's own directory. That is what run_from: ${parent} is for, and it splits every batch along directory boundaries: batch: true never batches across directories. format does not share the problem because #1114 already runs it from the package root.

--format=json is the documented machine-readable output. It reports absolute paths, so the batch can run once from the project root.

Measurements

Four real repositories, each with dart@SYSTEM enabled and its packages already resolved, on Dart 3.13.1 and Trunk CLI 1.25.0. analysis_options counts the analysis_options.yaml files in the repository, since that is what root_or_parent_with groups by. Invocation counts come from a shim placed ahead of dart on PATH that records each argument list.

repository        analysis_options  dart dirs   before      after     invocations
Flutter app, 419 files           8        137   101.60s    14.70s    119 -> 16
Flutter game                     1         34    38.97s     4.12s     35 -> 4
Flutter app                      2         24    14.66s     4.31s     21 -> 2
analyzer plugins                 4         16    13.65s     9.99s     23 -> 11

The gain tracks how many analysis_options.yaml files a repository has, because that is what still bounds a batch after the change. A single-package repository collapses to one analyze invocation; a repository with four of them keeps four groups and gains less.

A synthetic fixture of 161 files across 40 directories, alternating the two definitions and rewriting every file between rounds so the lint cache cannot serve a previous result, gives a median of 7.46s against 2.98s with 43 invocations against 6. Wall-clock figures were taken on a loaded machine and are directional; invocation counts are exact.

Correctness

Checked on a fixture with two packages, so that root_or_parent_with(analysis_options.yaml) resolves to a subdirectory for some targets and to the repository root for others.

  • Locations stay correct across package boundaries: example/lib/bad.dart:3:12 and lib/feature_1/file.dart:3:12.
  • Rule codes stay lowercase: dart/prefer_single_quotes, dart/undefined_identifier. issue_url_format is unaffected.
  • Severities map as before: INFO to low, ERROR to high.
  • An existing // trunk-ignore(dart/prefer_single_quotes) still suppresses its issue, with no trunk/ignore-does-nothing notice.
  • Trunk applies the regex to every diagnostic in the single-line JSON document, not only the first: three violations across two packages were all reported.

On the Flutter app above, which reports 167 issues from its own very_good_analysis rule set, the before and after runs produce the same 167 issues with identical files, lines, columns, levels, and rule codes.

--format=machine was tried first and rejected. It reports absolute paths too, but uppercases every rule code to PREFER_SINGLE_QUOTES, which breaks issue_url_format and silently disarms every trunk-ignore(dart/…) comment already written in a consumer repository.

What changes for users

Issue messages become the analyzer's problemMessage alone. Previously the regex captured the default format's line, which concatenates the problem and the correction:

before: Local variables should be final. Try making the variable final.
after:  Local variables should be final.

The JSON output carries correctionMessage as a separate field, so the hint is available but cannot be joined into a single capture group by a regex parser.

Tests

npx jest linters/dart passes with the snapshot regenerated. The regenerated snapshot changes four lines, all of them message:

-      "message": "Undefined name 'find'. Try correcting the name to one that is defined, or defining the name.",
+      "message": "Undefined name 'find'.",

code, level, line, column, file, and issueUrl are byte-identical, and the second snapshot file is untouched.

Worth noting for review: linters/dart/test_data/ carries its own analysis_options.yaml, so root_or_parent_with(analysis_options.yaml) resolves to the same directory ${parent} does. The existing snapshot therefore cannot distinguish the two run_from settings on its own, and the multi-package case above is what exercises that part of the change. I am happy to add such a fixture if you would like it covered.

…ct root

The default analyze format is documented as user-consumable and unspecified between releases, and its relative paths force one invocation per parent directory. The JSON format reports absolute paths, lowercase rule codes, and uppercase severities, so a batch can be analyzed in one pass from the project root while existing trunk-ignore directives and issue URLs keep working.
The JSON output keeps the correction sentence in a separate correctionMessage field, so each recorded message is now the problem message alone. Codes, levels, lines, columns, and issue URLs are unchanged.
@trunk-io

trunk-io Bot commented Aug 29, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant