Skip to content

Commit 20cb7fd

Browse files
authored
feat: [breaking] Improve exports analysis with ExportSpecifier (#119)
1 parent 8e9fd84 commit 20cb7fd

12 files changed

+380
-203
lines changed

README.md

+35-14
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,13 @@ const { init, parse } = require('es-module-lexer');
3030
// this is necessary for the Web Assembly boot
3131
await init;
3232

33-
const [imports, exports] = parse('export var p = 5');
34-
exports[0] === 'p';
33+
const source = 'export var p = 5';
34+
const [imports, exports] = parse(source);
35+
36+
// Returns "p"
37+
source.slice(exports[0].s, exports[0].e);
38+
// Returns "p"
39+
source.slice(exports[0].ls, exports[0].le);
3540
})();
3641
```
3742

@@ -50,6 +55,7 @@ import { init, parse } from 'es-module-lexer';
5055
export function q () {
5156
5257
};
58+
export { x as 'external name' } from 'external';
5359
5460
// Comments provided to demonstrate edge cases
5561
import /*comment!*/ ( 'asdf', { assert: { type: 'json' }});
@@ -61,21 +67,36 @@ import { init, parse } from 'es-module-lexer';
6167
// Returns "modထ"
6268
imports[0].n
6369
// Returns "mod\u1011"
64-
source.substring(imports[0].s, imports[0].e);
70+
source.slice(imports[0].s, imports[0].e);
6571
// "s" = start
6672
// "e" = end
6773

6874
// Returns "import { name } from 'mod'"
69-
source.substring(imports[0].ss, imports[0].se);
75+
source.slice(imports[0].ss, imports[0].se);
7076
// "ss" = statement start
7177
// "se" = statement end
7278

7379
// Returns "{ type: 'json' }"
74-
source.substring(imports[1].a, imports[1].se);
80+
source.slice(imports[1].a, imports[1].se);
7581
// "a" = assert, -1 for no assertion
7682

77-
// Returns "p,q"
78-
exports.toString();
83+
// Returns "external"
84+
source.slice(imports[2].s, imports[2].e);
85+
86+
// Returns "p"
87+
source.slice(exports[0].s, exports[0].e);
88+
// Returns "p"
89+
source.slice(exports[0].ls, exports[0].le);
90+
// Returns "q"
91+
source.slice(exports[1].s, exports[1].e);
92+
// Returns "q"
93+
source.slice(exports[1].ls, exports[1].le);
94+
// Returns "'external name'"
95+
source.slice(exports[2].s, exports[2].e);
96+
// Returns -1
97+
exports[2].ls;
98+
// Returns -1
99+
exports[2].le;
79100

80101
// Dynamic imports are indicated by imports[2].d > -1
81102
// In this case the "d" index is the start of the dynamic import bracket
@@ -85,13 +106,13 @@ import { init, parse } from 'es-module-lexer';
85106
// Returns "asdf" (only for string literal dynamic imports)
86107
imports[2].n
87108
// Returns "import /*comment!*/ ( 'asdf', { assert: { type: 'json' } })"
88-
source.substring(imports[2].ss, imports[2].se);
109+
source.slice(imports[3].ss, imports[3].se);
89110
// Returns "'asdf'"
90-
source.substring(imports[2].s, imports[2].e);
111+
source.slice(imports[3].s, imports[3].e);
91112
// Returns "( 'asdf', { assert: { type: 'json' } })"
92-
source.substring(imports[2].d, imports[2].se);
113+
source.slice(imports[3].d, imports[3].se);
93114
// Returns "{ assert: { type: 'json' } }"
94-
source.substring(imports[2].a, imports[2].se - 1);
115+
source.slice(imports[3].a, imports[3].se - 1);
95116

96117
// For non-string dynamic import expressions:
97118
// - n will be undefined
@@ -101,11 +122,11 @@ import { init, parse } from 'es-module-lexer';
101122
// For nested dynamic imports, the se value of the outer import is -1 as end tracking does not
102123
// currently support nested dynamic immports
103124

104-
// import.meta is indicated by imports[2].d === -2
125+
// import.meta is indicated by imports[3].d === -2
105126
// Returns true
106-
imports[2].d === -2;
127+
imports[4].d === -2;
107128
// Returns "import /*comment!*/.meta"
108-
source.substring(imports[2].s, imports[2].e);
129+
source.slice(imports[4].s, imports[4].e);
109130
// ss and se are the same for import meta
110131
})();
111132
```

chompfile.toml

+8-3
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ deps = ['src/lexer.h', 'src/lexer.c']
7373
run = """
7474
${{ WASI_PATH }}/bin/clang src/lexer.c --sysroot=${{ WASI_PATH }}/share/wasi-sysroot -o lib/lexer.wasm -nostartfiles \
7575
"-Wl,-z,stack-size=13312,--no-entry,--compress-relocations,--strip-all,\
76-
--export=parse,--export=sa,--export=e,--export=ri,--export=re,--export=is,--export=ie,--export=ss,--export=ip,--export=se,--export=ai,--export=id,--export=es,--export=ee,--export=f,--export=__heap_base" \
76+
--export=parse,--export=sa,--export=e,--export=ri,--export=re,--export=is,--export=ie,--export=ss,--export=ip,--export=se,--export=ai,--export=id,--export=es,--export=ee,--export=els,--export=ele,--export=f,--export=__heap_base" \
7777
-Wno-logical-op-parentheses -Wno-parentheses \
7878
-Oz
7979
"""
@@ -87,7 +87,7 @@ run = """
8787
${{ EMSDK_PATH }}/emsdk activate 1.40.1-fastcomp
8888
8989
${{ EMSDK_PATH }}/fastcomp/emscripten/emcc ./src/lexer.c -o lib/lexer.emcc.js -s WASM=0 -Oz --closure 1 \
90-
-s EXPORTED_FUNCTIONS="['_parse','_sa','_e','_ri','_re','_is','_ie','_ss','_ip','_se','_ai','_id','_es','_ee','_f','_setSource']" \
90+
-s EXPORTED_FUNCTIONS="['_parse','_sa','_e','_ri','_re','_is','_ie','_ss','_ip','_se','_ai','_id','_es','_ee','_els','_ele','_f','_setSource']" \
9191
-s ERROR_ON_UNDEFINED_SYMBOLS=0 -s SINGLE_FILE=1 -s TOTAL_STACK=4997968 -s --separate-asm -Wno-logical-op-parentheses -Wno-parentheses
9292
9393
# rm lib/lexer.emcc.js
@@ -152,11 +152,16 @@ run = '''
152152

153153
[[task]]
154154
name = 'test'
155-
deps = ['test:js', 'test:wasm']
155+
deps = ['test:wasm', 'test:asm']
156156

157157
[[task]]
158158
name = 'test:js'
159+
run = 'mocha -b -u tdd test/*.cjs'
160+
161+
[[task]]
162+
name = 'test:asm'
159163
deps = ['dist/lexer.asm.js']
164+
env = { ASM = '1' }
160165
run = 'mocha -b -u tdd test/*.cjs'
161166

162167
[[task]]

lexer.js

+19-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ function addImport (ss, s, e, d) {
2020
return impt;
2121
}
2222

23+
function addExport (s, e, ls, le) {
24+
exports.push({ s, e, ls, le });
25+
}
26+
2327
function readName (impt) {
2428
let { d, s } = impt;
2529
if (d !== -1)
@@ -43,7 +47,7 @@ export function parse (_source, _name) {
4347
name = _name || '@';
4448

4549
imports = [];
46-
exports = new Set();
50+
exports = [];
4751

4852
source = _source;
4953
pos = -1;
@@ -202,7 +206,7 @@ export function parse (_source, _name) {
202206
if (templateDepth !== -1 || openTokenDepth)
203207
syntaxError();
204208

205-
return [imports, [...exports], facade];
209+
return [imports, exports, facade];
206210
}
207211

208212
function tryParseImportStatement () {
@@ -287,6 +291,7 @@ function tryParseImportStatement () {
287291

288292
function tryParseExportStatement () {
289293
const sStartPos = pos;
294+
const prevExport = exports.length;
290295

291296
pos += 6;
292297

@@ -300,7 +305,7 @@ function tryParseExportStatement () {
300305
switch (ch) {
301306
// export default ...
302307
case 100/*d*/:
303-
exports.add(source.slice(pos, pos + 7));
308+
addExport(pos, pos + 7, -1, -1);
304309
return;
305310

306311
// export async? function*? name () {
@@ -317,17 +322,18 @@ function tryParseExportStatement () {
317322
}
318323
const startPos = pos;
319324
ch = readToWsOrPunctuator(ch);
320-
exports.add(source.slice(startPos, pos));
325+
addExport(startPos, pos, startPos, pos);
321326
pos--;
322327
return;
323328

329+
// export class name ...
324330
case 99/*c*/:
325331
if (source.startsWith('lass', pos + 1) && isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos + 5))) {
326332
pos += 5;
327333
ch = commentWhitespace(true);
328334
const startPos = pos;
329335
ch = readToWsOrPunctuator(ch);
330-
exports.add(source.slice(startPos, pos));
336+
addExport(startPos, pos, startPos, pos);
331337
pos--;
332338
return;
333339
}
@@ -353,7 +359,7 @@ function tryParseExportStatement () {
353359
}
354360
if (pos === startPos)
355361
return;
356-
exports.add(source.slice(startPos, pos));
362+
addExport(startPos, pos, startPos, pos);
357363
ch = commentWhitespace(true);
358364
if (ch === 61/*=*/) {
359365
pos--;
@@ -404,6 +410,11 @@ function tryParseExportStatement () {
404410
if (ch === 102/*f*/ && source.startsWith('rom', pos + 1)) {
405411
pos += 4;
406412
readImportString(sStartPos, commentWhitespace(true));
413+
414+
// There were no local names.
415+
for (let i = prevExport; i < exports.length; ++i) {
416+
exports[i].ls = exports[i].le = -1;
417+
}
407418
}
408419
else {
409420
pos--;
@@ -556,6 +567,7 @@ function readCodePointToString () {
556567

557568
function readExportAs (startPos, endPos) {
558569
let ch = source.charCodeAt(pos);
570+
let ls = startPos, le = endPos;
559571
if (ch === 97 /*a*/) {
560572
pos += 2;
561573
ch = commentWhitespace(true);
@@ -565,7 +577,7 @@ function readExportAs (startPos, endPos) {
565577
ch = commentWhitespace(true);
566578
}
567579
if (pos !== startPos)
568-
exports.add(source.slice(startPos, endPos));
580+
addExport(startPos, endPos, ls, le);
569581
return ch;
570582
}
571583

0 commit comments

Comments
 (0)