Skip to content

fix: honor an excluded tag on an entry re-export - #2062

Merged
webpro merged 1 commit into
webpro-nl:mainfrom
devYRPauli:fix/entry-reexport-exclude-tag
Sep 28, 2026
Merged

webpro merged 1 commit into
webpro-nl:mainfrom
devYRPauli:fix/entry-reexport-exclude-tag

Conversation

@devYRPauli

Copy link
Copy Markdown
Contributor

With includeEntryExports, an export that an entry file re-exports is skipped when the re-export has @public. An excluded custom tag on the same re-export does not do this. It hides the re-export in the entry file, but the original export is still reported.

// index.ts (entry)
/** @lintignore */
export { somethingToIgnore } from './module.js';

With tags: ["-lintignore"] and --include-entry-exports, main reports somethingToIgnore in module.ts. The same re-export with @public reports nothing.

The check in analyze.ts that skips an entry re-export reads only shouldIgnore, which covers @public, @beta, @alias and @internal in production mode:

if (reExportedItem && shouldIgnore(reExportedItem.jsDocTags)) continue;

It now also skips the export when the re-export has an excluded tag (options.tags[1]). hasTag in util/tag.ts is now exported for this.

Included tags (+tag) are not checked here on purpose. In +tag mode, shouldIgnoreTags is true for every untagged export, so an untagged re-export would hide the export it points to. The +lintignore output of the new fixture is the same on main and on this branch.

The new fixture fixtures/re-exports/excluded-tag sits next to fixtures/re-exports/public. It has one tagged re-export and one untagged re-export as a control:

export main this branch
somethingToIgnore in module.ts reported not reported
somethingUnused in index.ts reported reported
somethingUnused in module.ts reported reported

Verified:

node --test test/re-exports/excluded-tag.test.ts   main: 0 passed, 1 failed; branch: 1 passed
bun test test/re-exports test/tags-hints test/entry  100 passed, 0 failed
node scripts/run-test.js --runtime node --smoke     1165 passed, 0 failed
tsc --noEmit                                        exit 0
pnpm run ci                                         exit 0
pnpm knip && pnpm knip:production                   no findings

With includeEntryExports, an export that an entry file re-exports is
skipped when the re-export carries @public, @beta or @alias. An excluded
custom tag such as @lintignore on the same re-export hid only the
re-export itself, and the original export was still reported.

The entry re-export check now also skips the export when the re-export
has an excluded tag. Included tags are not checked here, because an
untagged re-export would then hide every export in +tag mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Sep 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 557fd4e

@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 pull request, and for the clear before and after table! The entry re-export check honored custom tags when it was added in 9ccefb3 and lost them when 78255fc moved custom tags out of getShouldIgnoreHandler for tag hints, so this restores that behavior for excluded tags. It also matters for --fix: with --include-entry-exports, main removes the export from module.ts while the tagged re-export still points to it, and the code no longer compiles. The new test fails on main and passes here. Specifier-level tags, barrels in between, type and default re-exports, and mixed +/- tags behave as expected, and leaving included tags out is the right call. One optional note inline.

@@ -217,7 +217,11 @@ export const analyze = async ({
// Skip exports if re-exported from entry file and tagged
if (!isReferenced) {
const reExportedItem = graph.get(reExportingEntryFile)?.exports.get(identifier);

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.

Optional, and not introduced here: this lookup uses the export name from the origin file, so it only finds the entry export when the re-export keeps that name. With this change, excluded tags also share a side effect that @public already has on main, where an unrelated tagged export with the same name in the entry hides the origin:

// index.ts (entry)
export { default as Button } from './button.js';

/** @lintignore */
export default function App() {}

Button is still reported in index.ts, but default in button.ts is not. In the other direction, a tag on export { apple as green } from './module.js' has no effect on apple. Looking up the name the entry exports it under, which isReferenced already has when it reaches the entry, would fix both, for every tag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in #2069. The check now reads every name that the entry re-exports the export under. Both examples above are fixed, for every tag.

One case needs the full set of names: /** @public */ export { pear } next to an untagged export { pear as fruit }. Main handles it, and a check that reads only the first name breaks it. The re-exports/public fixture now covers it.

@webpro
webpro merged commit b22e275 into webpro-nl:main Sep 28, 2026
29 of 32 checks passed
@webpro

webpro commented Sep 30, 2026

Copy link
Copy Markdown
Member

🚀 This pull request is included in v6.39.0. See Release 6.39.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.

2 participants