diff --git a/README.md b/README.md index 6decfaa..6a5c7a2 100755 --- a/README.md +++ b/README.md @@ -70,31 +70,35 @@ STRING_LITERAL: A `"` or `'` bounded ECMA-262 string literal. IDENTIFIER_STRING: ( `"` IDENTIFIER `"` | `'` IDENTIFIER `'` ) -COMMENT_SPACE: Any ECMA-262 whitespace, ECMA-262 block comment or ECMA-262 line comment - -MODULE_EXPORTS: `module` COMMENT_SPACE `.` COMMENT_SPACE `exports` +MODULE_EXPORTS: `module` `.` `exports` EXPORTS_IDENTIFIER: MODULE_EXPORTS_IDENTIFIER | `exports` -EXPORTS_DOT_ASSIGN: EXPORTS_IDENTIFIER COMMENT_SPACE `.` COMMENT_SPACE IDENTIFIER COMMENT_SPACE `=` +EXPORTS_DOT_ASSIGN: EXPORTS_IDENTIFIER `.` IDENTIFIER `=` -EXPORTS_LITERAL_COMPUTED_ASSIGN: EXPORTS_IDENTIFIER COMMENT_SPACE `[` COMMENT_SPACE IDENTIFIER_STRING COMMENT_SPACE `]` COMMENT_SPACE `=` +EXPORTS_LITERAL_COMPUTED_ASSIGN: EXPORTS_IDENTIFIER `[` IDENTIFIER_STRING `]` `=` -EXPORTS_LITERAL_PROP: (IDENTIFIER (COMMENT_SPACE `:` COMMENT_SPACE IDENTIFIER)?) | (IDENTIFIER_STRING COMMENT_SPACE `:` COMMENT_SPACE IDENTIFIER) +EXPORTS_LITERAL_PROP: (IDENTIFIER `:` IDENTIFIER)?) | (IDENTIFIER_STRING `:` IDENTIFIER) -EXPORTS_SPREAD: `...` COMMENT_SPACE (IDENTIFIER | REQUIRE) +EXPORTS_SPREAD: `...` (IDENTIFIER | REQUIRE) EXPORTS_MEMBER: EXPORTS_DOT_ASSIGN | EXPORTS_LITERAL_COMPUTED_ASSIGN -ES_MODULE_DEFINE: `Object` COMMENT_SPACE `.` COMMENT_SPACE `defineProperty COMMENT_SPACE `(` COMMENT_SPACE `__esModule` COMMENT_SPACE `,` COMMENT_SPACE IDENTIFIER_STRING +EXPORTS_DEFINE: `Object` `.` `defineProperty `(` IDENTIFIER_STRING `, {` + (`enumerable: true,`)? + ( + `value:` | + `get:` `function`? `()` {` return IDENTIFIER (`.` IDENTIFIER | `[` IDENTIFIER_STRING `]`)? `;`? `}` + ) + `})` -EXPORTS_LITERAL: MODULE_EXPORTS COMMENT_SPACE `=` COMMENT_SPACE `{` COMMENT_SPACE (EXPORTS_LITERAL_PROP | EXPORTS_SPREAD) COMMENT_SPACE `,` COMMENT_SPACE)+ `}` +EXPORTS_LITERAL: MODULE_EXPORTS `=` `{` (EXPORTS_LITERAL_PROP | EXPORTS_SPREAD) `,`)+ `}` -REQUIRE: `require` COMMENT_SPACE `(` COMMENT_SPACE STRING_LITERAL COMMENT_SPACE `)` +REQUIRE: `require` `(` STRING_LITERAL `)` EXPORTS_ASSIGN: (`var` | `const` | `let`) IDENTIFIER `=` REQUIRE -MODULE_EXPORTS_ASSIGN: MODULE_EXPORTS COMMENT_SPACE `=` COMMENT_SPACE REQUIRE +MODULE_EXPORTS_ASSIGN: MODULE_EXPORTS `=` REQUIRE EXPORT_STAR: (`__export` | `__exportStar`) `(` REQUIRE @@ -113,9 +117,9 @@ EXPORT_STAR_LIB: `Object.keys(` IDENTIFIER$1 `).forEach(function (` IDENTIFIER$2 `})` ``` -* The returned export names are taken to be the combination of: - 1. `IDENTIFIER` and `IDENTIFIER_STRING` slots for all `EXPORTS_MEMBER` and `EXPORTS_LITERAL` matches. - 2. `__esModule` if there is an `ES_MODULE_DEFINE` match. +Spacing between tokens is taken to be any ECMA-262 whitespace, ECMA-262 block comment or ECMA-262 line comment. + +* The returned export names are taken to be the combination of the `IDENTIFIER` and `IDENTIFIER_STRING` slots for all `EXPORTS_MEMBER`, `EXPORTS_LITERAL` and `EXPORTS_DEFINE` matches. * The reexport specifiers are taken to be the the combination of: 1. The `REQUIRE` matches of the last matched of either `MODULE_EXPORTS_ASSIGN` or `EXPORTS_LITERAL`. 2. All _top-level_ `EXPORT_STAR` `REQUIRE` matches and `EXPORTS_ASSIGN` matches whose `IDENTIFIER` also matches the first `IDENTIFIER` in `EXPORT_STAR_LIB`. @@ -156,17 +160,66 @@ It will in turn underclassify in cases where the identifiers are renamed: })(exports); ``` -#### __esModule Detection - -In addition, `__esModule` is detected as an export when set by `Object.defineProperty`: +`Object.defineProperty` is detected for specifically value and getter forms returning an identifier or member expression: ```js -// DETECTS: __esModule -Object.defineProperty(exports, 'a', { value: 'a' }); +// DETECTS: a, b, c, d, __esModule +Object.defineProperty(exports, 'a', { + enumerable: true, + get: function () { + return q.p; + } +}); +Object.defineProperty(exports, 'b', { + enumerable: true, + get: function () { + return q['p']; + } +}); +Object.defineProperty(exports, 'c', { + enumerable: true, + get () { + return b; + } +}); +Object.defineProperty(exports, 'd', { value: 'd' }); Object.defineProperty(exports, '__esModule', { value: true }); ``` -No other named exports are detected for `defineProperty` calls in order not to trigger getters or non-enumerable properties unnecessarily. +Alternative object definition structures or getter function bodies are not detected: + +```js +// DETECTS: NO EXPORTS +Object.defineProperty(exports, 'a', { + enumerable: false, + get () { + return p; + } +}); +Object.defineProperty(exports, 'b', { + configurable: true, + get () { + return p; + } +}); +Object.defineProperty(exports, 'c', { + get: () => p +}); +Object.defineProperty(exports, 'd', { + enumerable: true, + get: function () { + return dynamic(); + } +}); +Object.defineProperty(exports, 'e', { + enumerable: true, + get () { + return 'str'; + } +}); +``` + +`Object.defineProperties` is also not supported. #### Exports Object Assignment diff --git a/lexer.js b/lexer.js index 9340831..845f82b 100755 --- a/lexer.js +++ b/lexer.js @@ -260,30 +260,114 @@ function tryParseObjectDefineOrKeys (keys) { pos++; ch = commentWhitespace(); if (ch === 100/*d*/ && source.startsWith('efineProperty', pos + 1)) { - pos += 14; - revertPos = pos - 1; - ch = commentWhitespace(); - if (ch !== 40/*(*/) { - pos = revertPos; - return; - } - pos++; - ch = commentWhitespace(); - if (readExportsOrModuleDotExports(ch)) { + while (true) { + pos += 14; + revertPos = pos - 1; + ch = commentWhitespace(); + if (ch !== 40/*(*/) break; + pos++; + ch = commentWhitespace(); + if (!readExportsOrModuleDotExports(ch)) break; + ch = commentWhitespace(); + if (ch !== 44/*,*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 39/*'*/ && ch !== 34/*"*/) break; + let quot = ch; + const exportPos = ++pos; + if (!identifier() || source.charCodeAt(pos) !== quot) break; + const expt = source.slice(exportPos, pos); + pos++; + ch = commentWhitespace(); + if (ch !== 44/*,*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 123/*{*/) break; + pos++; ch = commentWhitespace(); - if (ch === 44/*,*/) { + if (ch === 101/*e*/) { + if (!source.startsWith('numerable', pos + 1)) break; + pos += 10; + ch = commentWhitespace(); + if (ch !== 58/*:*/) break; pos++; ch = commentWhitespace(); - if (ch === 39/*'*/ || ch === 34/*"*/) { - const exportPos = ++pos; - if (identifier() && source.charCodeAt(pos) === ch) { - // revert for "(" - const expt = source.slice(exportPos, pos); - if (expt === '__esModule') - addExport(expt); - } + if (ch !== 116/*t*/ || !source.startsWith('rue', pos + 1)) break; + pos += 4; + ch = commentWhitespace(); + if (ch !== 44) break; + pos++; + ch = commentWhitespace(); + } + if (ch === 118/*v*/) { + if (!source.startsWith('alue', pos + 1)) break; + pos += 5; + ch = commentWhitespace(); + if (ch !== 58/*:*/) break; + pos++; + addExport(expt); + break; + } + else if (ch === 103/*g*/) { + if (!source.startsWith('et', pos + 1)) break; + pos += 3; + ch = commentWhitespace(); + if (ch === 58/*:*/) { + pos++; + ch = commentWhitespace(); + if (ch !== 102/*f*/) break; + if (!source.startsWith('unction', pos + 1)) break; + pos += 8; + ch = commentWhitespace(); + } + if (ch !== 40/*(*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 41/*)*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 123/*{*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 114/*r*/) break; + if (!source.startsWith('eturn', pos + 1)) break; + pos += 6; + ch = commentWhitespace(); + if (!identifier()) break; + ch = commentWhitespace(); + if (ch === 46/*.*/) { + pos++; + commentWhitespace(); + if (!identifier()) break; + ch = commentWhitespace(); + } + else if (ch === 91/*[*/) { + pos++; + ch = commentWhitespace(); + if (ch === 39/*'*/) singleQuoteString(); + else if (ch === 34/*"*/) doubleQuoteString(); + else break; + pos++; + ch = commentWhitespace(); + if (ch !== 93/*]*/) break; + pos++; + ch = commentWhitespace(); + } + if (ch === 59/*;*/) { + pos++; + ch = commentWhitespace(); } + if (ch !== 125/*}*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 125/*}*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 41/*)*/) break; + addExport(expt); + return; } + break; } } else if (keys && ch === 107/*k*/ && source.startsWith('eys', pos + 1)) { diff --git a/src/lexer.c b/src/lexer.c index 6c76baa..737420e 100755 --- a/src/lexer.c +++ b/src/lexer.c @@ -272,29 +272,118 @@ void tryParseObjectDefineOrKeys (bool keys) { pos++; ch = commentWhitespace(); if (ch == 'd' && str_eq13(pos + 1, 'e', 'f', 'i', 'n', 'e', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'y')) { - pos += 14; - revertPos = pos - 1; - ch = commentWhitespace(); - if (ch != '(') { - pos = revertPos; - return; - } - pos++; - ch = commentWhitespace(); - if (readExportsOrModuleDotExports(ch)) { + while (true) { + pos += 14; + revertPos = pos - 1; + ch = commentWhitespace(); + if (ch != '(') break; + pos++; + ch = commentWhitespace(); + if (!readExportsOrModuleDotExports(ch)) break; + ch = commentWhitespace(); + if (ch != ',') break; + pos++; + ch = commentWhitespace(); + if (ch != '\'' && ch != '"') break; + uint16_t quot = ch; + uint16_t* exportStart = ++pos; + if (!identifier(*pos) || *pos != quot) break; + uint16_t* exportEnd = pos; + pos++; + ch = commentWhitespace(); + if (ch != ',') break; + pos++; + ch = commentWhitespace(); + if (ch != '{') break; + pos++; ch = commentWhitespace(); - if (ch == ',') { + if (ch == 'e') { + if (!str_eq9(pos + 1, 'n', 'u', 'm', 'e', 'r', 'a', 'b', 'l', 'e')) break; + pos += 10; + ch = commentWhitespace(); + if (ch != ':') break; pos++; ch = commentWhitespace(); - if (ch == '\'' || ch == '"') { - uint16_t* exportPos = ++pos; - if (identifier(*pos) && *pos == ch) { - // revert for "(" - if (pos - exportPos == 10 && str_eq10(exportPos, '_', '_', 'e', 's', 'M', 'o', 'd', 'u', 'l', 'e')) - addExport(exportPos, pos); - } + if (ch != 't' || !str_eq3(pos + 1, 'r', 'u', 'e')) break; + pos += 4; + ch = commentWhitespace(); + if (ch != 44) break; + pos++; + ch = commentWhitespace(); + } + if (ch == 'v') { + if (!str_eq4(pos + 1, 'a', 'l', 'u', 'e')) break; + pos += 5; + ch = commentWhitespace(); + if (ch != ':') break; + pos++; + addExport(exportStart, exportEnd); + break; + } + else if (ch == 'g') { + if (!str_eq2(pos + 1, 'e', 't')) break; + pos += 3; + ch = commentWhitespace(); + if (ch == ':') { + pos++; + ch = commentWhitespace(); + if (ch != 'f') break; + if (!str_eq7(pos + 1, 'u', 'n', 'c', 't', 'i', 'o', 'n')) break; + pos += 8; + ch = commentWhitespace(); + } + if (ch != '(') break; + pos++; + ch = commentWhitespace(); + if (ch != ')') break; + pos++; + ch = commentWhitespace(); + if (ch != '{') break; + pos++; + ch = commentWhitespace(); + if (ch != 'r') break; + if (!str_eq5(pos + 1, 'e', 't', 'u', 'r', 'n')) break; + pos += 6; + ch = commentWhitespace(); + if (!identifier(ch)) break; + ch = commentWhitespace(); + if (ch == '.') { + pos++; + ch = commentWhitespace(); + if (!identifier(ch)) break; + ch = commentWhitespace(); + } + else if (ch == '[') { + pos++; + ch = commentWhitespace(); + if (ch == '\'') singleQuoteString(); + else if (ch == '"') doubleQuoteString(); + else break; + pos++; + ch = commentWhitespace(); + if (ch != ']') break; + pos++; + ch = commentWhitespace(); + } + if (ch == ';') { + pos++; + ch = commentWhitespace(); + } + if (ch != '}') break; + if (ch == ',') { + pos++; + ch = commentWhitespace(); } + pos++; + ch = commentWhitespace(); + if (ch != '}') break; + pos++; + ch = commentWhitespace(); + if (ch != ')') break; + addExport(exportStart, exportEnd); + return; } + break; } } else if (keys && ch == 'k' && str_eq3(pos + 1, 'e', 'y', 's')) { diff --git a/test/_unit.js b/test/_unit.js index 39be36f..945d0c3 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -37,6 +37,47 @@ suite('Lexer', () => { assert.equal(reexports[3], 'external4'); }); + test('Rollup Babel reexport getter', () => { + var { exports } = parse(` + Object.defineProperty(exports, 'a', { + enumerable: true, + get: function () { + return q.p; + } + }); + + Object.defineProperty(exports, 'b', { + enumerable: false, + get: function () { + return q.p; + } + }); + + Object.defineProperty(exports, "c", { + get: function () { + return q['p' ]; + } + }); + + Object.defineProperty(exports, 'd', { + get: function () { + return __ns.val; + } + }); + + Object.defineProperty(exports, 'e', { + get () { + return external; + } + }); + `); + assert.equal(exports.length, 4); + assert.equal(exports[0], 'a'); + assert.equal(exports[1], 'c'); + assert.equal(exports[2], 'd'); + assert.equal(exports[3], 'e'); + }); + test('Rollup Babel reexports', () => { var { exports, reexports } = parse(` "use strict"; @@ -447,14 +488,47 @@ suite('Lexer', () => { assert.equal(exports[1], 'Tokenizer'); }); - test('defineProperty', () => { + test('defineProperty value', () => { const { exports } = parse(` - Object.defineProperty(exports, 'namedExport', { value: true }); + Object.defineProperty(exports, 'namedExport', { enumerable: false, value: true }); + Object.defineProperty(exports, 'namedExport', { configurable: false, value: true }); + + Object.defineProperty(exports, 'a', { + enumerable: false, + get () { + return p; + } + }); + Object.defineProperty(exports, 'b', { + configurable: true, + get () { + return p; + } + }); + Object.defineProperty(exports, 'c', { + get: () => p + }); + Object.defineProperty(exports, 'd', { + enumerable: true, + get: function () { + return dynamic(); + } + }); + Object.defineProperty(exports, 'e', { + enumerable: true, + get () { + return 'str'; + } + }); + Object.defineProperty(module.exports, 'thing', { value: true }); + Object.defineProperty(exports, "other", { enumerable: true, value: true }); Object.defineProperty(exports, "__esModule", { value: true }); `); - assert.equal(exports.length, 1); - assert.equal(exports[0], '__esModule'); + assert.equal(exports.length, 3); + assert.equal(exports[0], 'thing'); + assert.equal(exports[1], 'other'); + assert.equal(exports[2], '__esModule'); }); test('module assign', () => {