Skip to content

Fix strict mode for caught optional peer imports - #2218

Merged
webpro merged 6 commits into
webpro-nl:mainfrom
jay23606:fix/2214-optional-peer-try-catch
Oct 27, 2026
Merged

webpro merged 6 commits into
webpro-nl:mainfrom
jay23606:fix/2214-optional-peer-try-catch

Conversation

@jay23606

@jay23606 jay23606 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Recognize only a directly awaited dynamic import inside a try/catch as a caught optional peer reference.
  • Preserve diagnostics for undeclared packages and missing relative imports, and preserve the existing new URL() optional peer behavior.
  • Keep optional peer type dependencies referenced in default and strict modes.

Tests

  • Before the fix: the focused caught-import regression failed because the declared optional peer was reported as unlisted.
  • After the fix: bun test test/dependencies/peer-dependencies-optional-strict.test.ts β€” 13 passed.
  • pnpm run build β€” passed.
  • pnpm run lint β€” passed.
  • git diff --check β€” passed.

Not verified

  • The wider test suite and smoke suite were not run; only the affected test file was run.

Fixes #2214

πŸ€– Generated with Claude Code

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>

@webpro webpro left a comment

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.

Thanks for picking this up, Jay, the scope is right: a directly awaited import() inside try with a catch, nothing else. The implementation reuses IMPORT_FLAGS.OPTIONAL though, and that flag already means "no error if not resolved" for new URL(x, import.meta.url) and /// <reference path>. Reusing it changes behavior in both directions, and the new fixture never reaches the code that implements the policy. Details inline.

Suggested shape for the next round:

  • Add a separate CAUGHT flag in IMPORT_FLAGS and leave OPTIONAL untouched.
  • Set it only when the ImportExpression is the direct argument of an AwaitExpression.
  • Pass it through DependencyReferenceOptions and let DependencyDeputy.isInDependencies accept optional peers when strict and caught, so @types attribution and the default mode stay as they are. Then the analyze.ts change is not needed.
  • Track try protection in the walker with the existing TryStatement and :exit hooks instead of re-walking the block.
  • Install optional-peer in the fixture's node_modules (as peer-dependencies-optional-strict-unreferenced does), assert counters, and add regressions for a caught import of an unlisted package and of a missing local file.

Comment thread packages/knip/src/graph/analyze.ts Resolved
@pkg-pr-new

pkg-pr-new Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/knip@2218
npm i https://pkg.pr.new/@knip/language-server@2218
npm i https://pkg.pr.new/@knip/mcp@2218

commit: 1f8b07c

jay23606 and others added 2 commits October 25, 2026 20:18
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>

@webpro webpro left a comment

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.

Thanks for the quick turnaround, this is much closer. The separate CAUGHT flag, the deputy-owned policy, the direct-argument await check and the stack-based try tracking all do what they should, and my earlier probes match main again. Two things remain, and the first one is what #2214 is about: const mod = await import('pg') inside try/catch is still unlisted in strict mode. The other is that the new URL case was turned into a new policy instead of being left as it was. Details inline, plus two branches that turned out to be unnecessary and can go.

Optional: the strict mode section in packages/docs/src/content/docs/features/production-mode.md could mention that an optional peer is accepted when its awaited import() is wrapped in try/catch.

Comment thread packages/knip/src/DependencyDeputy.ts Resolved
Comment thread packages/knip/src/DependencyDeputy.ts Resolved
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>

@webpro webpro left a comment

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.

All five points from the last round are addressed, thanks. The declarator forms go through the try stack, OPTIONAL is back to its original meaning, the two redundant branches are gone, and the fixtures are precise. My probe set against main shows default mode unchanged everywhere and strict mode changing only for the caught forms. Two small things are left before this can go in: a formatting failure in walk.ts that CI will flag, and Promise.all being accepted in only one of its forms. Both inline.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>

@webpro webpro left a comment

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.

Thanks for the quick follow-up! Both points are fixed: the formatting check passes, and an awaited Promise.all is now caught in the statement, return and assignment forms too, with the fixture covering the statement form next to the destructured one. Default mode is still unchanged, and strict mode only changes for the caught forms.

One more change before merging, see the inline comment. I also noticed the commits are co-authored by Claude Haiku 5.5. The walker is core logic that every Knip run goes through, so for changes like this please use a stronger model and review its output closely before pushing.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>

@webpro webpro left a comment

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.

Thanks for the refactor! getPromiseAllElements is now the single owner of the check, both call sites use it without changing behavior, and the walker's hot path is the same as before. Looks good to me.

@webpro
webpro merged commit e535f40 into webpro-nl:main Oct 27, 2026
32 checks passed
@webpro

webpro commented Oct 29, 2026

Copy link
Copy Markdown
Member

πŸš€ This pull request is included in v6.44.0. See Release 6.44.0 for release notes.

Using Knip in a commercial project? Please consider becoming a sponsor.

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.

πŸ› Try/catch imports of optional peer dependencies fail as "Unlisted dependencies" in strict mode

2 participants