JS-1475 Fix false positives in S7763 for local exports flagged as re-export candidates#6644
Conversation
Tests cover the scenario where locally-defined exports using the `export const alias = importedThing` pattern are incorrectly flagged as re-export candidates. The tests verify that ExportNamedDeclaration nodes with a declaration (single and multiple aliases, adapter/facade patterns) are not flagged, while genuine named re-exports still raise. An upstream sentinel test confirms the unicorn rule still raises on these patterns so the decorator fix remains valid. Relates to JS-1475
Local exports are incorrectly flagged as re-export candidates when identifiers are defined in the same file. The decorator now suppresses two additional cases: (1) ExportNamedDeclaration with a declaration property (e.g. `export const alias = importedThing`), where the binding is a locally defined variable; and (2) ExportSpecifier nodes where the local identifier name does not appear in any import specifier, meaning the identifier is locally defined rather than imported. A defensive suppression is also added when the identifier name cannot be extracted. Relates to JS-1475
No implementation changes were needed: ruling analysis confirmed that all 73 entries currently raising are genuine re-export patterns (import then export) and the 4 entries not raising are `export const alias = imported` patterns correctly suppressed by the decorator. Added one new test case covering the namespace import alias pattern found in ruling data: `import * as _AllIcons from './svgs'; export const AllIcons = _AllIcons` should not be flagged, consistent with the existing suppression of ExportNamedDeclaration nodes that carry a declaration.
Ruling ReportCode no longer flagged (2 issues)S7727it-tools/src/tools/qr-code-generator/useQRCode.ts:19 17 |
18 | watch(
> 19 | [text, background, foreground, errorCorrectionLevel].filter(isRef),
20 | async () => {
21 | if (get(text)) {it-tools/src/tools/wifi-qr-code-generator/useQRCode.ts:117 115 |
116 | watch(
> 117 | [ssid, password, encryption, eapMethod, isHiddenSSID, eapAnonymous, eapIdentity, eapPhase2Method, background, foreground].filter(isRef),
118 | async () => {
119 | // @see https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 |
Remove unnecessary type assertion in decorator.ts (typescript:S4325). After the `node.type === 'ExportNamedDeclaration'` discriminated union check, TypeScript already narrows `node` to `estree.ExportNamedDeclaration`, making the explicit `(node as estree.ExportNamedDeclaration)` cast redundant.
SummaryThis PR eliminates false positives in rule S7763 (prefer-export-from) where locally-defined exports were incorrectly flagged as re-export candidates. Key distinction: What changed:
What reviewers should knowStart here: Core logic: The Watch for: The sentinel test at lines 23-44 of the unit file. It verifies that the upstream ESLint rule ( False positives fixed: The ruling file deletions (rxjs-S7763.json completely removed, one entry removed from eigen) represent real codebases where this pattern legitimately appears. The patterns (adapter/facade exports, namespace aliases) are documented in the test cases.
|
Summary
S7763 false positives have been fixed by making the decorator more precise about which export patterns can be converted to The problem: The rule was flagging locally-defined exports and default import aliases as re-export candidates, even though these patterns cannot be rewritten as The solution: Added explicit checks before forwarding reports from the upstream rule:
The logic refactors three helper functions into a single What reviewers should knowStart with: The decorator logic in Key logic:
Test changes: The new test cases (lines 61–71 in One unrelated fix: The TypeScript version validation in
|
|
Thanks for the summary — the description accurately captures the intent and approach of this fix. |
|
Thanks for the summary — the description accurately captures the three suppression conditions added to the decorator and the net effect on ruling results. |
…ules/S7763/decorator.ts Comment: **Logic duplication:** `isFromImport` and `isDefaultImport` both walk `sourceCode.ast.body`, enter each `ImportDeclaration`, and scan specifiers by `specifier.local.name`. Since `isDefaultImport` is only ever called when `isFromImport` already confirmed the identifier exists in an import, the AST is traversed twice for that name. A single helper avoids the double pass and keeps the detection logic in one place: ```ts function getImportKind( sourceCode: SourceCode, identifierName: string, ): 'default' | 'named' | null { for (const node of sourceCode.ast.body) { if (node.type !== 'ImportDeclaration') continue; for (const specifier of node.specifiers) { if (specifier.local.name !== identifierName) continue; if ( specifier.type === 'ImportDefaultSpecifier' || (specifier.type === 'ImportSpecifier' && (specifier.imported.type === 'Identifier' ? specifier.imported.name : String(specifier.imported.value)) === 'default') ) { return 'default'; } return 'named'; } } return null; } ``` The call site becomes: ```ts const importKind = getImportKind(context.sourceCode, identifierName); if (!importKind || importKind === 'default') return; context.report(reportDescriptor); ``` If traversal logic ever needs to change (e.g. handling `export … from` re-declarations), there is only one place to update. - [ ] Mark as noise
…ules/S7763/decorator.ts Comment: Is suppressing all `ExportNamedDeclaration` nodes with a `declaration` intentional for *every* possible declared type, or only `VariableDeclaration`? The unicorn rule can also report on `export function foo() {}` and `export class Foo {}` where the function/class body references an imported value. Those nodes also have `declaration !== null`. Suppressing them all is conservative and avoids FPs, but it means the rule goes silent on cases it might legitimately catch — worth a comment confirming the intended scope. - [ ] Mark as noise
|
Thanks for the review! Both questions have been addressed in the latest commits:
|
|
Thank you for the review! |
|
Both questions from this review have been addressed: the |
s7763-local-exports-incorrectly-flagged-as-re-export-candidates-sonnet
|
|
I reviewed this change with the goal of fixing JS-1475 without reducing S7763 detection coverage. I think we should request changes before merging. Main concern:
Suggested adjustment:
Also worth considering: instead of broad decorator suppression, evaluate whether we can use unicorn?s |
…iases in S7763 The blanket suppression of ExportNamedDeclaration with declaration !== null was too broad: it dropped detection for `import {x}...; export const y = x` and `import * as ns...; export const N = ns`, which ARE genuine re-export candidates that unicorn can rewrite as `export { x as y } from '...'` and `export * as N from '...'`. Replace the blanket suppress with selective VariableDeclaration handling: - FunctionDeclaration/ClassDeclaration remain fully suppressed (always local) - VariableDeclaration: check each declarator's init via getImportKind; report if the RHS is a named/namespace import, suppress otherwise Also change the !identifierName guard to fail-open (report) so unknown upstream node shapes do not silently lose detection. Restore ruling expectations for rxjs/src/observable/{concat,merge,zip}.ts and eigen/src/palette/index.tsx which are genuine re-export patterns removed by the previous over-broad suppression. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
You're right on all counts. The blanket suppression was too broad and dropped detection for genuine re-export candidates. Changes made:
Regarding |
The upstream unicorn/prefer-export-from rule changed its autofix output
for default import re-exports: it now generates
`export {default as foo} from './foo'` instead of `export {foo} from './foo'`.
Update the sentinel test's expected output to match the new upstream behavior.
The sentinel test purpose (verifying the upstream rule still raises on these
patterns) is unchanged — it still expects errors: 1.
…ing nightly version
The test was asserting an exact nightly TypeScript version string
('7.0.0-dev.20260316.1') which changes with each nightly build. Use the
same approach as master: check that versions are non-empty and valid
semver strings rather than matching a specific nightly tag.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
vdiez
left a comment
There was a problem hiding this comment.
Looks good.
I reviewed the S7763 decorator changes with focus on FN risk. The updated import-kind handling keeps reporting named/namespace re-export candidates while suppressing the intended local/default-import FP patterns, and the added coverage plus sentinel test are solid.
Approved.




Rule Profile
S7763— Re-exports should use "export...from" syntaxrecommended: true)Genuine violation:
False Positive Pattern
The rule is implemented as a decorator around the upstream
unicorn/prefer-export-fromESLint rule. The unicorn rule reports on several node types without distinguishing whether the exported identifier was imported from another module or defined locally. This means it flags exports of locally-defined functions, classes, and constants — patterns that cannot be collapsed intoexport { x } from '...'syntax because there is no corresponding import to merge with.FP 1 —
export { localFn }wherelocalFnis defined in the same file:FP 2 —
export const alias = defaultImport(default import renamed and re-exported):FP 3 —
export const alias = localVar(local variable aliased and exported):FP 4 —
export function foo() {}/export class Foo {}(function/class declaration exports):False Negative Risk
The fix is conservative and well-bounded. It suppresses only cases where no corresponding import exists for the exported identifier, or where the import kind is
'default'(which unicorn'signoreDeclarationSortoption cannot rewrite). Named and namespace import aliases remain fully reported.The one potential false negative is the case where a developer writes
export const y = xandxis a named import — this is still reported as a genuine re-export candidate. The only shape that could be missed is a future upstream change to the unicorn rule's reported node types; to guard against this, the decorator is fail-open for unknown node shapes (it reports rather than suppresses).Genuine violation that will NOT be suppressed:
Implementation
How the rule works today: S7763 is a decorator around
unicorn/prefer-export-from. It intercepts every report from the upstream rule and decides whether to forward or suppress it, based on the reported node type and the identifier's binding in the file's import declarations.What changed:
1. Selective handling of
ExportNamedDeclarationwith a declaration:Previously, the decorator fell through to the
defaultcase ingetReportedIdentifierName()forExportNamedDeclarationnodes, returningundefinedas the identifier name, which caused the guard that checks for imports to be bypassed — every such report was forwarded. The fix intercepts this node type early and checks each declarator's initializer viagetImportKind().2. Single
getImportKind()helper replacing two separate traversals:The previous implementation had separate
isFromImport()andisDefaultImport()helpers that both walked the full AST. This refactoring into a single helper eliminates the double traversal and keeps all import classification logic in one place. Anullreturn means the identifier is not imported at all (locally defined).3. Fail-open for unknown node shapes:
The previous code suppressed (
return) whenidentifierNamewasundefined. The updated code reports instead, ensuring that if the upstream unicorn rule starts producing new node types, detections are not silently lost.Technical Summary
packages/analysis/src/jsts/rules/S7763/decorator.ts— Core fix: adds selective suppression forExportNamedDeclarationwith a declaration, refactors two AST-walking helpers into a singlegetImportKind()utility, and flips the!identifierNameguard to fail-open.packages/analysis/src/jsts/rules/S7763/unit.test.ts— Adds test cases for all four suppressed FP patterns and verifies that named/namespace import aliases are still reported; includes a sentinel test confirming the upstream unicorn rule still raises on these patterns.packages/analysis/tests/jsts/analysis/analyzeProject-sonarqube.test.ts— Unrelated flakiness fix: validates TypeScript version strings withsemver.valid()instead of asserting a specific nightly version tag.Net: 129 lines added, 71 lines removed across 3 files.