From f5a288be8799aa41186f68499d9e4abb5d2737d6 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 31 Oct 2020 06:55:19 -0700 Subject: [PATCH 1/5] support specific getter forms --- README.md | 79 ++++++++++++++++++++++++-------- lexer.js | 124 ++++++++++++++++++++++++++++++++++++++++++-------- test/_unit.js | 45 ++++++++++++++++-- 3 files changed, 205 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 6decfaa..27f62e2 100755 --- a/README.md +++ b/README.md @@ -70,31 +70,34 @@ 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 +116,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`. @@ -124,7 +127,7 @@ EXPORT_STAR_LIB: `Object.keys(` IDENTIFIER$1 `).forEach(function (` IDENTIFIER$2 #### Named Exports Parsing -The basic matching rules for named exports are `exports.name`, `exports['name']` or `Object.defineProperty(exports, 'name', ...)`. This matching is done without scope analysis and regardless of the expression position: +The basic matching rules for named exports are `exports.name` and `exports['name']`. This matching is done without scope analysis and regardless of the expression position: ```js // DETECTS EXPORTS: a, b @@ -156,17 +159,53 @@ It will in turn underclassify in cases where the identifiers are renamed: })(exports); ``` -#### __esModule Detection +#### Exports Getters -In addition, `__esModule` is detected as an export when set by `Object.defineProperty`: +`Object.defineProperty` is detected for any usage of the `__esModule` export, as well as the specific value and getter forms: ```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. +Dynamic getter functions that do not return a direct identifier or member expression are not detected: + +```js +// DETECTS: NO EXPORTS +Object.defineProperty(exports, 'a', { + enumerable: true, + get: function () { + return dynamic(); + } +}); +Object.defineProperty(exports, 'b', { + enumerable: true, + get () { + return 'nope'; + } +}); +``` + +`Object.defineProperties` is also not supported. #### Exports Object Assignment diff --git a/lexer.js b/lexer.js index 9340831..299146c 100755 --- a/lexer.js +++ b/lexer.js @@ -260,30 +260,116 @@ 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) !== ch) break; + // revert for "(" + const expt = source.slice(exportPos, pos); + if (source.charCodeAt(pos) !== quot) break; + 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; + addExport(expt); + pos++; + ch = commentWhitespace(); + if (ch !== 125/*}*/) break; + pos++; + ch = commentWhitespace(); + if (ch !== 41/*)*/) break; + return; } + break; } } else if (keys && ch === 107/*k*/ && source.startsWith('eys', pos + 1)) { diff --git a/test/_unit.js b/test/_unit.js index 39be36f..b20ec7d 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -37,6 +37,40 @@ 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; + } + }); + `); + assert.equal(exports.length, 3); + assert.equal(exports[0], 'a'); + assert.equal(exports[1], 'c'); + assert.equal(exports[2], 'd'); + }); + test('Rollup Babel reexports', () => { var { exports, reexports } = parse(` "use strict"; @@ -447,14 +481,17 @@ 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(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', () => { From 8145ce12b0cabb2add0085110baad9b45fb65cfb Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 31 Oct 2020 08:55:51 -0700 Subject: [PATCH 2/5] wasm impl and updtes --- README.md | 22 +++++++-- lexer.js | 6 +-- src/lexer.c | 125 ++++++++++++++++++++++++++++++++++++++++++-------- test/_unit.js | 39 +++++++++++++++- 4 files changed, 166 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 27f62e2..b378ff4 100755 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ EXPORTS_DEFINE: `Object` `.` `defineProperty `(` IDENTIFIER_STRING `, {` `value:` | `get:` `function`? `()` {` return IDENTIFIER (`.` IDENTIFIER | `[` IDENTIFIER_STRING `]`)? `;`? `}` ) + `})` EXPORTS_LITERAL: MODULE_EXPORTS `=` `{` (EXPORTS_LITERAL_PROP | EXPORTS_SPREAD) `,`)+ `}` @@ -187,20 +188,35 @@ Object.defineProperty(exports, 'd', { value: 'd' }); Object.defineProperty(exports, '__esModule', { value: true }); ``` -Dynamic getter functions that do not return a direct identifier or member expression are not detected: +Other getter structurs or not return a direct identifier or member expression 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, 'b', { +Object.defineProperty(exports, 'e', { enumerable: true, get () { - return 'nope'; + return 'str'; } }); ``` diff --git a/lexer.js b/lexer.js index 299146c..845f82b 100755 --- a/lexer.js +++ b/lexer.js @@ -275,10 +275,8 @@ function tryParseObjectDefineOrKeys (keys) { if (ch !== 39/*'*/ && ch !== 34/*"*/) break; let quot = ch; const exportPos = ++pos; - if (!identifier() || source.charCodeAt(pos) !== ch) break; - // revert for "(" + if (!identifier() || source.charCodeAt(pos) !== quot) break; const expt = source.slice(exportPos, pos); - if (source.charCodeAt(pos) !== quot) break; pos++; ch = commentWhitespace(); if (ch !== 44/*,*/) break; @@ -360,13 +358,13 @@ function tryParseObjectDefineOrKeys (keys) { ch = commentWhitespace(); } if (ch !== 125/*}*/) break; - addExport(expt); pos++; ch = commentWhitespace(); if (ch !== 125/*}*/) break; pos++; ch = commentWhitespace(); if (ch !== 41/*)*/) break; + addExport(expt); return; } break; 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 b20ec7d..945d0c3 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -64,11 +64,18 @@ suite('Lexer', () => { return __ns.val; } }); + + Object.defineProperty(exports, 'e', { + get () { + return external; + } + }); `); - assert.equal(exports.length, 3); + 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', () => { @@ -484,6 +491,36 @@ suite('Lexer', () => { test('defineProperty value', () => { const { exports } = parse(` 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 }); From 306ca5575dc3433965f05bd6afb4c7eb2a42184b Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 31 Oct 2020 08:56:57 -0700 Subject: [PATCH 3/5] readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index b378ff4..6838112 100755 --- a/README.md +++ b/README.md @@ -160,8 +160,6 @@ It will in turn underclassify in cases where the identifiers are renamed: })(exports); ``` -#### Exports Getters - `Object.defineProperty` is detected for any usage of the `__esModule` export, as well as the specific value and getter forms: ```js From 35b27659b5ec2293a7ef7224753dc504310a505e Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 31 Oct 2020 08:58:00 -0700 Subject: [PATCH 4/5] readme clarifications --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6838112..d45e217 100755 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ It will in turn underclassify in cases where the identifiers are renamed: })(exports); ``` -`Object.defineProperty` is detected for any usage of the `__esModule` export, as well as the specific value and getter forms: +`Object.defineProperty` is detected for specifically value and getter forms returning an identifier or member expression: ```js // DETECTS: a, b, c, d, __esModule @@ -186,7 +186,7 @@ Object.defineProperty(exports, 'd', { value: 'd' }); Object.defineProperty(exports, '__esModule', { value: true }); ``` -Other getter structurs or not return a direct identifier or member expression are not detected: +Alternative object definition structures or getter function bodies are not detected: ```js // DETECTS: NO EXPORTS From 88e514c79cbce7ce48db3d7be360fcefc8fb2f4c Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 31 Oct 2020 09:01:35 -0700 Subject: [PATCH 5/5] readme tweak --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d45e217..6a5c7a2 100755 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Spacing between tokens is taken to be any ECMA-262 whitespace, ECMA-262 block co #### Named Exports Parsing -The basic matching rules for named exports are `exports.name` and `exports['name']`. This matching is done without scope analysis and regardless of the expression position: +The basic matching rules for named exports are `exports.name`, `exports['name']` or `Object.defineProperty(exports, 'name', ...)`. This matching is done without scope analysis and regardless of the expression position: ```js // DETECTS EXPORTS: a, b