Skip to content

Commit af4b1ee

Browse files
committed
improve export capture logic
1 parent 405b6a2 commit af4b1ee

File tree

3 files changed

+96
-51
lines changed

3 files changed

+96
-51
lines changed

src/idom/web/utils.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,46 @@ def resolve_module_exports_from_url(url: str, max_depth: int) -> Set[str]:
5050

5151
def resolve_module_exports_from_source(content: str) -> Tuple[Set[str], Set[str]]:
5252
names: Set[str] = set()
53-
for match in _JS_MODULE_EXPORT_PATTERN.findall(content):
54-
for export in match.split(","):
55-
export_parts = export.split(" as ", 1)
56-
names.add(export_parts[-1].strip())
57-
names.update(_JS_MODULE_EXPORT_FUNC_PATTERN.findall(content))
58-
names.update(_JS_MODULE_EXPORT_NAME_PATTERN.findall(content))
59-
60-
references: Set[str] = set(_JS_MODULE_EXPORT_FROM_REF_PATTERN.findall(content))
61-
return names, references
53+
references: Set[str] = set()
54+
for export in _JS_EXPORT_PATTERN.findall(
55+
# strip comments
56+
_JS_LINE_COMMENT.sub("", content)
57+
):
58+
export = export.rstrip(";").strip()
59+
# Exporting individual features
60+
if export.startswith("let "):
61+
names.update(let.split("=", 1)[0] for let in export[4:].split(","))
62+
elif export.startswith("function "):
63+
names.add(export[9:].split("(", 1)[0])
64+
elif export.startswith("class "):
65+
names.add(export[6:].split("{", 1)[0])
66+
# Renaming exports and export list
67+
elif export.startswith("{") and export.endswith("}"):
68+
names.update(
69+
item.split(" as ", 1)[-1] for item in export.strip("{}").split(",")
70+
)
71+
# Exporting destructured assignments with renaming
72+
elif export.startswith("const "):
73+
names.update(
74+
item.split(":", 1)[0]
75+
for item in export[6:].split("=", 1)[0].strip("{}").split(",")
76+
)
77+
# Default exports
78+
elif export.startswith("default "):
79+
names.add("default")
80+
# Aggregating modules
81+
elif export.startswith("* as "):
82+
names.add(export[5:].split(" from ", 1)[0])
83+
elif export.startswith("* "):
84+
references.add(export[2:].split("from ", 1)[-1].strip("'\""))
85+
elif export.startswith("{") and " from " in export:
86+
names.update(
87+
item.split(" as ", 1)[-1]
88+
for item in export.split(" from ")[0].strip("{}").split(",")
89+
)
90+
else:
91+
logger.warning(f"Unknown export type {export!r}")
92+
return {n.strip() for n in names}, {r.strip() for r in references}
6293

6394

6495
def _resolve_relative_file_path(base_path: Path, rel_url: str) -> Path:
@@ -81,16 +112,5 @@ def _resolve_relative_url(base_url: str, rel_url: str) -> str:
81112
return f"{base_url}/{rel_url}"
82113

83114

84-
_JS_MODULE_EXPORT_PATTERN = re.compile(
85-
r";?\s*export\s*{([0-9a-zA-Z_$\s,]*)}\s*;", re.MULTILINE
86-
)
87-
_JS_VAR = r"[a-zA-Z_$][0-9a-zA-Z_$]*"
88-
_JS_MODULE_EXPORT_NAME_PATTERN = re.compile(
89-
fr";?\s*export\s+({_JS_VAR})\s+{_JS_VAR}\s*;", re.MULTILINE
90-
)
91-
_JS_MODULE_EXPORT_FUNC_PATTERN = re.compile(
92-
fr";?\s*export\s+function\s+({_JS_VAR})\s*\(.*?", re.MULTILINE
93-
)
94-
_JS_MODULE_EXPORT_FROM_REF_PATTERN = re.compile(
95-
r""";?\s*export\s+\*\s+from\s+['"](.*?)['"];"""
96-
)
115+
_JS_LINE_COMMENT = re.compile(r"//.*$")
116+
_JS_EXPORT_PATTERN = re.compile(r";?\s*export(?=\s+|{)(.*?(?:;|}\s*))", re.MULTILINE)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copied from: https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
2+
3+
// Exporting individual features
4+
export let name1, name2, name3; // also var, const
5+
export let name4 = 4, name5 = 5, name6; // also var, const
6+
export function functionName(){...}
7+
export class ClassName {...}
8+
9+
// Export list
10+
export { name7, name8, name9 };
11+
12+
// Renaming exports
13+
export { variable1 as name10, variable2 as name11, name12 };
14+
15+
// Exporting destructured assignments with renaming
16+
export const { name13, name14: bar } = o;
17+
18+
// Aggregating modules
19+
export * from "source1"; // does not set the default export
20+
export * from "source2"; // does not set the default export
21+
export * as name15 from "source3"; // Draft ECMAScript® 2O21
22+
export { name16, name17 } from "source4";
23+
export { import1 as name18, import2 as name19, name20 } from "source5";

tests/test_web/test_utils.py

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
1+
from pathlib import Path
2+
13
import pytest
24

35
from idom.web.utils import resolve_module_exports_from_source
4-
from tests.general_utils import assert_same_items
6+
7+
8+
JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures"
59

610

711
@pytest.mark.parametrize(
8-
"module_source, expected_names, expected_references",
12+
"text",
913
[
10-
(
11-
"asdfasdfasdf;export{one as One, two as Two};asdfasdf;",
12-
["One", "Two"],
13-
[],
14-
),
15-
(
16-
"asd;export{one as One};asdfasdf;export{two as Two};",
17-
["One", "Two"],
18-
[],
19-
),
20-
("asdasd;export default something;", ["default"], []),
21-
(
22-
"asdasd;export One something;asdfa;export Two somethingElse;",
23-
["One", "Two"],
24-
[],
25-
),
26-
(
27-
"asdasd;export One something;asdfa;export{two as Two};asdfasdf;",
28-
["One", "Two"],
29-
[],
30-
),
14+
"export default expression;",
15+
"export default function (…) { … } // also class, function*",
16+
"export default function name1(…) { … } // also class, function*",
17+
"export { something as default };",
18+
"export { default } from 'some-source';",
19+
"export { something as default } from 'some-source';",
3120
],
3221
)
33-
def test_resolve_module_exports_from_source(
34-
module_source, expected_names, expected_references
35-
):
36-
names, references = resolve_module_exports_from_source(module_source)
37-
assert_same_items(names, expected_names)
38-
assert_same_items(references, expected_references)
22+
def test_resolve_module_default_exports_from_source(text):
23+
names, references = resolve_module_exports_from_source(text)
24+
assert names == {"default"} and not references
25+
26+
27+
def test_resolve_module_exports_from_source():
28+
fixture_file = JS_FIXTURES_DIR / "exports_syntax.js"
29+
names, references = resolve_module_exports_from_source(fixture_file.read_text())
30+
assert (
31+
names
32+
== (
33+
{f"name{i}" for i in range(1, 21)}
34+
| {
35+
"functionName",
36+
"ClassName",
37+
}
38+
)
39+
and references == {"source1", "source2"}
40+
)

0 commit comments

Comments
 (0)