Javascript import transform

Transform ES6 imports to dynamic imports for browser ESM compatibility

tree-sitter exploration

JS_LANGUAGE = Language(tsjs.language())
JS_LANGUAGE.field_count
36
parser = Parser(JS_LANGUAGE)
parser.language
<Language id=4765155344, version=15, name="javascript">

import

tree = parser.parse(b'''
import x from "foo";
'''
)
root_node = tree.root_node
assert root_node.type == 'program'
assert root_node.start_point == (1, 0)
assert root_node.end_point == (2, 0)
root_node
<Node type=program, start_point=(1, 0), end_point=(2, 0)>
import_statement = root_node.children[0]
assert import_statement.type == 'import_statement'
assert import_statement.start_point == (1, 0)
assert import_statement.end_point == (1, 20)
import_statement
<Node type=import_statement, start_point=(1, 0), end_point=(1, 20)>
import_statement.children
[<Node type="import", start_point=(1, 0), end_point=(1, 6)>,
 <Node type=import_clause, start_point=(1, 7), end_point=(1, 8)>,
 <Node type="from", start_point=(1, 9), end_point=(1, 13)>,
 <Node type=string, start_point=(1, 14), end_point=(1, 19)>,
 <Node type=";", start_point=(1, 19), end_point=(1, 20)>]
assert len(import_statement.children) == 5
assert import_statement.children[0].type == 'import'
assert import_statement.children[1].type == 'import_clause'
assert import_statement.children[2].type == 'from'
assert import_statement.children[3].type == 'string'
assert import_statement.children[4].type == ';'
assert import_statement.children[0].text == b'import'
assert import_statement.children[1].text == b'x'
assert import_statement.children[2].text == b'from'
assert import_statement.children[3].text == b'"foo"'
assert import_statement.children[4].text == b';'
code = b'''
import x from "foo";
const y = await import("bar");
console.log("hello");
'''

tree = parser.parse(code)
root = tree.root_node

def walk(node, code):
    if node.type == "import_statement":
        print("Static import:", code[node.start_byte:node.end_byte].decode())
    elif node.type == "call_expression":
        # detect dynamic import
        func = node.child_by_field_name("function")
        if func and func.type == "import":
            print("Dynamic import:", code[node.start_byte:node.end_byte].decode())
    for child in node.children:
        walk(child, code)

walk(root, code)
Static import: import x from "foo";
Dynamic import: import("bar")

Transform

Write a Python function that detects static ES6 import declarations and rewrites them into equivalent dynamic import() statements as per the below mapping. Background:

  • ES6 import declarations (static) are converted at parse time and can use default imports, named imports, namespace imports, etc.
  • Dynamic import() always returns a module namespace object (a Promise), so to access default or named exports, you must destructure or access properties after the promise resolves.
  • Default import: import x from "mod"; Translates to: const {default: x} = await import("mod");
  • Named import: import {a, b} from "mod"; Translates to: const {a, b} = await import("mod");
  • Namespace import: import * as foo from "mod"; Translates to: const foo = await import("mod");
  • Side-effect only: import "mod"; Translates to: await import("mod");
  • Note: await can only be used at top level in modules or inside async functions.

Reference:


Step 1: Mapping Examples

Static Import Dynamic Import Equivalent
import x from "mod"; const {default: x} = await import("mod");
import {a, b} from "mod"; const {a, b} = await import("mod");
import * as foo from "mod"; const foo = await import("mod");
import "mod"; await import("mod");

Let’s see what tree-sitter gives us for the above examples.

def show_tree(tree):
    def walk(node, depth=0):
        print('  '*depth + f"{node.type}")
        for child in node.children:
            walk(child, depth+1)
    walk(tree.root_node)
def traverse_tree(node: Tree|Node) -> Generator[Node, None, None]:
    cursor = node.walk()
    visited_children = False
    while True:
        if not visited_children:
            yield cursor.node  # type: ignore
            if not cursor.goto_first_child():
                visited_children = True
        elif cursor.goto_next_sibling():
            visited_children = False
        elif not cursor.goto_parent():
            break
examples = [
    b'import x from "foo";',
    b'import {a, b as c} from "bar";',
    b'import * as ns from "baz";',
    b'import x, {a, b as c} from "mod";',
    b'import "sidefx";'
]

for code in examples:
    print(f"code: '{code.decode()}'")
    tree = parser.parse(code)
    show_tree(tree)
    print('----')
code: 'import x from "foo";'
program
  import_statement
    import
    import_clause
      identifier
    from
    string
      "
      string_fragment
      "
    ;
