oxc-project / oxc-project/backlog

AST: tighten TSImportType to only valid options

Open
#245 1 comment 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

Summary

Replace TSImportType.options: Option<Box<ObjectExpression>> with a grammar-specific representation.

Import-type options are not arbitrary object expressions. Valid options have a constrained shape:

type Data = import("./data.json", {
  with: {
    type: "json",
  },
});

The current AST allows builders and transforms to construct impossible forms such as:

import("x", {});
import("x", { other: {} });
import("x", { with: expression });
import("x", { with: { type: 1 } });
import("x", { with: { ...attributes } });

Motivation

Oxc currently represents the options as:

pub struct TSImportType<'a> {
    pub source: StringLiteral<'a>,
    pub options: Option<Box<'a, ObjectExpression<'a>>>,
    pub qualifier: Option<TSImportTypeQualifier<'a>>,
    pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}

ObjectExpression admits arbitrary properties, spreads, computed keys, methods, accessors, and arbitrary expression values. None of those accurately describe TypeScript import-type options.

The parser validates some of these restrictions, but invalid input is still recovered into the unrestricted AST shape. Direct AST construction can bypass the parser entirely. Every consumer must therefore handle structures which cannot occur in valid TypeScript.

TypeScript itself models this syntax as import attributes rather than a general object literal.

Current AST Shape

pub struct TSImportType<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub source: StringLiteral<'a>,
    pub options: Option<Box<'a, ObjectExpression<'a>>>,
    pub qualifier: Option<TSImportTypeQualifier<'a>>,
    pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}

Proposed AST Shape

Introduce a dedicated options node containing a recognized keyword and statically shaped import attributes:

pub struct TSImportType<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub source: StringLiteral<'a>,
    pub options: Option<Box<'a, TSImportTypeOptions<'a>>>,
    pub qualifier: Option<TSImportTypeQualifier<'a>>,
    pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}

pub struct TSImportTypeOptions<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub keyword: WithClauseKeyword,
    pub attributes: Vec<'a, ImportAttribute<'a>>,
}

ImportAttribute already restricts keys to identifiers or string literals and values to string literals:

pub struct ImportAttribute<'a> {
    pub key: ImportAttributeKey<'a>,
    pub value: StringLiteral<'a>,
}

Reusing WithClause may also be possible, although a dedicated node may better reflect the additional { with: ... } wrapper used by import types.

The legacy assert spelling can remain representable through WithClauseKeyword if required for recovery or compatibility, without permitting arbitrary property names.

ESTree serialization should continue producing the existing ObjectExpression | null representation.

Syntax Mapping

Source form Current representation Proposed representation
import("x") options: None Unchanged
import("x", { with: { type: "json" } }) Arbitrary nested ObjectExpression TSImportTypeOptions containing one ImportAttribute
import("x", { assert: { type: "json" } }) Arbitrary nested ObjectExpression Recognized legacy keyword, if retained
import("x", { other: {} }) Valid AST shape plus diagnostic Diagnostic; arbitrary key cannot inhabit TSImportTypeOptions
import("x", { with: value }) Any Expression Diagnostic; no arbitrary expression in the options AST
import("x", { with: { type: 1 } }) Numeric expression value Diagnostic; attribute values are string literals
import("x", { with: { ...attrs } }) SpreadElement Diagnostic; spread cannot inhabit the dedicated node

Guaranteed Invariants

  • TSImportType.options cannot contain a general-purpose object expression.
  • Present options contain exactly one recognized outer keyword.
  • Attribute keys are static identifiers or string literals.
  • Attribute values are string literals.
  • Spreads, computed keys, methods, accessors, and unrelated outer properties are unrepresentable.
  • Parser recovery emits diagnostics without polluting the valid AST model.
  • Existing ESTree output remains compatible.

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 at the TSImportType definition and trace the parser's recovery path for import-type options, then inspect WithClause, ImportAttribute, and ESTree serialization. Confirm how the proposed dedicated options node fits existing AST conventions and compatibility requirements. Done means invalid option shapes are diagnosed without entering the valid AST model, while existing ESTree output remains compatible.

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
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.