Avoid Star Import

ID

javascript.avoid_star_import

Severity

low

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

JavaScript

Tags

code-style, imports

Description

Reports a namespace import, import * as ns from 'module', which binds every export of the module behind one name instead of naming what the file uses. The namespace half of a mixed import (import defaults, * as ns from 'module') is reported too.

Only import is examined. export * from 'module' is a re-export rather than a use — it makes this module’s own surface a pass-through, which is a different decision — and import('module') resolves to a namespace object by definition, with no alternative form to prefer.

Two namespace imports are left alone, both because the advice cannot be followed there.

A type-only namespace import, import type * as X from 'module', is erased at compile time and creates no runtime binding at all: there is nothing for a bundler to keep and nothing for a rename to break at run time.

The JSX factory in a file that contains JSX is the other. Under the classic JSX transform — the default, and what "jsx": "react" selects — <div/> compiles to React.createElement('div'), so the factory namespace has to be bound in the file whether or not the source ever writes React. itself, and no list of named imports can stand in for it. The name is the compiler’s rather than a library’s: TypeScript’s jsxFactory and Babel’s @jsx pragma both rename it, so it is read from the jsxFactory property. The exemption applies only where the markup is; the same import in a file with no JSX is an ordinary namespace import and is reported.

Rationale

// Bad
import * as utils from './utils';

export function run(input) {
  return utils.format(utils.parse(input));
}
  • The import line stops being a summary of the file’s dependencies. To know which of the module’s exports matter here, a reader has to search the body for utils., and a reviewer cannot see from a diff whether the file started using more of the module.

  • It defeats tree-shaking. A bundler that cannot see which members are reachable has to keep the whole module, so the namespace form is paid for in shipped bytes.

  • A rename inside the imported module stops failing at the import and starts failing at the use site — at run time, as undefined is not a function, rather than as a resolution error the tooling can point at.

This rule is opinionated by design and reports every namespace import rather than judging when one has earned its keep. import * as path from 'path' is idiomatic in plenty of codebases, and a project that wants it should turn the rule off rather than have the rule guess.

Remediation

Import the names the file actually uses.

// Good
import { format, parse } from './utils';

export function run(input) {
  return format(parse(input));
}

When the namespace genuinely is the point — a module of many small helpers where the prefix is what makes the call sites readable — keep it and turn the rule off for the project rather than working around it. And when a name would collide, import { parse as parseUtil } from './utils' renames one binding without giving up the explicit list.

Configuration

properties:
  # The namespace the JSX transform calls into, as TypeScript's compiler option of the same name
  # spells it. Set it to match a project that renames the factory.
  jsxFactory: React

© 2026 Xygeni. All rights reserved