----
code: 'import {a, b as c} from "bar";'
program
  import_statement
    import
    import_clause
      named_imports
        {
        import_specifier
          identifier
        ,
        import_specifier
          identifier
          as
          identifier
        }
    from
    string
      "
      string_fragment
      "
    ;
----
code: 'import * as ns from "baz";'
program
  import_statement
    import
    import_clause
      namespace_import
        *
        as
        identifier
    from
    string
      "
      string_fragment
      "
    ;
----
code: 'import x, {a, b as c} from "mod";'
program
  import_statement
    import
    import_clause
      identifier
      ,
      named_imports
        {
        import_specifier
          identifier
        ,
        import_specifier
          identifier
          as
          identifier
        }
    from
    string
      "
      string_fragment
      "
    ;
----
code: 'import "sidefx";'
program
  import_statement
    import
    string
      "
      string_fragment
      "
    ;
----

Step 2: Extracting and Rewriting Imports (first version)

Let’s first extract all static imports, parse their structure, and print what their dynamic import replacement would be.

def extract_string_content(string_node):
    # The string node contains quotes and string_fragment
    fragments = []
    for child in string_node.children:
        if child.type == 'string_fragment':
            fragments.append(child.text.decode())
    return ''.join(fragments)

def extract_import_specifiers(named_imports_node):
    specifiers = []
    for child in named_imports_node.children:
        if child.type == 'import_specifier':
            # Could be: identifier OR identifier 'as' identifier
            if len(child.children) == 1:
                # Simple: {a}
                name = child.children[0].text.decode()
                specifiers.append({'imported': name, 'local': name})
            elif len(child.children) == 3:
                # Aliased: {a as b}
                imported = child.children[0].text.decode()
                local = child.children[2].text.decode()
                specifiers.append({'imported': imported, 'local': local})
    return specifiers

def parse_import_statement(import_node, code):
    result = {
        'type': 'import_statement',
        'source': None,
        'default_import': None,
        'named_imports': [],
        'namespace_import': None,
        'side_effect_only': False,
        'has_mixed_default_namespace': False,
        'original_text': code[import_node.start_byte:import_node.end_byte].decode()
    }
    
    # Find the source (module path)
    for child in import_node.children:
        if child.type == 'string':
            result['source'] = extract_string_content(child)
            break
    
    # Check if it's side-effect only (no import_clause)
    import_clause = None
    for child in import_node.children:
        if child.type == 'import_clause':
            import_clause = child
            break
    
    if import_clause is None:
        result['side_effect_only'] = True
        return result
    
    # Parse import_clause - need to handle mixed default + namespace
    has_default = False
    has_namespace = False
    
    for child in import_clause.children:
        if child.type == 'identifier':
            # Default import
            result['default_import'] = child.text.decode()
            has_default = True
        elif child.type == 'named_imports':
            # Named imports: {a, b as c}
            result['named_imports'] = extract_import_specifiers(child)
        elif child.type == 'namespace_import':
            # Namespace import: * as ns
            has_namespace = True
            # Find the identifier after 'as'
            for ns_child in child.children:
                if ns_child.type == 'identifier':
                    result['namespace_import'] = ns_child.text.decode()
        # Skip commas
    
    # Check for mixed default + namespace
    if has_default and has_namespace:
        result['has_mixed_default_namespace'] = True
    
    return result

def generate_dynamic_import(import_data, fn='import'):
    source = import_data['source']
    
    if import_data['side_effect_only']:
        return f'await {fn}("{source}");'
    
    # Handle mixed default + namespace case
    if import_data['has_mixed_default_namespace']:
        ns_name = import_data['namespace_import']
        default_name = import_data['default_import']
        return f'const {ns_name} = await {fn}("{source}");\nconst {default_name} = {ns_name}.default;'
    
    # Handle pure namespace import
    if import_data['namespace_import'] and not import_data['default_import']:
        return f'const {import_data["namespace_import"]} = await {fn}("{source}");'
    
    # Build destructuring pattern for default and/or named imports
    destructure_parts = []
    
    # Add default import
    if import_data['default_import']:
        destructure_parts.append(f'default: {import_data["default_import"]}')
    
    # Add named imports
    for spec in import_data['named_imports']:
        if spec['imported'] == spec['local']:
            destructure_parts.append(spec['imported'])
        else:
            destructure_parts.append(f'{spec["imported"]}: {spec["local"]}')
    
    if destructure_parts:
        destructure = '{' + ', '.join(destructure_parts) + '}'
        return f'const {destructure} = await {fn}("{source}");'
    
    # Fallback (shouldn't happen)
    return f'await {fn}("{source}");'

