Skip to content

feat(validator): check imports and barrels - #31

Open
Statyk7 wants to merge 4 commits into
feat/validator-severityfrom
feat/import-barrel-checks
Open

Statyk7 wants to merge 4 commits into
feat/validator-severityfrom
feat/import-barrel-checks

Conversation

@Statyk7

@Statyk7 Statyk7 commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Closes #25. Stacked on #30, which is stacked on #29: the base is feat/validator-severity, so this diff shows only the #25 changes. Merge #29, then #30, then this.

The validator now reads the import and export directives of every Dart file under a package's lib/ and bin/ and adds five rules:

Rule ID Severity Catches
missing-primary-barrel error A feature package with no lib/{package}.dart
deep-import error An import or export of another workspace package past the barrels at the root of its lib/
cross-feature-primary-barrel error A feature importing another feature's primary presentation barrel; dedicated barrels pass, and apps may import primary barrels
data-barrel-exports-internals error A data barrel exporting from dtos/ or mappers/; repositories and data sources are fine, since the app constructs data sources to inject them
unused-dependency warning A workspace dependency under dependencies never imported in lib/ or bin/

Findings in a Dart file are grouped under that file with their line, and can be suppressed in it with // ffca-ignore: <rule-id> — <reason>, the Dart form #21 deferred to this issue.

