diff --git a/lexer.js b/lexer.js index 3e30b6d..10fefbc 100755 --- a/lexer.js +++ b/lexer.js @@ -999,12 +999,16 @@ function tryParseLiteralExports () { pos++; ch = commentWhitespace(); // nothing more complex than identifier expressions for now - if (!identifier()) { + if (!identifier() && source.charCodeAt(pos) !== 92/*\*/) { pos = revertPos; return; } ch = source.charCodeAt(pos); } + else if (ch !== 44/*,*/ && ch !== 125/*}*/ && ch !== 61/*=*/ && ch !== 40/*(*/) { + pos = revertPos; + return; + } _exports.add(decode(source.slice(startPos, endPos))); } else if (ch === 46/*.*/ && source.startsWith('..', pos + 1)) { @@ -1027,7 +1031,7 @@ function tryParseLiteralExports () { pos++; ch = commentWhitespace(); // nothing more complex than identifier expressions for now - if (!identifier()) { + if (!identifier() && source.charCodeAt(pos) !== 92/*\*/) { pos = revertPos; return; } @@ -1305,14 +1309,14 @@ function isIdentifierChar(code, astral) { function identifier () { let ch = source.codePointAt(pos); - if (!isIdentifierStart(ch, true) || ch === '\\') + if (!isIdentifierStart(ch, true)) return false; pos += codePointLen(ch); while (ch = source.codePointAt(pos)) { if (isIdentifierChar(ch, true)) { pos += codePointLen(ch); } - else if (ch === '\\') { + else if (ch === 92/*\*/) { // no identifier escapes support for now return false; } diff --git a/lib/lexer.wasm b/lib/lexer.wasm index dc29138..78db5f1 100755 Binary files a/lib/lexer.wasm and b/lib/lexer.wasm differ diff --git a/src/lexer.c b/src/lexer.c index fd3f1cf..1745aca 100755 --- a/src/lexer.c +++ b/src/lexer.c @@ -1007,12 +1007,16 @@ void tryParseLiteralExports () { pos++; ch = commentWhitespace(); // nothing more complex than identifier expressions for now - if (!identifier(ch)) { + if (!identifier(ch) && *pos != '\\') { pos = revertPos; return; } ch = *pos; } + else if (ch != ',' && ch != '}' && ch != '=' && ch != '(') { + pos = revertPos; + return; + } addExport(startPos, endPos); } else if (ch == '\'' || ch == '"') { @@ -1024,7 +1028,7 @@ void tryParseLiteralExports () { pos++; ch = commentWhitespace(); // nothing more complex than identifier expressions for now - if (!identifier(ch)) { + if (!identifier(ch) && *pos != '\\') { pos = revertPos; return; } @@ -1161,7 +1165,7 @@ uint32_t fullCharCode(uint16_t ch) { bool identifier (uint16_t startCh) { uint32_t ch = fullCharCode(startCh); - if (!isIdentifierStart(ch) && ch != '\\') + if (!isIdentifierStart(ch)) return false; pos += charCodeByteLen(ch); while (ch = fullCharCode(*pos)) { diff --git a/src/lexer.js b/src/lexer.js index 431cb5b..599540c 100755 --- a/src/lexer.js +++ b/src/lexer.js @@ -42,7 +42,7 @@ export function parse (source, name = '@') { while (wasm.rre()) { const reexptStr = decode(source.slice(wasm.res(), wasm.ree())); - if (reexptStr) + if (reexptStr !== undefined) reexports.add(reexptStr); } while (wasm.ru()) { diff --git a/test/_unit.js b/test/_unit.js index 719d922..5a794ee 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -88,6 +88,79 @@ suite('Lexer', () => { } }); + test('does not detect method prefixes as object exports', () => { + for (const property of ['get value () { return value; }', 'set value (value) {}', + 'async value () {}', 'async *value () {}', + 'get "value" () { return value; }', 'get [value] () { return value; }', + 'get /* comment */ value () { return value; }']) { + assert.deepStrictEqual(parse(`module.exports = { before, ${property}, after };`), { + exports: ['before'], reexports: [] + }); + } + for (const property of ['get', 'get: value', '"get": value', "'get': value"]) { + assert.deepStrictEqual(parse(`module.exports = { ${property}, after };`), { + exports: ['get', 'after'], reexports: [] + }); + } + for (const name of ['value', 'get', 'set', 'async']) { + assert.deepStrictEqual(parse(`module.exports = { before, ${name} () {}, after };`), { + exports: ['before', name], reexports: [] + }); + } + }); + + test('does not detect escaped identifiers', () => { + for (const name of ['\\u0061', '\\u{61}', 'a\\u0062', 'a\\u{62}']) { + assert.deepStrictEqual(parse(`exports.${name} = value; module.exports = { ${name} };`), { + exports: [], reexports: [] + }); + } + assert.deepStrictEqual(parse('exports.α = value; exports.𐊧 = value;'), { + exports: ['α', '𐊧'], reexports: [] + }); + assert.deepStrictEqual(parse('exports["\\u0061"] = value; module.exports = {"\\u0062": value};'), { + exports: ['a', 'b'], reexports: [] + }); + }); + + test('preserves empty reexport specifiers', () => { + for (const source of ['module.exports = require("");', "module.exports = require('');", + '__exportStar(require(""), exports);', 'module.exports = { ...require("") };']) { + assert.deepStrictEqual(parse(source), { exports: [], reexports: [''] }); + } + assert.deepStrictEqual(parse('module.exports = require("\\uD800");'), { + exports: [], reexports: [] + }); + }); + + test('preserves export keys before unsupported identifier value expressions', () => { + for (const value of ['a.b', '\\u0061', '\\u{61}', 'a\\u0062', 'a\\u{62}']) { + for (const key of ['value', '"value"', "'value'"]) { + assert.deepStrictEqual(parse(`module.exports = { ${key}: ${value} };`), { + exports: ['value'], reexports: [] + }); + assert.deepStrictEqual(parse(`module.exports = { before, ${key}: ${value}, after }; exports.later = later;`), { + exports: ['before', 'value', 'later'], reexports: [] + }); + } + } + }); + + test('does not detect getters with escaped identifier returns', () => { + for (const name of ['\\u0061', '\\u{61}', 'a\\u0062', 'a\\u{62}']) { + const getter = `Object.defineProperty(exports, 'value', { get () { return ${name}; } });`; + assert.deepStrictEqual(parse(getter), { + exports: [], reexports: [] + }); + assert.deepStrictEqual(parse(`exports.value = value; ${getter}`), { + exports: [], reexports: [] + }); + } + assert.deepStrictEqual(parse("Object.defineProperty(exports, 'value', { get () { return module['\\u0061']; } });"), { + exports: ['value'], reexports: [] + }); + }); + test('automatic Wasm initialization', async () => { const lexer = await import('../dist/lexer.mjs?automatic-initialization');