Skip to content

FEDX-7267: Support Dart doc imports in dependency_validator - #197

Open
engops-wk wants to merge 9 commits into
masterfrom
cursor/support-dart-doc-imports-7257
Open

FEDX-7267: Support Dart doc imports in dependency_validator#197
engops-wk wants to merge 9 commits into
masterfrom
cursor/support-dart-doc-imports-7257

Conversation

@engops-wk

@engops-wk engops-wk commented Sep 8, 2026

Copy link
Copy Markdown

Opened by Dustin Pauze with the ai-sdlc workflow for FEDX-7267

Pull Request

Intent

This change adds support for Dart's @docImport documentation imports (introduced in Dart 3.8) when scanning package usage, so packages referenced only via doc imports in lib/ are no longer incorrectly flagged as unused. Since doc imports are not runtime dependencies, packages used only that way are treated like non-public usage for dependency promotion purposes — they can live in either dependencies or dev_dependencies without triggering an over-promotion warning, but they still must be declared somewhere in pubspec.yaml.

Changes

  • lib/src/import_export_ast_visitor.dart: Replaced getDartDirectivePackageNames/ImportExportVisitor.packageNames with a new getDartPackageUsage API returning a DartPackageUsage record that separates directivePackageNames (import/export) from docImportPackageNames (@docImport). Adds comment-token scanning (visitComment, _collectDocImportsFromPrecedingComments, _docImportUriPattern) to pick up doc imports both attached to AST nodes and dangling at the top of a file.
  • lib/src/dependency_validator.dart: Updated usage collection in checkPackage to call getDartPackageUsage instead of the removed API; doc-import-only packages found in lib/ are added to packagesUsedOutsidePublicDirs and excluded from the over-promoted dependencies set so they aren't flagged when placed in dev_dependencies. Also includes minor formatting cleanup (removed unused catch binding, reformatted multi-line expressions).
  • pubspec.yaml / .tool-versions / .github/workflows/ci.yaml: Raised minimum SDK to ^3.8.0 (required for doc import syntax support in the analyzer), bumped analyzer to >=8.0.0 <15.0.0, pubspec_parse to ^1.6.0, and package version to 6.0.0; CI and .tool-versions now pin Dart 3.8.1.
  • CHANGELOG.md: Documents the 6.0.0 release — doc import support, promotion rules for doc-import-only packages, removal of getDartDirectivePackageNames, and the new Dart 3.8 minimum requirement.
  • test/import_export_ast_visitor_test.dart (new): Unit tests for getDartPackageUsage covering plain imports/exports, doc imports in library/declaration/dangling comments, combined usage, show/as clauses on doc imports, and doc imports in bin/ files.
  • test/executable_test.dart: Adds an end-to-end doc imports test group covering passing/failing scenarios for dev_dependency-only, dependency-only, missing-from-pubspec, and unused-flag suppression cases for doc-import usage.
  • test/utils.dart, test/pubspec_to_json.dart: Adds an environment parameter to checkProject (with new requireDart38 constant) and replaces the ad hoc PubspecToJson.toJson extension with a pubspecToJson helper that strips null/empty fields, needed to keep generated sandbox pubspecs valid under pub.
  • test/workspace_test.dart, test/utils_test.dart, lib/src/constants.dart, lib/src/pubspec_config.dart: Formatting-only churn (re-indentation/re-wrapping) from running dart format under the updated SDK/formatter version, no behavioral change.

How To QA

  • 1. Run dart pub get then dart test (or the CI unit-test workflow) against Dart SDK 3.8.1 as configured in .tool-versions; confirm all tests pass, including the new test/import_export_ast_visitor_test.dart and the doc imports group in test/executable_test.dart.
  • 2. Manually verify the core scenario: create a package with a dev_dependency (e.g. meta) referenced only via /// @docImport 'package:meta/meta.dart'; in a lib/*.dart file, run dart run dependency_validator, and confirm it reports "No dependency issues found!" instead of flagging meta as unused or over-promoted.
  • 3. Verify the negative case: remove the package from pubspec.yaml entirely while still referencing it via @docImport in lib/; run the validator and confirm it now reports the package under "These packages are used outside lib/ but are not dev_dependencies:".
  • 4. Confirm a package declared as a regular dependency (not dev) and only used via doc import in lib/ is still accepted without an over-promotion warning.
  • 5. Confirm CI passes with the updated SDK pin (3.8.1) and analyzer/pubspec_parse version bumps in pubspec.yaml.

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
Comment thread lib/src/dependency_validator.dart Outdated
Comment thread test/executable_test.dart Outdated
Comment thread test/import_export_ast_visitor_test.dart
Comment thread lib/src/import_export_ast_visitor.dart