def transform_imports(code, fn='import'):
    tree = parser.parse(code)
    imports = []
    
    def walk(node):
        if node.type == 'import_statement':
            import_data = parse_import_statement(node, code)
            dynamic_equivalent = generate_dynamic_import(import_data, fn)
            imports.append({
                'original': import_data['original_text'],
                'transformed': dynamic_equivalent,
                'data': import_data
            })
        
        for child in node.children:
            walk(child)
    
    walk(tree.root_node)
    return imports
examples = [
    b'import x from "foo";',  # default import
    b'import {a, b as c} from "bar";',  # named import
    b'import * as ns from "baz";',  # namespace import
    b'import x, {a, b as c} from "mod";',  # mixed default + named import
    b'import "sidefx";',  # side-effect only
    b'import defaultExport, * as name from "module-name";',  # mixed default + namespace import
]

print("=== IMPORT TRANSFORMATION RESULTS ===\n")

for code in examples:
    print(f"Original: {code.decode()}")
    transformations = transform_imports(code)
    for t in transformations:
        print(f"Dynamic:  {t['transformed']}")
        print(f"Data:     {t['data']}")
    print("---")
=== IMPORT TRANSFORMATION RESULTS ===

Original: import x from "foo";
Dynamic:  const {default: x} = await import("foo");
Data:     {'type': 'import_statement', 'source': 'foo', 'default_import': 'x', 'named_imports': [], 'namespace_import': None, 'side_effect_only': False, 'has_mixed_default_namespace': False, 'original_text': 'import x from "foo";'}
---
Original: import {a, b as c} from "bar";
Dynamic:  const {a, b: c} = await import("bar");
Data:     {'type': 'import_statement', 'source': 'bar', 'default_import': None, 'named_imports': [{'imported': 'a', 'local': 'a'}, {'imported': 'b', 'local': 'c'}], 'namespace_import': None, 'side_effect_only': False, 'has_mixed_default_namespace': False, 'original_text': 'import {a, b as c} from "bar";'}
---
Original: import * as ns from "baz";
Dynamic:  const ns = await import("baz");
Data:     {'type': 'import_statement', 'source': 'baz', 'default_import': None, 'named_imports': [], 'namespace_import': 'ns', 'side_effect_only': False, 'has_mixed_default_namespace': False, 'original_text': 'import * as ns from "baz";'}
---
Original: import x, {a, b as c} from "mod";
Dynamic:  const {default: x, a, b: c} = await import("mod");
Data:     {'type': 'import_statement', 'source': 'mod', 'default_import': 'x', 'named_imports': [{'imported': 'a', 'local': 'a'}, {'imported': 'b', 'local': 'c'}], 'namespace_import': None, 'side_effect_only': False, 'has_mixed_default_namespace': False, 'original_text': 'import x, {a, b as c} from "mod";'}
---
Original: import "sidefx";
Dynamic:  await import("sidefx");
Data:     {'type': 'import_statement', 'source': 'sidefx', 'default_import': None, 'named_imports': [], 'namespace_import': None, 'side_effect_only': True, 'has_mixed_default_namespace': False, 'original_text': 'import "sidefx";'}
---
Original: import defaultExport, * as name from "module-name";
Dynamic:  const name = await import("module-name");
const defaultExport = name.default;
Data:     {'type': 'import_statement', 'source': 'module-name', 'default_import': 'defaultExport', 'named_imports': [], 'namespace_import': 'name', 'side_effect_only': False, 'has_mixed_default_namespace': True, 'original_text': 'import defaultExport, * as name from "module-name";'}
---
def transform_js_source(code_bytes):
    "Transform JavaScript source code, replacing import statements with dynamic imports."
    if isinstance(code_bytes, str):
        code_bytes = code_bytes.encode('utf-8')
    
    tree = parser.parse(code_bytes)
    imports_with_positions = []
    
    def find_imports(node):
        if node.type == 'import_statement':
            import_data = parse_import_statement(node, code_bytes)
            dynamic_equivalent = generate_dynamic_import(import_data)
            
            imports_with_positions.append({
                'start_byte': node.start_byte,
                'end_byte': node.end_byte,
                'original': import_data['original_text'],
                'replacement': dynamic_equivalent,
                'data': import_data
            })
        
        for child in node.children:
            find_imports(child)
    
    find_imports(tree.root_node)
    
    # Sort by start_byte in descending order (work backwards)
    imports_with_positions.sort(key=lambda x: x['start_byte'], reverse=True)
    
    # Start with the original code
    result = code_bytes
    
    # Replace each import statement working backwards
    for import_info in imports_with_positions:
        start = import_info['start_byte']
        end = import_info['end_byte']
        replacement = import_info['replacement'].encode('utf-8')
        
        # Replace the import statement
        result = result[:start] + replacement + result[end:]
    
    return result.decode('utf-8')
js_code = '''
import x from "foo";
import {a, b as c} from "bar";
import * as ns from "baz";
import x, {a, b as c} from "mod";
import "sidefx";
import defaultExport, * as name from "module-name";

console.log("Hello, world!");
const myVar = 42;

function doSomething() {
    return x + a + ns.something;
}

export { doSomething };
'''

print("=== ORIGINAL CODE ===")
display(Markdown(f"```js\n{js_code}\n```"))
print("\n=== TRANSFORMED CODE ===")
transformed = transform_js_source(js_code)
display(Markdown(f"```js\n{transformed}\n```"))
=== ORIGINAL CODE ===

import x from "foo";
import {a, b as c} from "bar";
import * as ns from "baz";
import x, {a, b as c} from "mod";
import "sidefx";
import defaultExport, * as name from "module-name";

console.log("Hello, world!");
const myVar = 42;

function doSomething() {
    return x + a + ns.something;
}

export { doSomething };

=== TRANSFORMED CODE ===

const {default: x} = await import("foo");
const {a, b: c} = await import("bar");
const ns = await import("baz");
const {default: x, a, b: c} = await import("mod");
await import("sidefx");
const name = await import("module-name");
const defaultExport = name.default;

console.log("Hello, world!");
const myVar = 42;

function doSomething() {
    return x + a + ns.something;
}

export { doSomething };
def transform_js_source_with_details(code_bytes):
    "Transform JavaScript source and return both result and transformation details."
    if isinstance(code_bytes, str):
        code_bytes = code_bytes.encode('utf-8')
    
    tree = parser.parse(code_bytes)
    imports_with_positions = []
    
    def find_imports(node):
        if node.type == 'import_statement':
            import_data = parse_import_statement(node, code_bytes)
            dynamic_equivalent = generate_dynamic_import(import_data)
            
            imports_with_positions.append({
                'start_byte': node.start_byte,
                'end_byte': node.end_byte,
                'original': import_data['original_text'],
                'replacement': dynamic_equivalent,
                'data': import_data
            })
        
        for child in node.children:
            find_imports(child)
    
    find_imports(tree.root_node)
    
    # Sort by start_byte in descending order (work backwards)
    imports_with_positions.sort(key=lambda x: x['start_byte'], reverse=True)
    
    # Start with the original code
    result = code_bytes
    
    # Replace each import statement working backwards
    for import_info in imports_with_positions:
        start = import_info['start_byte']
        end = import_info['end_byte']
        replacement = import_info['replacement'].encode('utf-8')
        
        # Replace the import statement
        result = result[:start] + replacement + result[end:]
    
    return {
        'transformed_code': result.decode('utf-8'),
        'transformations': imports_with_positions,
        'original_code': code_bytes.decode('utf-8')
    }
# Test with details
result = transform_js_source_with_details(js_code)

print("=== TRANSFORMATION DETAILS ===")
for i, t in enumerate(result['transformations']):
    print(f"Transform {i+1}:")
    print(f"  Original:  {t['original']}")
    print(f"  Becomes:   {t['replacement']}")
    print(f"  Position:  {t['start_byte']}-{t['end_byte']}")
    print()

print("=== FINAL RESULT ===")
display(Markdown(f"```js\n{result['transformed_code']}\n```"))
=== TRANSFORMATION DETAILS ===
Transform 1:
  Original:  import defaultExport, * as name from "module-name";
  Becomes:   const name = await import("module-name");
const defaultExport = name.default;
  Position:  131-182

Transform 2:
  Original:  import "sidefx";
  Becomes:   await import("sidefx");
  Position:  114-130

Transform 3:
  Original:  import x, {a, b as c} from "mod";
  Becomes:   const {default: x, a, b: c} = await import("mod");
  Position:  80-113

Transform 4:
  Original:  import * as ns from "baz";
  Becomes:   const ns = await import("baz");
  Position:  53-79

Transform 5:
  Original:  import {a, b as c} from "bar";
  Becomes:   const {a, b: c} = await import("bar");
  Position:  22-52

Transform 6:
  Original:  import x from "foo";
  Becomes:   const {default: x} = await import("foo");
  Position:  1-21

=== FINAL RESULT ===

const {default: x} = await import("foo");
const {a, b: c} = await import("bar");
const ns = await import("baz");
const {default: x, a, b: c} = await import("mod");
await import("sidefx");
const name = await import("module-name");
const defaultExport = name.default;

console.log("Hello, world!");
const myVar = 42;

function doSomething() {
    return x + a + ns.something;
}

export { doSomething };
def find_dynamic_imports(node, code_bytes):
    "Find all dynamic import call expressions in the AST."
    dynamic_imports = []
    
    def walk(node):
        if node.type == "call_expression":
            # Check if this is a dynamic import call
            func = node.child_by_field_name("function")
            if func and func.type == "import":
                dynamic_imports.append({
                    'start_byte': func.start_byte,
                    'end_byte': func.end_byte,
                    'full_start_byte': node.start_byte,
                    'full_end_byte': node.end_byte,
                    'original_call': code_bytes[node.start_byte:node.end_byte].decode(),
                    'original_function': func.text.decode()
                })
        
        for child in node.children:
            walk(child)
    
    walk(node)
    return dynamic_imports

def transform_dynamic_imports(code_bytes, new_function_name="import"):
    """Transform dynamic import calls to use a different function name."""
    if isinstance(code_bytes, str):
        code_bytes = code_bytes.encode('utf-8')
    
    tree = parser.parse(code_bytes)
    dynamic_imports = find_dynamic_imports(tree.root_node, code_bytes)
    
    # Sort by start_byte in descending order (work backwards)
    dynamic_imports.sort(key=lambda x: x['start_byte'], reverse=True)
    
    # Start with the original code
    result = code_bytes
    
    # Replace each dynamic import function name working backwards
    for import_info in dynamic_imports:
        start = import_info['start_byte']
        end = import_info['end_byte']
        replacement = new_function_name.encode('utf-8')
        
        # Replace just the function name
        result = result[:start] + replacement + result[end:]
    
    return result.decode('utf-8')
test_dynamic_code = '''
// Static imports (these should be ignored by dynamic transformer)
import x from "foo";
import {a, b} from "bar";

// Dynamic imports (these should be transformed)
await import("/modules/my-module.js");
const { default: myDefault, foo, bar } = await import("/modules/my-module.js");
const module = await import("./utils.js");
import("./lazy-module.js").then(m => console.log(m));

// Other code
console.log("Hello world");
'''

print("=== DYNAMIC IMPORT DETECTION ===")
tree = parser.parse(test_dynamic_code.encode('utf-8'))
dynamic_imports = find_dynamic_imports(tree.root_node, test_dynamic_code.encode('utf-8'))

for i, di in enumerate(dynamic_imports):
    print(f"Dynamic import {i+1}:")
    print(f"  Full call: {di['original_call']}")
    print(f"  Function:  {di['original_function']}")
    print(f"  Position:  {di['start_byte']}-{di['end_byte']}")
    print()

print("=== DYNAMIC IMPORT TRANSFORMATION ===")
print("Original:")
display(Markdown(f"```js\n{test_dynamic_code}\n```"))
print("\nTransformed (import -> AAAA):")
transformed_dynamic = transform_dynamic_imports(test_dynamic_code, "AAAA")
display(Markdown(f"```js\n{transformed_dynamic}\n```"))
=== DYNAMIC IMPORT DETECTION ===
Dynamic import 1:
  Full call: import("/modules/my-module.js")
  Function:  import
  Position:  171-177

Dynamic import 2:
  Full call: import("/modules/my-module.js")
  Function:  import
  Position:  251-257

Dynamic import 3:
  Full call: import("./utils.js")
  Function:  import
  Position:  305-311

Dynamic import 4:
  Full call: import("./lazy-module.js")
  Function:  import
  Position:  327-333

=== DYNAMIC IMPORT TRANSFORMATION ===
Original:

// Static imports (these should be ignored by dynamic transformer)
import x from "foo";
import {a, b} from "bar";

// Dynamic imports (these should be transformed)
await import("/modules/my-module.js");
const { default: myDefault, foo, bar } = await import("/modules/my-module.js");
const module = await import("./utils.js");
import("./lazy-module.js").then(m => console.log(m));

// Other code
console.log("Hello world");

Transformed (import -> AAAA):

// Static imports (these should be ignored by dynamic transformer)
import x from "foo";
import {a, b} from "bar";

// Dynamic imports (these should be transformed)
await AAAA("/modules/my-module.js");
const { default: myDefault, foo, bar } = await AAAA("/modules/my-module.js");
const module = await AAAA("./utils.js");
AAAA("./lazy-module.js").then(m => console.log(m));

// Other code
console.log("Hello world");
def transform_all_imports(code_bytes, new_function_name="import"):
    """Transform both static and dynamic imports."""
    if isinstance(code_bytes, str):
        code_bytes = code_bytes.encode('utf-8')
    
    # First transform static imports to dynamic imports
    result = transform_js_source(code_bytes)
    
    # Then transform all dynamic imports (including the newly created ones)
    result = transform_dynamic_imports(result, new_function_name)
    
    return result
combined_test_code = '''
import x from "foo";
import {a, b as c} from "bar";
import * as ns from "baz";
import "sidefx";

const { default: existing } = await import("./existing2.js");

console.log("Hello, world!");
const myVar = 42;

async function doSomething() {
    const existingDynamic = await import("./existing.js");
    return x + a + ns.something;
}

export { doSomething };
'''

print("=== COMBINED TRANSFORMATION ===")
print("Original:")
display(Markdown(f"```js\n{combined_test_code}\n```"))
print("\nAfter transforming to AAAA:")
final_result = transform_all_imports(combined_test_code, "AAAA")
display(Markdown(f"```js\n{final_result}\n```"))
=== COMBINED TRANSFORMATION ===
Original:

import x from "foo";
import {a, b as c} from "bar";
import * as ns from "baz";
import "sidefx";

const { default: existing } = await import("./existing2.js");

console.log("Hello, world!");
const myVar = 42;

async function doSomething() {
    const existingDynamic = await import("./existing.js");
    return x + a + ns.something;
}

export { doSomething };

After transforming to AAAA:

const {default: x} = await AAAA("foo");
const {a, b: c} = await AAAA("bar");
const ns = await AAAA("baz");
await AAAA("sidefx");

const { default: existing } = await AAAA("./existing2.js");

console.log("Hello, world!");
const myVar = 42;

async function doSomething() {
    const existingDynamic = await AAAA("./existing.js");
    return x + a + ns.something;
}

export { doSomething };
def transform_all_imports_with_details(code_bytes, new_function_name="import"):
    "Transform both static and dynamic imports with detailed reporting."
    if isinstance(code_bytes, str):
        code_bytes = code_bytes.encode('utf-8')
    
    original_code = code_bytes.decode('utf-8')
    
    # Step 1: Transform static imports to dynamic imports
    static_result = transform_js_source_with_details(code_bytes)
    
    # Step 2: Transform all dynamic imports (including newly created ones)
    after_static = static_result['transformed_code']
    tree = parser.parse(after_static.encode('utf-8'))
    dynamic_imports = find_dynamic_imports(tree.root_node, after_static.encode('utf-8'))
    
    # Transform dynamic imports
    final_code = transform_dynamic_imports(after_static, new_function_name)
    
    return {
        'original_code': original_code,
        'after_static_transform': after_static,
        'final_code': final_code,
        'static_transformations': static_result['transformations'],
        'dynamic_transformations': dynamic_imports,
        'new_function_name': new_function_name
    }
detailed_result = transform_all_imports_with_details(combined_test_code, "AAAA")

print("=== DETAILED TRANSFORMATION REPORT ===")
print(f"Target function name: {detailed_result['new_function_name']}")
print(f"\nStatic imports found: {len(detailed_result['static_transformations'])}")
for i, t in enumerate(detailed_result['static_transformations']):
    print(f"  {i+1}. {t['original']} -> {t['replacement']}")

print(f"\nDynamic imports found: {len(detailed_result['dynamic_transformations'])}")
for i, t in enumerate(detailed_result['dynamic_transformations']):
    print(f"  {i+1}. {t['original_call']}")

print(f"\n=== FINAL RESULT ===")
print(detailed_result['final_code'])
=== DETAILED TRANSFORMATION REPORT ===
Target function name: AAAA

Static imports found: 4
  1. import "sidefx"; -> await import("sidefx");
  2. import * as ns from "baz"; -> const ns = await import("baz");
  3. import {a, b as c} from "bar"; -> const {a, b: c} = await import("bar");
  4. import x from "foo"; -> const {default: x} = await import("foo");

Dynamic imports found: 6
  1. import("foo")
  2. import("bar")
  3. import("baz")
  4. import("sidefx")
  5. import("./existing2.js")
  6. import("./existing.js")

=== FINAL RESULT ===

const {default: x} = await AAAA("foo");
const {a, b: c} = await AAAA("bar");
const ns = await AAAA("baz");
await AAAA("sidefx");

const { default: existing } = await AAAA("./existing2.js");

console.log("Hello, world!");
const myVar = 42;

async function doSomething() {
    const existingDynamic = await AAAA("./existing.js");
    return x + a + ns.something;
}

export { doSomething };

JSImportTransform

node.type='string'
    node.type='"'
    node.type='string_fragment'
    node.type='"'
node.type='import_specifier'
    node.type='identifier'
node.type=','
node.type='import_specifier'
    node.type='identifier'
    node.type='as'
    node.type='identifier'
node.type='import_statement'
  node.type='import'
  node.type='import_clause'
    node.type='identifier'
  node.type='from'
  ...
node.type='import_statement'
  node.type='import'
  node.type='import_clause'
    node.type='named_imports'
      node.type='{'
      node.type='import_specifier'
        node.type='identifier'
      node.type=','
      node.type='import_specifier'
        node.type='identifier'
        node.type='as'
        node.type='identifier'
      node.type='}'
  node.type='from'
  ...
node.type='import_statement'
  node.type='import'
  node.type='import_clause'
    node.type='namespace_import'
      node.type='*'
      node.type='as'
      node.type='identifier'
  node.type='from'
  ...
def _replace_old(bytes: bytes, imports):
    "Replace ranges.`imports` is a list of dicts with `start`, `end`, and `replace` keys."
    imports.sort(key=lambda x: x['start'], reverse=True)
    res = bytes
    for import_info in imports:
        start, end = import_info['start'], import_info['end']
        replacement = import_info['replace'].encode('utf-8')
        res = res[:start] + replacement + res[end:]
    return res
original = b"Hello world!"
imports = [{'start': 6, 'end': 11, 'replace': 'Python'}]
result = _replace(original, imports)
test_eq(result, b"Hello Python!")

original = b"import x; import y;"
imports = [
    {'start': 0, 'end': 9, 'replace': 'const x = 1;'},
    {'start': 10, 'end': 19, 'replace': 'const y = 2;'}
]
result = _replace(original, imports)
test_eq(result, b"const x = 1; const y = 2;")

original = b"abcde"
imports = [
    {'start': 1, 'end': 4, 'replace': 'X'},
    {'start': 0, 'end': 2, 'replace': 'Y'}
]
# Should process in order: first replace 0-2 with 'Y', then adjust positions
result = _replace(original, imports)
test_eq(result, b"YXe")

original = b"Hello"
imports = [{'start': 3, 'end': 5, 'replace': 'p!'}]
result = _replace(original, imports)
test_eq(result, b"Help!")

# empty replacement
original = b"Hello world"
imports = [{'start': 5, 'end': 11, 'replace': ''}]
result = _replace(original, imports)
test_eq(result, b"Hello")

# replacement longer than original
original = b"Hi"
imports = [{'start': 0, 'end': 2, 'replace': 'Hello'}]
result = _replace(original, imports)
test_eq(result, b"Hello")

original = b"Hello world"
imports = []
result = _replace(original, imports)
test_eq(result, b"Hello world")

source

JSImportTransform

 JSImportTransform (import_name='import', comment_marker=('transform',
                    ('ignore',)))

Initialize self. See help(type(self)) for accurate signature.

parser = Parser(JS_LANGUAGE)
transformer = JSImportTransform("AAAA")
test_code = '''
import x from "foo";
import {a, b as c} from "bar";
import * as ns from "baz";
import "sidefx";

const { default: someFunc } = await import("./existing.js");

console.log("Hello, world!");
const myVar = 42;

async function doSomething() {
    const existingNS = await import("./existing.js");
    return x + a + ns.something;
}

export { doSomething };
'''

result = transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)
test_is('const {a, b: c} = await AAAA("bar");' in result, True)
test_is('const ns = await AAAA("baz");' in result, True)
test_is('await AAAA("sidefx");' in result, True)
test_is('const existingNS = await AAAA("./existing.js");' in result, True)
test_is('const { default: someFunc } = await AAAA("./existing.js");' in result, True)
display(Markdown(f"```js\n{result}\n```"))

const {default: x} = await AAAA("foo");
const {a, b: c} = await AAAA("bar");
const ns = await AAAA("baz");
await AAAA("sidefx");

const { default: someFunc } = await AAAA("./existing.js");

console.log("Hello, world!");
const myVar = 42;

async function doSomething() {
    const existingNS = await AAAA("./existing.js");
    return x + a + ns.something;
}

export { doSomething };
test_code = '''
console.log(`import x from "foo";`);
'''

result = transformer(test_code)
test_eq(result, test_code)
test_code = '''
import * from "baz";
'''

test_fail(lambda: transformer(test_code))

ignore marker functionality with various whitespace scenarios.

test_code = '''
import x from "foo"; // @transform: ignore
import y from "bar";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('const {default: y} = await AAAA("bar");' in result, True)

Same line comment with exact marker

test_code = '''
// @transform: ignore
import x from "foo";
import y from "bar";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('const {default: y} = await AAAA("bar");' in result, True)

Previous line comment with exact marker

test_code = '''
import x from "foo"; /* @transform: ignore */
import y from "bar";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('const {default: y} = await AAAA("bar");' in result, True)

Block comment same line

test_code = '''
/* @transform: ignore */
import x from "foo";
import y from "bar";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('const {default: y} = await AAAA("bar");' in result, True)

Block comment previous line

test_code = '''
//   @transform: ignore   
import x from "foo";
import y from "bar";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('const {default: y} = await AAAA("bar");' in result, True)

Extra whitespace around marker

test_code = '''
// TODO: @transform: ignore - this breaks something
import x from "foo";
import y from "bar";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('const {default: y} = await AAAA("bar");' in result, True)

Marker with extra text before/after

test_code = '''
const a = await import("normal.js");
const b = await import("ignored.js"); // @transform: ignore
'''
result = transformer(test_code)
test_is('const a = await AAAA("normal.js");' in result, True)
test_is('const b = await import("ignored.js");' in result, True)

Dynamic import with ignore marker

test_code = '''
import x from "foo";
// @transform: ignore
import y from "bar";
const z = await import("baz.js");
// @transform: ignore
const w = await import("qux.js");
'''
result = transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)
test_is('import y from "bar";' in result, True)
test_is('const z = await AAAA("baz.js");' in result, True)
test_is('const w = await import("qux.js");' in result, True)

Mixed static and dynamic with ignore

test_code = '''
import {a, b} from "mod1";
// @ts-ignore   // @transform: ignore
import {c, d} from "mod2";
import {e, f} from "mod3";
'''
result = transformer(test_code)
test_is('const {a, b} = await AAAA("mod1");' in result, True)
test_is('import {c, d} from "mod2";' in result, True)
test_is('const {e, f} = await AAAA("mod3");' in result, True)

Multiple imports, only one ignored

test_code = '''
// @transform:ignore (no space after colon)
import x from "foo";
// @transform :ignore (space before colon)
import y from "bar";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('import y from "bar";' in result, True)

Different spacing in marker itself

test_code = '''
// @TRANSFORM: IGNORE
import x from "foo";
'''
result = transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)

Case sensitivity test (should NOT ignore)

test_code = '''
// @transform: ignor
import x from "foo";
'''
result = transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)

Partial marker match (should NOT ignore)

test_code = '''
import x from "foo";
// @transform: ignore
import y from "bar";
'''
result = transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)
test_is('import y from "bar";' in result, True)

Comment on line after import (should NOT ignore)

test_code = '''
import * as ns from "mod1";
// @transform: ignore
import * as ns2 from "mod2";
import mydefault, {named} from "mod3";
// @transform: ignore
import default2, {named2} from "mod4";
'''
result = transformer(test_code)
test_is('const ns = await AAAA("mod1");' in result, True)
test_is('import * as ns2 from "mod2";' in result, True)
test_is('const {default: mydefault, named} = await AAAA("mod3");' in result, True)
test_is('import default2, {named2} from "mod4";' in result, True)

Namespace and mixed imports with ignore

Edge cases for ignore marker functionality.

# Multiple comments, only one with ignore
test_code = '''
// This is a regular comment
// @transform: ignore
import x from "foo";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)

# Comment with ignore marker two lines before (should NOT ignore)
test_code = '''
// @transform: ignore

import x from "foo";
'''
result = transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)

# Side-effect import with ignore
test_code = '''
import "side-effect.js";
// @transform: ignore
import "ignored-side-effect.js";
'''
result = transformer(test_code)
test_is('await AAAA("side-effect.js");' in result, True)
test_is('import "ignored-side-effect.js";' in result, True)

# Multiple markers in same comment
test_code = '''
// @transform: ignore @transform: ignore
import x from "foo";
'''
result = transformer(test_code)
test_is('import x from "foo";' in result, True)

# Marker in string literal (should NOT ignore)
test_code = '''
console.log("// @transform: ignore");
import x from "foo";
'''
result = transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)

Test custom ignore marker functionality.

# Create transformer with custom marker
custom_transformer = JSImportTransform("AAAA", ('skip', ('transform',)))

# Custom marker works
test_code = '''
// @skip: transform
import x from "foo";
import y from "bar";
'''
result = custom_transformer(test_code)
test_is('import x from "foo";' in result, True)
test_is('const {default: y} = await AAAA("bar");' in result, True)

# Default marker doesn't work with custom transformer
test_code = '''
// @transform: ignore
import x from "foo";
'''
result = custom_transformer(test_code)
test_is('const {default: x} = await AAAA("foo");' in result, True)