oxc-project / oxc-project/backlog

AST: Encode Import Declaration Forms as Clause Variants

Open
#220 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
No language data
Stars
7
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Parent: oxc-project/backlog#210

Summary

Replace ImportDeclaration::specifiers, phase, and import_kind with an ImportClause enum that represents the valid side-effect, value, type-only, source-phase, and defer-phase forms.

Within value and type-only imports, use narrower clause variants for default, named, and namespace bindings so invalid specifier combinations and ordering are unrepresentable.

Motivation

ImportDeclaration currently stores an optional vector of unrestricted specifier variants:

pub struct ImportDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub specifiers: Option<Vec<'a, ImportDeclarationSpecifier<'a>>>,
    pub source: StringLiteral<'a>,
    pub phase: Option<ImportPhase>,
    pub with_clause: Option<Box<'a, WithClause<'a>>>,
    pub import_kind: ImportOrExportKind,
}

pub enum ImportDeclarationSpecifier<'a> {
    ImportSpecifier(Box<'a, ImportSpecifier<'a>>) = 0,
    ImportDefaultSpecifier(Box<'a, ImportDefaultSpecifier<'a>>) = 1,
    ImportNamespaceSpecifier(Box<'a, ImportNamespaceSpecifier<'a>>) = 2,
}

The vector permits combinations that are not in the grammar:

  • multiple default or namespace specifiers;
  • a default specifier after a named or namespace specifier;
  • named and namespace specifiers in the same declaration;
  • any specifiers on a side-effect import;
  • a source-phase import with named or namespace specifiers;
  • a defer-phase import with default or named specifiers;
  • a type-only import combining a default binding with named or namespace bindings.

The parser validates these relationships procedurally, but builders and transformations can create
invalid states directly. Codegen then reconstructs grammar from vector order using mutable state.

Option<Vec<_>> also carries a syntax distinction that the type does not explain:

None     => import "mod"
Some([]) => import {} from "mod"

Valid Syntax Forms

import "mod";

import value from "mod";
import { a, b as c } from "mod";
import value, { a } from "mod";
import * as ns from "mod";
import value, * as ns from "mod";

import type T from "mod";
import type { T } from "mod";
import type * as Types from "mod";

import source wasm from "mod";
import defer * as ns from "mod";

Proposed AST Shape

Keep the source and import attributes on one shared declaration and encode the grammar in its clause:

pub struct ImportDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub clause: ImportClause<'a>,
    pub source: StringLiteral<'a>,
    pub with_clause: Option<Box<'a, WithClause<'a>>>,
}

pub enum ImportClause<'a> {
    SideEffect,
    Value(ImportValueClause<'a>),
    Type(ImportTypeClause<'a>),
    Source(Box<'a, ImportDefaultSpecifier<'a>>),
    Defer(Box<'a, ImportNamespaceSpecifier<'a>>),
}

Value imports permit an optional default binding before either named or namespace bindings:

pub enum ImportValueClause<'a> {
    Default(Box<'a, ImportDefaultSpecifier<'a>>),
    Named {
        default: Option<Box<'a, ImportDefaultSpecifier<'a>>>,
        specifiers: Vec<'a, ImportSpecifier<'a>>,
    },
    Namespace {
        default: Option<Box<'a, ImportDefaultSpecifier<'a>>>,
        namespace: Box<'a, ImportNamespaceSpecifier<'a>>,
    },
}

An empty named vector intentionally represents import {} from "mod"; a default plus an empty named
vector represents import value, {} from "mod".

Type-only imports do not permit a default binding combined with another clause:

pub enum ImportTypeClause<'a> {
    Default(Box<'a, ImportDefaultSpecifier<'a>>),
    Named(Vec<'a, ImportSpecifier<'a>>),
    Namespace(Box<'a, ImportNamespaceSpecifier<'a>>),
}

The enums are conceptual; their exact representation may use boxed per-form structs if required by
AST codegen or layout constraints.

Syntax Mapping

Source form Proposed representation
import "mod" ImportClause::SideEffect
import value from "mod" Value(Default(...))
import { a } from "mod" Value(Named { default: None, .. })
import value, { a } from "mod" Value(Named { default: Some(...), .. })
import * as ns from "mod" Value(Namespace { default: None, .. })
import value, * as ns from "mod" Value(Namespace { default: Some(...), .. })
import type T from "mod" Type(Default(...))
import type { T } from "mod" Type(Named(...))
import type * as Types from "mod" Type(Namespace(...))
import source wasm from "mod" Source(...)
import defer * as ns from "mod" Defer(...)

Guaranteed Invariants

  • Side-effect imports contain no bindings.
  • Default bindings occur at most once and only before named or namespace bindings.
  • Named and namespace bindings cannot coexist in one import.
  • Type-only imports cannot combine a default binding with another binding form.
  • Source-phase imports contain exactly one default binding.
  • Defer-phase imports contain exactly one namespace binding.
  • Import kind and phase cannot disagree with the clause shape.
  • import "mod" and import {} from "mod" have distinct variants.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating ImportDeclaration, ImportDeclarationSpecifier, and the parser and code-generation entry points that consume them. Compare their current handling with the proposed ImportClause variants and syntax mapping; done means the AST, parser, builders, transformations, and code generation preserve all listed forms and invariants.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.