@engops-wk engops-wk left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review round 1 — CHANGES REQUESTED (posted as a comment; GitHub blocks REQUEST_CHANGES on one's own PR)

Solid, well-scoped approach: separating directivePackageNames from docImportPackageNames in a small value object and reusing _addPackageName is the right shape, and the test coverage intent (unit + executable-level) is good. But CI is red on this PR and the failures are directly caused by this change, so it can't merge as-is.

Failing CI checks (must be green before approval)

CI (.github/workflows/ci.yaml) runs three reusable jobs from Workiva/gha-dart-oss@v0.1.14, all pinned to sdk: 3.7.2:

  1. unit-tests (test-unit.yaml, Dart 3.7.2) — root cause. Doc imports are a Dart 3.8 language feature. parseString defaults to FeatureSet.latestLanguageVersion(), which on the 3.7.2 toolchain does not enable doc imports, so Comment.docImports is always empty. Every new assertion that expects a doc-import package (test/import_export_ast_visitor_test.dart "collects doc imports…" and "collects both…", plus the three positive/negative cases in the doc imports group in test/executable_test.dart) fails. The executable_test cases fail for a second, compounding reason: checkProject in test/utils.dart writes the fixture pubspec with requireDart36 (sdk: ^3.6.0), so the fixture project's language version is below 3.8 too.
  2. checks (checks.yaml, Dart 3.7.2)dart format --set-exit-if-changed fails. Several added lines exceed 80 cols / aren't dart format output (e.g. the first test(...) description in the new doc imports group, and the getDartPackageUsage(File('${d.sandbox}/project/main.dart'),) call wrapping in the new unit test file).
  3. build (build.yaml, Dart 3.7.2) — release/analyze surface: this is a user-facing behavior change with no CHANGELOG.md entry under # Unreleased and no version bump in pubspec.yaml; this repo auto-releases off those and every prior behavior change landed with a changelog entry.

Design concerns

  • Silent behavior regression for existing consumers. Merging doc-import-only lib/ usage into packagesUsedOutsidePublicDirs feeds the fatal overPromotedDependencies check. A package that doc-imports package:foo in lib/ and legitimately declares foo as a runtime dependency (very common — you doc-import what you also use) will start failing with exit 1 purely from upgrading. The ticket says these "likely only need to be dev_dependencies" — that's a floor, not a prohibition. Recommend treating doc-import-only packages as satisfied by either dependencies or dev_dependencies: keep them out of "missing" and "unused", but exclude them from over-promotion. If we deliberately want the stricter behavior, it needs a version bump plus README + CHANGELOG notes.
  • Test coverage gap on the traversal itself. All doc-import fixtures attach the comment to a library; directive. GeneralizingAstVisitor.visitComment is only reached because AnnotatedNode.visitChildren walks documentationComment; the un-exercised (and more common) case is a @docImport on a class/function doc comment in a file with no library; directive. Also worth cases for a doc import with a show/as clause and one in bin/.
  • Nit / follow-up (non-blocking): the file and class names (import_export_ast_visitor.dart, ImportExportVisitor) no longer describe what they do; consider renaming in a follow-up now that the return type is DartPackageUsage. getDartDirectivePackageNames was removed outright — it's under lib/src/, so not public API, but mention the removal in the changelog entry.

Please get all three CI jobs green and resolve the over-promotion semantics, then re-request review.

cursoragent and others added 2 commits September 8, 2026 19:33
- Exclude doc-import-only lib/ packages from over-promotion checks
- Bump Dart SDK to 3.8.1 in CI, tool-versions, and pubspec (^3.8.0)
- Pass explicit FeatureSet 3.8.0 to parseString for doc imports
- Add requireDart38 test fixture environment for doc import tests
- Expand AST visitor tests (declaration comments, show/as, bin/)
- Run dart format and bump version to 5.1.0 with CHANGELOG entry

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
- Run dart format across the repo with Dart 3.8.1
- Remove obsolete test/pubspec_to_json.dart (pubspec_parse now has toJson)
- Fix unused_catch_stack in dependency_validator.dart

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
Comment thread test/utils.dart
Comment thread lib/src/import_export_ast_visitor.dart Outdated
Comment thread pubspec.yaml Outdated
Comment thread CHANGELOG.md Outdated

@engops-wk engops-wk left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review round 2 — CHANGES REQUESTED (posted as a comment; GitHub blocks REQUEST_CHANGES on one's own PR)

Do not merge: CI is red, and there are two compatibility problems beyond the CI break.

The core model is right — separating @docImport usage from real directive usage and treating lib/ doc imports as non-runtime is the correct semantics, and test/import_export_ast_visitor_test.dart has good coverage (show/as clauses, bin/, dart: scheme, mixed).

Failing CI checks

  • unit-tests / test-unit (sdk 3.8.1)compile failure. test/pubspec_to_json.dart was deleted and import 'pubspec_to_json.dart'; removed from test/utils.dart, but checkProject() and checkWorkspace() still call pubspec.toJson(). Pubspec from pubspec_parse has no toJson(); it came only from the deleted PubspecToJson extension. Every test in executable_test.dart and workspace_test.dart fails, including the new doc imports group.
  • checks / analyze (sdk 3.8.1) — same error: The method 'toJson' isn't defined for the type 'Pubspec' in test/utils.dart.
  • build (sdk 3.8.1) — blocked by the same analysis error.

The deletion of test/pubspec_to_json.dart is unrelated to doc-import support and looks accidental.

Correctness / compatibility

  1. lib/src/import_export_ast_visitor.dart pins the parser feature set to language version 3.8.0 — strictly narrower than the previous default (FeatureSet.latestLanguageVersion()). Any consumer file using 3.9+ syntax will fail to parse and hit exit(1). Remove the pin.
  2. pubspec.yaml raises the SDK floor from ^3.0.0 to ^3.8.0 while releasing as 5.1.0 — a breaking constraint change in a minor release. Major bump, or keep the floor and detect capability dynamically.
  3. CHANGELOG says doc-import-only packages are "not flagged as ... missing," which contradicts the implementation and the new test fails when a package referenced via doc import in lib/ is missing from pubspec.

Non-blocking observations

  • Much of the diff in test/workspace_test.dart, lib/src/constants.dart, lib/src/pubspec_config.dart, test/utils_test.dart is dart format 3.8 style churn; call it out in the PR body so reviewers don't hunt for logic there.
  • packagesUsedViaDocImportInPublicFiles is both merged into packagesUsedOutsidePublicDirs and removed from overPromotedDependencies; a one-line comment at the declaration ("valid in either dependency section") would help future maintainers.
  • Consider a test asserting a doc import in a test/ file still counts as dev usage, to lock in the non-public-file branch.

Push the helper fix, get all three jobs green, and address items 1–3. Happy to re-review promptly.

- Remove hardcoded FeatureSet from parseString (use analyzer default)
- Bump version to 6.0.0 to reflect breaking SDK floor (^3.8.0)
- Fix CHANGELOG wording for doc-import dependency requirements
- Bump pubspec_parse to ^1.6.0 for native Pubspec.toJson() in tests

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
Comment thread test/import_export_ast_visitor_test.dart
Comment thread lib/src/import_export_ast_visitor.dart
Comment thread test/executable_test.dart
Comment thread test/utils.dart
Comment thread CHANGELOG.md Outdated
Comment thread pubspec.yaml Outdated

@engops-wk engops-wk left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review round 3 — CHANGES REQUESTED (posted as a comment; GitHub blocks REQUEST_CHANGES on one's own PR). CI is red — not approving.

CI failed before this review started, and the failures are reproducible from the diff.

Failing checks and root causes

1. unit-tests (Workiva/gha-dart-oss/.github/workflows/test-unit.yaml@v0.1.14, sdk 3.8.1) — failing. Two independent causes:

  • Whole suite broken by the test/pubspec_to_json.dart deletion. Round 2 flagged this as a compile error; it's now "fixed" by relying on pubspec_parse 1.6.0's real Pubspec.toJson(). That resolves compilation but not behavior: the generated _$PubspecToJson emits every field unconditionally, including 'version': null, 'description': null, 'repository': null, 'flutter': null, 'executables': {}, 'workspace': null, 'resolution': null. The deleted hand-rolled PubspecToJson extension deliberately emitted only name/environment/dependencies/dev_dependencies with if (x != null) guards. pub rejects version: null, so every checkProject/checkWorkspace-based test now writes an invalid pubspec.yaml into the sandbox and fails at resolve time. This is the broadest failure and it takes down pre-existing tests, not just the new ones.

  • New visitor tests assert behavior the visitor does not have. Four of the eight tests in test/import_export_ast_visitor_test.dart put the @docImport in a doc comment separated from the following declaration by a blank line and with no library; directive (...without a library directive, show clauses, as clauses, bin/ files). A /// block followed by a blank line is a dangling comment: the analyzer does not attach it to the next declaration, it never becomes an AnnotatedNode.documentationComment, and so GeneralizingAstVisitor.visitComment is never invoked for it. These tests expect {'meta'}/{'collection'} and will get isEmpty. This is exactly the traversal gap round 2 asked you to cover — the test was added, but written in a form that documents a real hole instead of closing it.

2. checks (checks.yaml@v0.1.14) — expected to fail on dart format --set-exit-if-changed. Round 2's formatting comment was addressed for the new files, but bumping environment: sdk to ^3.8.0 switches dart format to tall style for the entire package, and untouched files were not reformatted — e.g. bin/dependency_validator.dart (the argParser cascade and the two Logger.root.onRecord chains) and lib/src/utils.dart (listFilesWithExtensionIn chain) still carry short-style wrapping. Run dart format . on 3.8.1 and commit the full result.

Good progress since round 2

The over-promotion regression is genuinely fixed (..removeAll(packagesUsedViaDocImportInPublicFiles)), the hard-pinned featureSet was correctly dropped, the version is now 6.0.0 with the SDK floor documented, and the CHANGELOG wording no longer contradicts the tests. The core design — DartPackageUsage splitting directive vs. doc-import usage, and folding doc-import-only lib/ usage into the "used outside lib/" set — is the right model. Remaining blockers are correctness/CI, not architecture.

Also blocking

  • Real behavior gap behind the bad tests. The dangling-comment case isn't only a test bug: a library-level @docImport at the top of a file with no library; is silently missed and the package is still reported as unused. Fix the visitor (also scan unit.beginToken.precedingComments) and the tests, rather than reshaping tests to match the gap.
  • analyzer lower bound. >=7.1.0 <15.0.0 still permits analyzer 7.x, which has no Comment.docImports — a compile error for consumers who resolve an older analyzer, even though this repo resolves higher.
  • CHANGELOG structure. # Unreleased is now an empty heading sitting directly above # 6.0.0; consolidate so the publish workflow reads unambiguously.

Please push the fixes, get CI green, then re-request review.

cursoragent and others added 2 commits September 8, 2026 20:22
- Restore test/pubspec_to_json.dart with pubspecToJson() that omits null entries
- Scan beginToken.precedingComments for dangling file-level @DocImport comments
- Fix doc-import AST visitor tests and add regression for dangling comments
- Raise analyzer lower bound to >=8.0.0 for Comment.docImports support
- Remove empty CHANGELOG Unreleased heading

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
@dustinlessard-wf
dustinlessard-wf marked this pull request as ready for review September 9, 2026 13:15
@dustinlessard-wf
dustinlessard-wf requested a review from a team as a code owner September 9, 2026 13:15
@dustinlessard-wf

Copy link
Copy Markdown

@cursor Resolve the merge conflicts

@cursor

cursor Bot commented Sep 9, 2026

Copy link
Copy Markdown

Merge conflicts are resolved and pushed in commit 0224925.

Conflicts resolved

lib/src/import_export_ast_visitor.dart

  • Kept the PR’s getDartPackageUsage / DartPackageUsage API and doc-import handling (including dangling comment scanning).
  • Integrated master’s featureSetForSdkConstraint and optional featureSet parameter on parseString.

lib/src/dependency_validator.dart

  • Kept the PR’s doc-import promotion logic (packagesUsedViaDocImportInPublicFiles, packagesUsedOnlyViaDocImport, etc.).
  • Integrated master’s nested-package support and featureSet usage by calling getDartPackageUsage(file, featureSet: featureSet).

Verification

  • No remaining conflict markers.
  • dart analyze lib/ — clean.
  • All 13 doc-import tests pass.
  • 4 pre-existing test failures remain (analyzer/dart_style version conflicts in the sandbox environment, unrelated to this merge).

The branch cursor/support-dart-doc-imports-7257 is up to date with master and ready for CI.

Open in Web Open in Cursor 

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
@dustinlessard-wf

Copy link
Copy Markdown

@cursor Resolve the failing format and analyze checks

@cursor

cursor Bot commented Sep 9, 2026

Copy link
Copy Markdown

Fixed and pushed in commit c8374fb.

Analyze

  • test/nested_packages_test.dart: Replaced all pubspec.toJson() calls with pubspecToJson(pubspec), so the pubspec_to_json.dart import is used and sandbox pubspecs omit null fields that pub rejects.

Format

  • Ran dart format on files flagged by CI:
    • lib/src/pubspec_config.g.dart
    • test/nested_packages_test.dart
    • test/utils_test.dart

Verification

  • dart analyze — no issues
  • dart format --set-exit-if-changed . — clean
Open in Web Open in Cursor 

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
@dustinlessard-wf

Copy link
Copy Markdown

@cursor the ci format check is still failing

@cursor

cursor Bot commented Sep 10, 2026

Copy link
Copy Markdown

Fixed and pushed in commit 45ec07a.

The format check was failing because lib/src/pubspec_config.g.dart was formatted with Dart 3.13.3 locally, while CI uses 3.8.1 (per .tool-versions). The formatters disagree on how to break the multi-line $checkedCreate call in that generated file.

Reformatted pubspec_config.g.dart with Dart 3.8.1; dart format --set-exit-if-changed . now passes with the CI SDK version.

Open in Web Open in Cursor 

Co-authored-by: Dustin Pauze <dustin.pauze@workiva.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants