Skip to content

fix: prevent EXPORT_DECAL_RE from crossing newlines into multi-line object literals#347

Open
RodrigoCarvalhoCode wants to merge 2 commits into
unjs:mainfrom
RodrigoCarvalhoCode:fix/export-decal-re-multiline-object-false-positives
Open

fix: prevent EXPORT_DECAL_RE from crossing newlines into multi-line object literals#347
RodrigoCarvalhoCode wants to merge 2 commits into
unjs:mainfrom
RodrigoCarvalhoCode:fix/export-decal-re-multiline-object-false-positives

Conversation

@RodrigoCarvalhoCode

@RodrigoCarvalhoCode RodrigoCarvalhoCode commented May 7, 2026

Copy link
Copy Markdown

Summary

Fixes #303, related to nuxt/nuxt#34997

findExports incorrectly picks up object property keys as named exports when scanning multi-line TypeScript files. The extraNames capture group in EXPORT_DECAL_RE used \s* and [\s\w:[\]{}]*, which allowed the regex to span across newlines into multi-line object literals passed as call arguments.

Reproduction:

findExports(`export const useStore = defineStore('id', {\n  state: () => ({}),\n  actions: { foo() {} }\n})`)
// before: ['useStore', 'actions']  ❌
// after:  ['useStore']             ✅

Changes

  • src/analyze.ts: In EXPORT_DECAL_RE, change the extraNames group:
    • \s*[^\S\n]* (horizontal whitespace only — no newlines after comma)
    • [\s\w:[\]{}]*[\w:[\]{}]* (drop \s to prevent crossing lines)
  • test/exports.test.ts: Add regression test for the above case

Test plan

  • New regression test fails before fix, passes after
  • All existing 199 tests continue to pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved export declaration pattern matching so multi-line object literals inside exported initializers no longer cause incorrect exported names to be detected.
  • Tests

    • Added a regression test ensuring object property keys inside variable initializers are not reported as additional exports.

…bject literals

The extraNames capture group in EXPORT_DECAL_RE used \s* and [\s\w:[\]{}]*
which allowed the regex to span across newlines, causing object property keys
(e.g. `actions`) in multi-line call arguments to be picked up as false-positive
export names.

Replace \s* with [^\S\n]* and drop \s from [\s\w:[\]{}]* so the group only
matches horizontal whitespace, keeping it confined to a single line.

Fixes unjs#303, related to nuxt/nuxt#34997

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 41e8fb58-4c13-4600-a0af-1ee6977d9fca

📥 Commits

Reviewing files that changed from the base of the PR and between 9e658d1 and 37b7964.

📒 Files selected for processing (2)
  • src/analyze.ts
  • test/exports.test.ts

📝 Walkthrough

Walkthrough

This PR tightens the EXPORT_DECAL_RE regex in src/analyze.ts to restrict whitespace captured in extraNames, and adds a regression test in test/exports.test.ts ensuring object property keys inside multi-line initializers are not misinterpreted as exported names.

Changes

Export Pattern Refinement

Layer / File(s) Summary
Pattern Fix
src/analyze.ts
EXPORT_DECAL_RE's extraNames capture group is tightened by replacing \s* with [^\S\n]*, excluding newlines from captured horizontal whitespace.
Regex Usage
src/analyze.ts
Subsequent code continues to use EXPORT_DECAL_RE to extract and process extraNames with no other logic changes.
Regression Test
test/exports.test.ts
Adds a test that passes a defineStore initializer containing a multi-line object literal (with actions: { foo() {} }) to findExports and asserts the result contains only useStore.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • unjs/mlly#336: Both PRs change how extra export names are extracted in src/analyze.ts (this PR tightens the regex whitespace capture while #336 replaces regex parsing with a dedicated parser).

Suggested labels

bug

Suggested reviewers

  • pi0

Poem

🐰 A tweak to the pattern, subtle and neat,
Whitespace confined so exports are sweet.
Tests hop in, guarding the gate,
Only useStore crosses — how great!
—Rabbit of Code

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and accurately summarizes the main change: it identifies the specific regex component (EXPORT_DECAL_RE) being fixed and its primary issue (preventing newline crossing in multi-line object literals), which directly corresponds to the core modification in src/analyze.ts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/exports.test.ts (1)

469-475: ⚡ Quick win

Regression test placed in the wrong describe block

The new test exercises findExports, but it sits inside describe("resolveModuleExportNames"). It should be moved into describe("findExports") alongside the other single-export declaration tests.

♻️ Suggested move

Remove lines 469–475 from the resolveModuleExportNames block and add them to the findExports describe block (before its closing }):

-  // https://github.com/unjs/mlly/issues/303
-  it("does not pick up object property keys from multi-line object literals as extra export names", () => {
-    const code = `export const useStore = defineStore('id', {\n  state: () => ({}),\n  actions: { foo() {} }\n})`;
-    const matches = findExports(code);
-    expect(matches).toHaveLength(1);
-    expect(matches[0].names).toEqual(["useStore"]);
-  });
 // Inside describe("findExports") — before its closing });
+  // https://github.com/unjs/mlly/issues/303
+  it("does not pick up object property keys from multi-line object literals as extra export names", () => {
+    const code = `export const useStore = defineStore('id', {\n  state: () => ({}),\n  actions: { foo() {} }\n})`;
+    const matches = findExports(code);
+    expect(matches).toHaveLength(1);
+    expect(matches[0].names).toEqual(["useStore"]);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/exports.test.ts` around lines 469 - 475, The test for "does not pick up
object property keys..." is exercising findExports but was added under
describe("resolveModuleExportNames"); move that it(...) block out of the
resolveModuleExportNames describe and insert it into the describe("findExports")
block (place it with the other single-export declaration tests, before that
describe's closing brace) so the test logically lives with other findExports
tests and uses the same context for findExports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/analyze.ts`:
- Line 262: The EXPORT_DECAL_RE regex no longer matches comma-separated sibling
bindings that occur on subsequent lines (e.g., "export const a = longFn(...), b
= 1" where b is on the next line); add a concise comment immediately above the
EXPORT_DECAL_RE declaration explaining this limitation: note that extraNames is
intentionally constrained to not cross newlines, so multi-line comma-separated
export bindings will be missed and are a known limitation (include an example
and rationale that this avoids false positives), and reference the
EXPORT_DECAL_RE symbol and the named capture groups (declaration, name,
extraNames) so future maintainers can find and adjust the regex if needed.

---

Nitpick comments:
In `@test/exports.test.ts`:
- Around line 469-475: The test for "does not pick up object property keys..."
is exercising findExports but was added under
describe("resolveModuleExportNames"); move that it(...) block out of the
resolveModuleExportNames describe and insert it into the describe("findExports")
block (place it with the other single-export declaration tests, before that
describe's closing brace) so the test logically lives with other findExports
tests and uses the same context for findExports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 85bae9b0-059b-4707-b123-4df8ad1f250b

📥 Commits

Reviewing files that changed from the base of the PR and between c5ce4e5 and 9e658d1.

📒 Files selected for processing (2)
  • src/analyze.ts
  • test/exports.test.ts

Comment thread src/analyze.ts
- Move regression test into describe("findExports") where it belongs
- Add comment on EXPORT_DECAL_RE documenting the known limitation that
  sibling bindings starting on a new line won't be captured by extraNames,
  with rationale and issue reference (unjs#303)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

Commas in export can cause findExports / findExportNames to add incorrect export

1 participant