Skip to content

RE_EXCLUDE class regex doesn't match classes with extends, causing false auto-import #518

Description

@posva

Description

When a file declares a class with extends and references its own name internally (e.g. Object.setPrototypeOf(this, SystemError.prototype)), unimport incorrectly injects an import for that class, causing an "Identifier has already been declared" error.

Root cause

The RE_EXCLUDE class regex in src/regexp.ts:10 is:

/\bclass\s*([\w$]+)\s*\{/g

This matches class Foo { but not class Foo extends Bar { — the extends Bar part breaks the pattern since it expects \s*\{ immediately after the class name.

As a result, the class name is not removed from the occurrenceMap in detect-regex.ts, and any self-reference within the class body gets auto-imported.

Reproduction

Failing unit test against current main:

it('should not inject import for class with extends that references itself', async () => {
  const { injectImports } = createUnimport({
    imports: [{ name: 'SystemError', from: 'test-id' }],
  })
  const code = `export class SystemError extends Error {
  constructor(message) {
    super(message);
    Object.setPrototypeOf(this, SystemError.prototype);
  }
}`
  expect((await injectImports(code)).code).toBe(code)
})

Expected: no import injected (class is locally declared)
Actual: import { SystemError } from 'test-id'; is injected at the top

Suggested fix

Simplify the regex to only capture the class name without requiring the opening brace:

/\bclass\s+([\w$]+)/g

This correctly handles extends, implements, generics, multiline declarations, etc. since we only need the name.

Downstream

Reported in nuxt/nuxt#26516

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions