diff --git a/README.md b/README.md index 1139b19..534fb7a 100755 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ EXPORT_STAR_LIB: `Object.keys(` IDENTIFIER$1 `).forEach(function (` IDENTIFIER$2 ``` * The returned export names are the matched `IDENTIFIER` and `IDENTIFIER_STRING` slots for all `EXPORTS_MEMBER`, `EXPORTS_DEFINE` and `EXPORTS_LITERAL` matches. -* The reexport specifiers are taken to be the `STRING_LITERAL` slots of all `MODULE_EXPORTS_ASSIGN` as well as all _top-level_ `EXPORT_STAR` `REQUIRE` matches and `EXPORTS_ASSIGN` matches whose `IDENTIFIER` also matches the first `IDENTIFIER` in `EXPORT_STAR_LIB`. +* The reexport specifiers are taken to be the `STRING_LITERAL` slot of the last `MODULE_EXPORTS_ASSIGN` as well as all _top-level_ `EXPORT_STAR` `REQUIRE` matches and `EXPORTS_ASSIGN` matches whose `IDENTIFIER` also matches the first `IDENTIFIER` in `EXPORT_STAR_LIB`. ### Parsing Examples @@ -180,16 +180,16 @@ module.exports = { #### module.exports reexport assignment -Any `module.exports = require('mod')` assignment is detected as a reexport: +Any `module.exports = require('mod')` assignment is detected as a reexport, but only the last one is returned: ```js -// DETECTS REEXPORTS: a, b, c +// DETECTS REEXPORTS: c module.exports = require('a'); (module => module.exports = require('b'))(NOT_MODULE); if (false) module.exports = require('c'); ``` -As a result, the total list of exports would be inferred as the union of all of these reexported modules, which can lead to possible over-classification. +This is to avoid overclassification in Webpack bundles with externals which include `module.exports = require('external')` in their source for every external dependency. #### Transpiler Re-exports diff --git a/include-wasm/cjs-module-lexer.h b/include-wasm/cjs-module-lexer.h index 72ab1a5..10c823b 100755 --- a/include-wasm/cjs-module-lexer.h +++ b/include-wasm/cjs-module-lexer.h @@ -123,8 +123,14 @@ void (*addExport)(const uint16_t*, const uint16_t*) = &_addExport; void (*addReexport)(const uint16_t*, const uint16_t*) = &_addReexport; bool parseCJS (uint16_t* source, uint32_t sourceLen, void (*addExport)(const uint16_t* start, const uint16_t* end), void (*addReexport)(const uint16_t* start, const uint16_t* end)); +enum RequireType { + Import, + ExportAssign, + ExportStar +}; + void tryBacktrackAddStarExportBinding (uint16_t* pos); -bool tryParseRequire (bool directStarExport); +bool tryParseRequire (enum RequireType requireType); void tryParseLiteralExports (); bool readExportsOrModuleDotExports (uint16_t ch); void tryParseModuleExportsDotAssign (); diff --git a/include/cjs-module-lexer.h b/include/cjs-module-lexer.h index d83907b..0bb8912 100755 --- a/include/cjs-module-lexer.h +++ b/include/cjs-module-lexer.h @@ -29,8 +29,14 @@ void bail (uint32_t err); bool parseCJS (uint16_t* source, uint32_t sourceLen, void (*addExport)(const uint16_t*, const uint16_t*), void (*addReexport)(const uint16_t*, const uint16_t*)); +enum RequireType { + Import, + ExportAssign, + ExportStar +}; + void tryBacktrackAddStarExportBinding (uint16_t* pos); -bool tryParseRequire (bool directStarExport); +bool tryParseRequire (enum RequireType requireType); void tryParseLiteralExports (); bool readExportsOrModuleDotExports (uint16_t ch); void tryParseModuleExportsDotAssign (); diff --git a/lexer.js b/lexer.js index 9b172bf..7f7c971 100755 --- a/lexer.js +++ b/lexer.js @@ -10,6 +10,7 @@ let openTokenDepth, nextBraceIsClass, starExportMap, lastStarExportSpecifier, + lastExportsAssignSpecifier, _exports, reexports; @@ -25,11 +26,17 @@ function resetState () { nextBraceIsClass = false; starExportMap = Object.create(null); lastStarExportSpecifier = null; + lastExportsAssignSpecifier = null; _exports = new Set(); reexports = new Set(); } +// RequireType +const Import = 0; +const ExportAssign = 1; +const ExportStar = 2; + const strictReserved = new Set(['implements', 'interface', 'let', 'package', 'private', 'protected', 'public', 'static', 'yield', 'enum']); module.exports = function parseCJS (source, name = '@') { @@ -42,6 +49,8 @@ module.exports = function parseCJS (source, name = '@') { e.loc = pos; throw e; } + if (lastExportsAssignSpecifier) + reexports.add(lastExportsAssignSpecifier); const result = { exports: [..._exports], reexports: [...reexports] }; resetState(); return result; @@ -85,7 +94,7 @@ function parseSource (cjsSource) { continue; case 114/*r*/: const startPos = pos; - if (tryParseRequire(false) && keywordStart(startPos)) + if (tryParseRequire(Import) && keywordStart(startPos)) tryBacktrackAddStarExportBinding(startPos - 1); lastTokenPos = pos; continue; @@ -97,7 +106,7 @@ function parseSource (cjsSource) { if (source.charCodeAt(pos) === 40/*(*/) { openTokenPosStack[openTokenDepth++] = lastTokenPos; if (source.charCodeAt(++pos) === 114/*r*/) - tryParseRequire(true); + tryParseRequire(ExportStar); } } lastTokenPos = pos; @@ -623,14 +632,14 @@ function tryParseExportsDotAssign (assign) { // require('...') if (ch === 114/*r*/) - tryParseRequire(true); + tryParseRequire(ExportAssign); } } } pos = revertPos; } -function tryParseRequire (directStarExport) { +function tryParseRequire (requireType) { // require('...') if (source.startsWith('equire', pos + 1)) { pos += 7; @@ -645,13 +654,17 @@ function tryParseRequire (directStarExport) { const reexportEnd = pos++; ch = commentWhitespace(); if (ch === 41/*)*/) { - if (directStarExport) { - reexports.add(source.slice(reexportStart, reexportEnd)); - } - else { - lastStarExportSpecifier = source.slice(reexportStart, reexportEnd); + switch (requireType) { + case ExportAssign: + lastExportsAssignSpecifier = source.slice(reexportStart, reexportEnd); + return true; + case ExportStar: + reexports.add(source.slice(reexportStart, reexportEnd)); + return true; + default: + lastStarExportSpecifier = source.slice(reexportStart, reexportEnd); + return true; } - return true; } } else if (ch === 34/*"*/) { @@ -659,13 +672,17 @@ function tryParseRequire (directStarExport) { const reexportEnd = pos++; ch = commentWhitespace(); if (ch === 41/*)*/) { - if (directStarExport) { - reexports.add(source.slice(reexportStart, reexportEnd)); - } - else { - lastStarExportSpecifier = source.slice(reexportStart, reexportEnd); + switch (requireType) { + case ExportAssign: + lastExportsAssignSpecifier = source.slice(reexportStart, reexportEnd); + return true; + case ExportStar: + reexports.add(source.slice(reexportStart, reexportEnd)); + return true; + default: + lastStarExportSpecifier = source.slice(reexportStart, reexportEnd); + return true; } - return true; } } } diff --git a/src/lexer.c b/src/lexer.c index b0cde97..b3c7c99 100755 --- a/src/lexer.c +++ b/src/lexer.c @@ -21,6 +21,8 @@ uint16_t* end; uint16_t* templateStack; uint16_t** openTokenPosStack; StarExportBinding* starExportStack; +uint16_t* reexportAssignStart; +uint16_t* reexportAssignEnd; bool nextBraceIsClass; uint16_t* lastReexportStart; @@ -62,6 +64,8 @@ bool parseCJS (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(const openTokenPosStack = &openTokenPosStack_[0]; starExportStack = &starExportStack_[0]; nextBraceIsClass = false; + reexportAssignStart = NULL; + reexportAssignEnd = NULL; pos = (uint16_t*)(source - 1); uint16_t ch = '\0'; @@ -94,7 +98,7 @@ bool parseCJS (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(const continue; case 'r': { uint16_t* startPos = pos; - if (tryParseRequire(false) && keywordStart(startPos)) + if (tryParseRequire(Import) && keywordStart(startPos)) tryBacktrackAddStarExportBinding(startPos - 1); lastTokenPos = pos; continue; @@ -107,7 +111,7 @@ bool parseCJS (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(const if (*pos == '(') { openTokenPosStack[openTokenDepth++] = lastTokenPos; if (*(++pos) == 'r') - tryParseRequire(true); + tryParseRequire(ExportStar); } } lastTokenPos = pos; @@ -217,6 +221,9 @@ bool parseCJS (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(const if (templateDepth != UINT16_MAX || openTokenDepth || has_error) return false; + if (reexportAssignStart) + addReexport(reexportAssignStart, reexportAssignEnd); + // success return true; } @@ -647,14 +654,14 @@ void tryParseExportsDotAssign (bool assign) { // require('...') if (ch == 'r') - tryParseRequire(true); + tryParseRequire(ExportAssign); } } } pos = revertPos; } -bool tryParseRequire (bool directStarExport) { +bool tryParseRequire (enum RequireType requireType) { // require('...') if (str_eq6(pos + 1, 'e', 'q', 'u', 'i', 'r', 'e')) { pos += 7; @@ -669,14 +676,19 @@ bool tryParseRequire (bool directStarExport) { uint16_t* reexportEnd = pos++; ch = commentWhitespace(); if (ch == ')') { - if (directStarExport) { - addReexport(reexportStart, reexportEnd); - } - else { - starExportStack->specifier_start = reexportStart; - starExportStack->specifier_end = reexportEnd; + switch (requireType) { + case ExportStar: + addReexport(reexportStart, reexportEnd); + return true; + case ExportAssign: + reexportAssignStart = reexportStart; + reexportAssignEnd = reexportEnd; + return true; + default: + starExportStack->specifier_start = reexportStart; + starExportStack->specifier_end = reexportEnd; + return true; } - return true; } } else if (ch == '"') { @@ -684,12 +696,18 @@ bool tryParseRequire (bool directStarExport) { uint16_t* reexportEnd = pos++; ch = commentWhitespace(); if (ch == ')') { - if (directStarExport) { - addReexport(reexportStart, reexportEnd); - } - else { - starExportStack->specifier_start = reexportStart; - starExportStack->specifier_end = reexportEnd; + switch (requireType) { + case ExportStar: + addReexport(reexportStart, reexportEnd); + return true; + case ExportAssign: + reexportAssignStart = reexportStart; + reexportAssignEnd = reexportEnd; + return true; + default: + starExportStack->specifier_start = reexportStart; + starExportStack->specifier_end = reexportEnd; + return true; } return true; } diff --git a/test/_unit.js b/test/_unit.js index 0f3cf05..5b8d8f8 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -423,9 +423,8 @@ suite('Lexer', () => { `); assert.equal(exports.length, 1); assert.equal(exports[0], 'asdf'); - assert.equal(reexports.length, 2); - assert.equal(reexports[0], './asdf'); - assert.equal(reexports[1], './another'); + assert.equal(reexports.length, 1); + assert.equal(reexports[0], './another'); }); test('Single parse cases', () => {