Decisions

  • The hook runs on .dart edits (the open question in feat: check imports and barrels in the layer validator #25), but for a Dart file the validator reports only the findings located in that file, so a repo with existing violations does not block every Dart edit. Package-wide findings (missing barrel, unused dependency) come with pubspec edits and --all. Cost: one dart run per Dart edit in an FFCA repo.
  • deep-import instead of a src/ rule. project_structure.md (Layer subfolders) puts models/, data_sources/, and so on directly under lib/, while data.md's examples use lib/src/. A src/-only rule would miss deep imports in the reference's own layout, so any import past a root barrel counts.
  • The primary barrel is lib/{package}.dart. The reference names it {feature}_{layer}.dart, which differs for backend packages: auth_data_firebase needs auth_data_firebase.dart.
  • Directives are read from source text, not with package:analyzer, which keeps the script free of dependencies, as it stays in the plugin rather than moving into Very Good CLI. The scanner stops at the first declaration and follows multi-line directives (see Review fixes below).

Fixtures and tests

  • Every feature package in the three fixture workspaces gains a barrel that imports its declared dependencies. valid_workspace covers the passing shapes: an app importing primary barrels, ideas_presentation importing cart_presentation's dedicated cart_badge.dart barrel, and product_data exporting its repository.
  • invalid_workspace gains sigma/upsilon (primary barrel), tau (data barrel exports), phi (deep import, plus a file under test/ that must be ignored), and chi (no barrel). warning_workspace gains omega: an unused dependency and a deep import suppressed in its Dart file.
  • scripts/analysis_options.yaml keeps the fixtures out of dart analyze.
  • 13 new validator tests, including a Dart file mode group, and 3 new Dart-edit cases in hooks/validate_layers_test.sh.

Docs

README rule table and Dart suppression syntax, CLAUDE.md hook behavior on Dart edits, AGENTS.md structure, and the hooks.json description. The auditor agent and the ffca-audit skill drop their manual unused-dependency and barrel checks, now deterministic, and keep barrel scope and the DTO leaks a regex cannot see.

Checked against a real repo. mealify_feature_first, the example app the reference cites, passes with no findings. A first run reported five false positives because the rule also covered data sources, which mealify exports on purpose; the rule now covers DTOs and mappers only, and the contradicting sentence in the data page is tracked in VGVentures/very_good_engineering#294.

Review fixes

An independent review before team review found these gaps, all fixed, each with a fixture and a test:

  • Undeclared imports bypassed the layer rules. In a Dart workspace, product_presentation could import cart_data without declaring it, and neither mode noticed. Imports of undeclared workspace packages now go through the rules table: a forbidden layer reports that layer rule's ID on the import line, and an allowed one reports the new undeclared-dependency error.
  • A clean pubspec edit blocked on a dependent's Dart findings. Import checks and suppression validation now run for the edited package only; dependents keep the layer and ownership checks.
  • Scaffolding blocked. A new package's pubspec, written before its lib/, failed missing-primary-barrel. Incremental mode now skips it until lib/ exists; --all still reports it.
  • Imports inside strings were read. A new directive scanner stops at the first declaration, follows multi-line directives, and reads every URI, so both sides of a conditional import are checked.
  • Only the primary data barrel was checked. Every barrel at the root of a data package's lib/ is checked now, following relative exports one level.
  • Dart-file suppressions: a // comment naming a pubspec-only rule is reported instead of silently matching nothing, and suppression findings are grouped under the file they are in.
  • Docs: CLAUDE.md said dart only runs for pubspec edits, and the barrel rule's citation now points at what the FFCA pages actually say (the general rule is missing from the FFCA section, raised on VGVentures/very_good_engineering#294).

New fixture shapes that must pass: a multi-line string containing an import, a two-line export … show, a double-quoted deferred import, and a shared package with no barrel. mealify_feature_first still passes with no findings.

Verified locally: dart analyze, dart format, dart test (71 tests), both hook test scripts (validate_layers_test.sh: 21 cases), markdownlint, and cspell.

Type of Change

  • New feature (feat)
  • Bug fix (fix)
  • Code refactor (refactor)
  • Documentation (docs)
  • CI change (ci)
  • Chore (chore)

🤖 Generated with Claude Code

https://claude.ai/code/session_017Vxxv9fVFGupLXEnmx5bSq

Read the import and export directives of every Dart file under a
package's lib/ and bin/, and add five rules:

- missing-primary-barrel: a feature package has no lib/{package}.dart.
- deep-import: an import or export reaches past the barrels at the root
  of another workspace package's lib/, src/ or not.
- cross-feature-primary-barrel: a feature imports another feature's
  primary presentation barrel instead of a dedicated one.
- data-barrel-exports-internals: a data barrel exports from
  data_sources/, dtos/, or mappers/.
- unused-dependency (warning): a workspace dependency under
  dependencies is never imported.

Findings in a Dart file carry its path and line, and can be suppressed
in that file with `// ffca-ignore: <rule-id> — <reason>`.

The hook now also runs on .dart edits. For a Dart file the validator
reports only the findings located in that file, so a Dart edit never
blocks on a violation elsewhere in the package.

Every fixture package gains a barrel that imports its declared
dependencies, and analysis_options.yaml keeps the fixtures out of
dart analyze.

Closes #25

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Vxxv9fVFGupLXEnmx5bSq
@Statyk7
Statyk7 requested a review from a team as a code owner October 15, 2026 00:10
@Statyk7 Statyk7 self-assigned this Oct 15, 2026
Statyk7 and others added 3 commits October 15, 2026 07:07
data-barrel-exports-internals flagged every data-source export, which
reported five false positives on mealify_feature_first: the app is the
composition root and constructs the data sources to inject them into
each repository, so the data barrels export them on purpose. The rule
now covers DTOs and mappers only.

The data page's "Neither is visible outside the package" contradicts
mealify and is tracked in VGVentures/very_good_engineering#294.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Vxxv9fVFGupLXEnmx5bSq
From a review of #29, #30 and #31:

- Check imports of undeclared workspace packages against the rules
  table. A Dart workspace resolves them, so `product_presentation` could
  import `cart_data` without either mode noticing. A forbidden layer
  reports that layer rule; an allowed one reports the new
  undeclared-dependency error.
- On a pubspec edit, run import checks and suppression validation for
  the edited package only, so a clean edit never blocks on a
  dependent's existing Dart findings.
- Skip missing-primary-barrel in incremental mode while a package has no
  lib/, so a new package can be scaffolded pubspec first.
- Read directives with a scanner that stops at the first declaration,
  follows multi-line directives, and reads every URI, so imports inside
  strings are ignored and both sides of a conditional import are checked.
- Check every barrel at the root of a data package's lib/, following
  relative exports one level, for exported DTOs and mappers.
- Report a `//` suppression naming a pubspec-only rule, and group
  suppression findings under the file the comment is in.
- Fix CLAUDE.md, which said dart only runs for pubspec edits, and cite
  the barrel rule's actual sources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Vxxv9fVFGupLXEnmx5bSq
final root = _findWorkspaceRoot(file.parent.absolute.path);
if (root == null) return 0; // not FFCA-shaped

final workspace = _discover(root);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a Dart edit, _discover still reads and scans every Dart file in the workspace, though only the edited file's directives are reported. Since this now runs on every Dart Edit/Write, could discovery read Dart files lazily? For example, keep pubspec discovery as it is, but only call _readDartFiles for the edited package in --file mode, and for all packages in --all. That would keep the per-edit cost proportional to one package, not the whole monorepo.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants