JS_LANGUAGE = Language(tsjs.language())
JS_LANGUAGE.field_count36
<Language id=4765155344, version=15, name="javascript">
<Node type=program, start_point=(1, 0), end_point=(2, 0)>
<Node type=import_statement, start_point=(1, 0), end_point=(1, 20)>
[<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 == ';'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")
Write a Python function that detects static ES6 import declarations and rewrites them into equivalent dynamic import() statements as per the below mapping. Background:
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.import x from "mod"; Translates to: const {default: x} = await import("mod");import {a, b} from "mod"; Translates to: const {a, b} = await import("mod");import * as foo from "mod"; Translates to: const foo = await import("mod");import "mod"; Translates to: await import("mod");await can only be used at top level in modules or inside async functions.Reference:
| 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 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():
breakcode: '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
"
;
----
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 importsexamples = [
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 resultcombined_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 };
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 resoriginal = 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")JSImportTransform (import_name='import', comment_marker=('transform', ('ignore',)))
Initialize self. See help(type(self)) for accurate signature.
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 };Same line comment with exact marker
Previous line comment with exact marker
Block comment same line
Block comment previous line
Extra whitespace around marker
Marker with extra text before/after
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
Different spacing in marker itself
Case sensitivity test (should NOT ignore)
Partial marker match (should NOT ignore)
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
# 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)