From 3ad2801a4df8b6ab6c56b7d571d3db20ea0a9cff Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Tue, 27 Oct 2026 17:42:06 +0100 Subject: [PATCH] feat(lexer): detect Webpack entry exports in extended mode Scanning every factory in a Webpack bundle reports internal names alongside the published entry's exports. Extended mode now follows recognized Webpack startup and reexport patterns to exclude unrelated factory exports. Refs: https://github.com/nodejs/cjs-module-lexer/issues/88 Signed-off-by: Ruben Bridgewater --- README.md | 10 +- build/Makefile | 2 +- include-wasm/cjs-module-lexer.h | 4 +- lexer.js | 305 +- lib/lexer.wasm | Bin 28959 -> 40106 bytes lib/lexer.wat | 24939 ++++++++++++++--------- src/lexer.c | 526 +- test/fixtures/webpack/LICENSE | 22 + test/fixtures/webpack/jest-watcher.cjs | 556 + test/webpack.js | 302 + 10 files changed, 16897 insertions(+), 9769 deletions(-) create mode 100644 test/fixtures/webpack/LICENSE create mode 100644 test/fixtures/webpack/jest-watcher.cjs create mode 100644 test/webpack.js diff --git a/README.md b/README.md index 7d3305a..2d71ff5 100755 --- a/README.md +++ b/README.md @@ -111,9 +111,17 @@ The same limitation applies to `Object.defineProperties`. Direct reexport assignments contribute all detected dependencies instead of only the last assignment. Object spreads of `require('literal')` contribute reexports, but named properties with require-valued expressions do not. +Extended mode recognizes Webpack assignments through `__webpack_exports__` and getter maps in `__webpack_require__.d`. +For object-shaped `__webpack_modules__` tables, a literal startup module ID selects the entry factory. +Detection follows internal whole-module reexports and supported star reexports, which exclude `default` and `__esModule`. +An inlined entry can instead initialize `__webpack_exports__` with an object literal. +Unrelated factories do not contribute names when the bundle publishes the recognized entry. +Unrecognized startup expressions and unresolved internal reexports retain candidate detection. + The result contains syntactic candidates, which can include non-enumerable properties, throwing getters, and unreachable assignments. Extended mode does not apply the default getter side-effect veto. -It does not resolve bindings or aliases, evaluate computed names, or determine runtime control flow. +It recognizes Webpack factory parameters but does not generally resolve bindings or aliases. +It does not evaluate computed names or determine runtime control flow. Names created by dynamic expressions can therefore remain undetected, and shadowed identifiers can produce false positives. Division and regular-expression detection remains heuristic. diff --git a/build/Makefile b/build/Makefile index 3a29c0c..809b50f 100755 --- a/build/Makefile +++ b/build/Makefile @@ -21,7 +21,7 @@ lib/lexer.wasm: include-wasm/cjs-module-lexer.h src/lexer.c $(WASM_LDFLAGS) $(WASM_EXTRA_LDFLAGS) optimize: lib/lexer.wasm - ${WASM_OPT} -Oz lib/lexer.wasm -o lib/lexer.wasm + ${WASM_OPT} -Oz --enable-bulk-memory --strip-debug lib/lexer.wasm -o lib/lexer.wasm clean: rm lib/* diff --git a/include-wasm/cjs-module-lexer.h b/include-wasm/cjs-module-lexer.h index 77911fe..262f92a 100755 --- a/include-wasm/cjs-module-lexer.h +++ b/include-wasm/cjs-module-lexer.h @@ -197,8 +197,8 @@ void tryParseLiteralExports (); void readExportDefinition (); void readExportProperty (); bool readExportsOrModuleDotExports (uint16_t ch); -void tryParseModuleExportsDotAssign (); -void tryParseExportsDotAssign (bool assign); +void tryParseModuleExportsDotAssign (uint32_t length); +void tryParseExportsDotAssign (bool assign, uint32_t length); void tryParseObjectDefineOrKeys (bool keys); bool identifier (uint16_t ch); diff --git a/lexer.js b/lexer.js index 2217554..08d762a 100755 --- a/lexer.js +++ b/lexer.js @@ -1,8 +1,10 @@ let source, pos, end; let extended, exportObjectPos; +let webpack; const STACK_DEPTH = 2048; const ClassToken = 1; const ExportObjectToken = 2; +const WebpackTableToken = 4; const templateStack = new Array(1024); const openTokenPosStack = new Array(1024); const openTokenTypeStack = new Array(1024); @@ -27,6 +29,7 @@ function resetState () { templateStackDepth = 0; nextBraceIsClass = false; allowMinifiedEnumerable = false; + webpack = undefined; starExportMap = Object.create(null); lastStarExportSpecifier = null; @@ -78,6 +81,7 @@ function parseCJS (source, name = '@', options) { error.loc = pos; throw error; } + if (webpack) finishWebpack(); const result = { exports: [..._exports].filter(expt => expt !== undefined && !unsafeGetters.has(expt)), reexports: [...reexports].filter(reexpt => reexpt !== undefined) }; resetState(); return result; @@ -116,6 +120,7 @@ function decode (str) { /** @param {string} cjsSource */ function parseSource (cjsSource) { + const extendedMode = extended; source = cjsSource; pos = -1; end = source.length - 1; @@ -139,7 +144,13 @@ function parseSource (cjsSource) { if (ch === 32 || ch < 14 && ch > 8) continue; - if (openTokenDepth === 0) { + if (extendedMode && keywordStart(pos) && readWebpackToken(ch)) { + skipTokenRun(); + lastTokenPos = pos; + continue; + } + + if (openTokenDepth === 0 || extendedMode && webpack?.current?.depth === openTokenDepth) { switch (ch) { case 105/*i*/: if (source.startsWith('mport', pos + 1) && keywordStart(pos)) @@ -207,25 +218,28 @@ function parseSource (cjsSource) { break; case 79/*O*/: if (source.startsWith('bject', pos + 1) && keywordStart(pos)) - tryParseObjectDefineOrKeys(openTokenDepth === 0); + tryParseObjectDefineOrKeys(openTokenDepth === 0 || extendedMode && + (webpack?.current ? webpack.current.depth === openTokenDepth : webpack?.inline)); skipTokenRun(); break; case 40/*(*/: pushOpenToken(lastTokenPos); break; case 91/*[*/: - if (extended) { + if (extendedMode) { pushOpenToken(lastTokenPos); } break; case 93/*]*/: - if (extended) { + if (extendedMode) { if (openTokenDepth === 0) throw new Error('Unexpected closing bracket.'); openTokenDepth--; } break; case 44/*,*/: - if (extended && openTokenDepth && (openTokenTypeStack[openTokenDepth - 1] & ExportObjectToken)) + if (extendedMode && openTokenDepth && (openTokenTypeStack[openTokenDepth - 1] & WebpackTableToken)) + readWebpackFactory(); + if (extendedMode && openTokenDepth && (openTokenTypeStack[openTokenDepth - 1] & ExportObjectToken)) readExportProperty(); break; case 41/*)*/: @@ -237,15 +251,32 @@ function parseSource (cjsSource) { pushOpenToken(lastTokenPos); openTokenTypeStack[openTokenDepth - 1] = nextBraceIsClass ? ClassToken : 0; nextBraceIsClass = false; - if (extended && pos === exportObjectPos) { + if (extendedMode && pos === exportObjectPos) { openTokenTypeStack[openTokenDepth - 1] |= ExportObjectToken; exportObjectPos = -1; readExportProperty(); } + if (extendedMode && pos === webpack?.tablePos) { + openTokenTypeStack[openTokenDepth - 1] |= WebpackTableToken; + readWebpackFactory(); + } + else if (extendedMode && pos === webpack?.factoryPos) { + webpack.current = webpack.pending; + webpack.current.depth = openTokenDepth; + _exports = webpack.current.exports ??= new Set(); + reexports = webpack.current.reexports ??= new Set(); + starExportMap = Object.create(null); + } break; case 125/*}*/: if (openTokenDepth === 0) throw new Error('Unexpected closing brace.'); + if (extendedMode && webpack?.current?.depth === openTokenDepth) { + webpack.current = undefined; + _exports = webpack.exports; + reexports = webpack.reexports; + starExportMap = webpack.starExportMap; + } if (openTokenDepth-- === templateDepth) { templateDepth = templateStack[--templateStackDepth]; templateString(); @@ -286,7 +317,7 @@ function parseSource (cjsSource) { lastToken === 41/*)*/ && isParenKeyword(openTokenPosStack[openTokenDepth]) || lastToken === 125/*}*/ && (isExpressionTerminator(openTokenPosStack[openTokenDepth]) || (openTokenTypeStack[openTokenDepth] & ClassToken) || - extended && isStatementBrace(openTokenPosStack[openTokenDepth], openTokenDepth)) || + extendedMode && isStatementBrace(openTokenPosStack[openTokenDepth], openTokenDepth)) || lastToken === 47/*/*/ && lastSlashWasDivision || isExpressionKeyword(lastTokenPos) || !lastToken) { @@ -316,6 +347,218 @@ function parseSource (cjsSource) { throw new Error('Unterminated braces.'); } +/** @param {number} ch */ +function readWebpackToken (ch) { + const current = webpack?.current; + const target = current?.target; + if (target && source.startsWith(target, pos) && !isIdentifierChar(source.codePointAt(pos + target.length), true)) { + tryParseExportsDotAssign(false, target.length); + return true; + } + if (current?.module && source.startsWith(current.module, pos) && + !isIdentifierChar(source.codePointAt(pos + current.module.length), true)) { + tryParseModuleExportsDotAssign(current.module.length); + return true; + } + if (ch !== 95/*_*/) return false; + if (source.startsWith('__webpack_modules__', pos) && + !isIdentifierChar(source.codePointAt(pos + 19), true)) { + const start = pos; + pos += 19; + if (commentWhitespace() === 61/*=*/) { + pos++; + let next = commentWhitespace(); + if (next === 40/*(*/) next = readExportExpressionStart(); + if (next === 123/*{*/) { + if (!webpack) { + webpack = { modules: new Map(), exports: _exports, reexports, + starExportMap, inlineDependencies: [], tablePos: pos, factoryPos: -1, published: false }; + } else { + webpack.invalid = true; + } + } + } + pos = start; + return false; + } + if (source.startsWith('__webpack_exports__', pos) && + !isIdentifierChar(source.codePointAt(pos + 19), true)) { + const start = pos; + if (webpack && !current) { + pos += 19; + if (commentWhitespace() === 61/*=*/) { + pos++; + const ch = commentWhitespace(); + webpack.inline = ch === 123; + webpack.entry = undefined; + const entry = readWebpackCall(); + if (entry !== undefined) webpack.entry = entry; + } + } + pos = start; + tryParseExportsDotAssign(false, 19); + return true; + } + if (!source.startsWith('__webpack_require__', pos) || + isIdentifierChar(source.codePointAt(pos + 19), true)) return false; + const start = pos; + if (current ? openTokenDepth === current.depth : webpack?.inline) { + const id = readWebpackCall(); + if (id !== undefined) { + lastStarExportSpecifier = { webpack: id }; + tryBacktrackAddStarExportBinding(start - 1); + return true; + } + } + pos = start + 19; + if (commentWhitespace() === 46/*.*/) { + pos++; + if (commentWhitespace() === 100/*d*/) { + pos++; + if (commentWhitespace() === 40/*(*/) { + pos++; + if (readExportsOrModuleDotExports(commentWhitespace()) && commentWhitespace() === 44/*,*/) { + pos++; + if (commentWhitespace() === 123/*{*/) exportObjectPos = pos; + } + } + } + } + pos = start; + return false; +} + +function readWebpackId () { + const ch = commentWhitespace(); + const start = pos; + if (ch === 34/*"*/ || ch === 39/*'*/) { + stringLiteral(ch); + return decode(source.slice(start, ++pos)); + } + if (ch >= 48 && ch <= 57) { + while (source.charCodeAt(pos) >= 48 && source.charCodeAt(pos) <= 57) pos++; + return source.slice(start, pos); + } + if (identifier()) return source.slice(start, pos); +} + +function readWebpackCall () { + const start = pos; + if (source.startsWith('__webpack_require__', pos)) { + pos += 19; + if (commentWhitespace() === 40/*(*/) { + pos++; + const ch = commentWhitespace(); + const id = ch === 34 || ch === 39 || ch >= 48 && ch <= 57 ? readWebpackId() : undefined; + if (id !== undefined && commentWhitespace() === 41/*)*/) { + pos++; + const ch = commentWhitespace(); + if (pos > end || ch === 59 || ch === 44 || ch === 41 || ch === 125) return id; + } + } + } + pos = start; +} + +function readWebpackFactory () { + const start = pos; + pos++; + const id = readWebpackId(); + if (id !== undefined && commentWhitespace() === 58/*:*/) { + pos++; + let ch = commentWhitespace(); + if (ch === 40/*(*/) { + const opening = pos; + pos++; + ch = commentWhitespace(); + if (ch !== 40/*(*/ && !(ch === 102 && source.startsWith('function', pos))) { + pos = opening; + ch = 40; + } + } + const fn = ch === 102/*f*/ && source.startsWith('function', pos); + if (fn) { + pos += 8; + ch = commentWhitespace(); + } + if (ch === 40/*(*/) { + pos++; + let module, target; + ch = commentWhitespace(); + if (ch !== 41) module = readWebpackId(); + ch = commentWhitespace(); + if (ch === 44) { + pos++; + target = readWebpackId(); + ch = commentWhitespace(); + if (ch === 44) { + pos++; + readWebpackId(); + ch = commentWhitespace(); + } + } + if (ch === 41) { + pos++; + ch = commentWhitespace(); + if (!fn && ch === 61 && source.charCodeAt(pos + 1) === 62) { + pos += 2; + ch = commentWhitespace(); + } + if (ch === 123) { + webpack.factoryPos = pos; + const existing = webpack.modules.get(id); + if (existing) { + webpack.invalid = true; + existing.target = target; + existing.module = module; + webpack.pending = existing; + } else { + webpack.pending = { target, module, dependencies: [] }; + webpack.modules.set(id, webpack.pending); + } + } + } + } + } + if (id !== undefined && webpack.factoryPos <= start) webpack.invalid = true; + pos = start; +} + +function finishWebpack () { + const names = new Set(_exports); + const dependencies = new Set(reexports); + const queue = []; + const recognized = webpack.published && (webpack.entry !== undefined || webpack.inline) && !webpack.invalid; + if (recognized) { + if (webpack.entry !== undefined) queue.push({ id: webpack.entry, star: false }); + else for (const dependency of webpack.inlineDependencies) queue.push(dependency); + const visited = new Map(); + for (let i = 0; i < queue.length; i++) { + const { id, star } = queue[i]; + const module = webpack.modules.get(id); + if (!module) { webpack.invalid = true; break; } + const flag = star ? 1 : 2; + if ((visited.get(id) ?? 0) & flag) continue; + visited.set(id, (visited.get(id) ?? 0) | flag); + for (const name of module.exports) { + if (!star || name !== 'default' && name !== '__esModule') names.add(name); + } + for (const dependency of module.reexports) dependencies.add(dependency); + for (const dependency of module.dependencies) + queue.push({ id: dependency.id, star: star || dependency.star }); + } + } + if (!recognized || webpack.invalid) { + for (const module of webpack.modules.values()) { + for (const name of module.exports ?? []) names.add(name); + for (const dependency of module.reexports ?? []) dependencies.add(dependency); + } + } + _exports = names; + reexports = dependencies; +} + +/** @param {number} bPos */ function tryBacktrackAddStarExportBinding (bPos) { while (source.charCodeAt(bPos) === 32/* */ && bPos >= 0) bPos--; @@ -910,7 +1153,11 @@ function tryParseObjectDefineOrKeys (keys) { const starExportSpecifier = starExportMap[id]; if (starExportSpecifier) { - reexports.add(decode(starExportSpecifier)); + if (typeof starExportSpecifier === 'object') + (webpack.current?.dependencies ?? webpack.inlineDependencies) + .push({ id: starExportSpecifier.webpack, star: true }); + else + reexports.add(decode(starExportSpecifier)); pos = revertPos; return; } @@ -923,8 +1170,15 @@ function tryParseObjectDefineOrKeys (keys) { function readExportsOrModuleDotExports (ch) { const revertPos = pos; - if (ch === 109/*m*/ && source.startsWith('odule', pos + 1)) { - pos += 6; + const target = extended && (webpack?.current?.target ?? '__webpack_exports__'); + if (target && source.startsWith(target, pos) && !isIdentifierChar(source.codePointAt(pos + target.length), true)) { + pos += target.length; + return true; + } + const module = extended && webpack?.current?.module; + if (module && source.startsWith(module, pos) && !isIdentifierChar(source.codePointAt(pos + module.length), true) || + ch === 109/*m*/ && source.startsWith('odule', pos + 1)) { + pos += module && source.startsWith(module, pos) ? module.length : 6; ch = commentWhitespace(); if (ch !== 46/*.*/) { pos = revertPos; @@ -943,8 +1197,9 @@ function readExportsOrModuleDotExports (ch) { } } -function tryParseModuleExportsDotAssign () { - pos += 6; +/** @param {number} [length] */ +function tryParseModuleExportsDotAssign (length = 6) { + pos += length; const revertPos = pos - 1; let ch = commentWhitespace(); if (ch === 46/*.*/) { @@ -959,8 +1214,12 @@ function tryParseModuleExportsDotAssign () { } /** @param {boolean} assign */ -function tryParseExportsDotAssign (assign) { - pos += 7; +/** + * @param {boolean} assign + * @param {number} [length] + */ +function tryParseExportsDotAssign (assign, length = 7) { + pos += length; const revertPos = pos - 1; let ch = commentWhitespace(); switch (ch) { @@ -1004,6 +1263,24 @@ function tryParseExportsDotAssign (assign) { pos++; ch = commentWhitespace(); if (extended) { + if (ch === 95/*_*/) { + const start = pos; + const id = readWebpackCall(); + if (webpack && id !== undefined) { + if (webpack.current) webpack.current.dependencies.push({ id, star: false }); + else { webpack.entry = id; webpack.published = true; } + } + pos = start; + if (webpack && !webpack.current && source.startsWith('__webpack_exports__', pos) && + !isIdentifierChar(source.codePointAt(pos + 19), true)) { + pos += 19; + const next = commentWhitespace(); + if (pos > end || next === 59 || next === 44 || next === 41 || next === 125) + webpack.published = true; + else + webpack.invalid = true; + } + } if (ch === 40/*(*/) ch = readExportExpressionStart(); if (ch === 123/*{*/) exportObjectPos = pos; else if (ch === 114/*r*/) tryParseRequire(ExportAssign); diff --git a/lib/lexer.wasm b/lib/lexer.wasm index fd7050af5d28867e4b123c01bb484ff15fc7f724..7a20eda90d9e7ac99c0ec3eb4dde87d8893eb281 100755 GIT binary patch literal 40106 zcmds=eY~Ajb?2X#bIv{Yo^x*wNyy7hNS<>D5EMvM1O;ER<0pv``OulpbmpHKbv{j7 zb6YhRbZm>{2Bb3DDx$U6T8UFfl#xzot;1N6fUnh3Q?-g3Ybc0XUw9FbH_d#1Ywdkr z?!BSXTL0*UC(nBJv!Av1UVH7e*IIk+eX^Ha_#1haW%+sS7Z-c?W-re7=6he9aq%Qb zDcOq~d+m>e5`bHNN%rEJ(tyY8oq?8=mCrbPtjwBO)&Oyt)$^h#%key~<+XaPmg}!p ztLJ%H=DbROjfqxC>MQa_qh97&-dIxX&BMV*^48wWZez1c8o#mYH(oybn?=@o(Tjd< z*GpdUqW|`i3wM=yap6m{jrp$Z)MD3#*^`Q0yRs*jvw)}Z@2>3h;*}R>XB4m8m2E0! zcV(N)S^j-WG5gBwsm)isWcI>c&wIfyW=~s|!2k5pi+=5bS6uYcm%r@7?9B02o`t+s zA8Qu1dONR8eEprp+0V<%y~UQQ*fyD!TdM5zEbG;(T(|o6+LkI0`R;CB@!jrjRuv(i z=@pZCyJ)qlT2-IvmaVF6eYePqy=3iPz!zD_s{FB5R%J_CAkIOov~x0URld6?9R8Sx za%Xy3c%Gah%J{knML*+MKVwObT0ea}rdQcaDEIVAq0i1LsA-i~AiCJna{EHbWw0+S zQxi<&rM006LcPvIJ3?hxE!37{mEV^8t>d>gnLGW@miM7meQ0e$#Rs)LVbyF8;+3Hi zUfKy+A$YzuJG)%vw~pVMep78K(VkEA-TLz;GpL@mri_qbe-++ab)Mcjo5ygdIxgt^0!sCb#CslqeqWE_p*!6F8gTSUxg0{O~iU*6%Ok0 z;0uefy~QObW;C)lu^|g@tu_`}w^3{=Ed5wF%ry;ZFI?FZ@LGUdt(*tCAyb}*;{8GHD);uvdORGC_lM&Bfq1{7 z=lSj>zcAh&?^j=yd%}^Ja5&!IkzTwuCcHZ(>`%aZ6Y%~797w> zcEvfRBu2ThXzHJKZ>%#xw`bK@r`3iBNbBgI<$9rA*@1@jL;hqFehr1HEHvBMw z>%w;vSXnI$LofUwrEVDDJ=sBeYK-C=^vK5Dy{2SFQKu7m@JX zJ*wQ3!qKrkvjIx))?gXyOw!?Wn1)7YDmJd_8H%_OG$N#I~G|)WozH6@K6G$jmnm~c0jCk;r^7N3J)ak=Rp=) zP(mDQDLgbvB|Q#PDKI#p-=o=f4a|AtzM2T_-Tc$s-RTI^;chhn*J?x_F;LWRsQ??L zSy%=e!>)Y-8pk?c8MwlTV&JL)$4-a)ppT_GUm3VAd^@F9gU@<{&z!F;uk%&;v*9a^ z45YKh;o9&(7slLW!FrL>1QLA&F?UhhdRAInrfvBIMMOX+a2FTy^vIcniYC zp|NdpoQE2S?&_+sHdFw*=u|C64InipM&NTu+DAhr8^lWsj~59G7Jh%1uzVb5-lK-K`erLEz}O7+xzQstA12 z|3aEhc$Vrj_&}XE_y{^7rK+ru`r6P;P@JAMp zr~`w>FhV9M+~^(Km(!LU%IKe3RH#G{l+z&+4p5BdM>*iz`T0MV>C+nXclvKg(w!mh zAC+`SIh(!~CU0CUb7vV;=bO_OdufwN7SNy4%+Z&EOdvK6AMC2{p7?)0W_wZ(U1J!N zkQqZ3RSfAoqNlgMn$1it8}Ci@ij}=yqgU_sdO24u(-c;O-pth0a-Mt3o0Gj>tJga| zzIc@H$|b#yaqzRYPxi?CSwvieyQVCLU}yO@Zl=wk%S$SrXN^(!)8AN zn>PCyIAN#^Y}@QtRU;Zvx79fwT+~#WTCeD=fZ9Bb`|OG;&=EKm=tvmel)=rK)m4?v zfgaN=#>Y?PVa$Vk*mGqd!DO-%+iO*=4f!sugQ+vsE9 zl+nk)j?riRaN`SWe4)lyvGGe?DYdB;ar^lU#YPXyBXt2+1gUsN^HolMC(-oa(`iI> zOOwMMk#K#YTAyMYMku!7iBn7(va`{86^`z$P76h6`7{LUGv;yY`8%%OTEBjL{raVm zF~!iW#`bl60Qd3V5Ng6iJ4Q!g4YT$&a zsRp*KqfQv*$aSsIK-oSaVv}~a*0D*4HqiOoK<94*oxj(PVskW8D8aH5vmpJRv~qH7 zXJvbBYcw;@RJ&#sT;pY`Ck>^1cbOZFX{1dv!!6BVgK*{bK^% zO0d)kGXf0s2279EX^kw@y~W-rglsZ08MIv!kZVFr4m+@KTb)(yX|nqW?DNvP5nh74 zPjvJxhV+CHq$m6{kV2nwGOA3-h&7?ZPGgl~uiPMNjoNN5sJ3cR_{>^h~msQ6g+AlPjPz&9Eg8N|+lENIe+X^hm?n zqZH7Fn!f6+R!iyuzX9Lum|P}ziK&)&MkJQuo`CN_1QB)$HB4tov98?J5PcKvWk&kk z`3)0~vCquM6cA@VIccKiAjA~lYJnaSuH>DFIQt~;6{1e*x^a(+=5m=lWSTN=EAye? zahWsm??jZZ0qL&JY7e+&Sty)a)HqeyGH*u`iSSi+!R+Lcevd8*|a?!!^P=Goi85UQ3|k`Y2jO3q`l+dt#b4FLVoQ zx_M&oKsTkuSvI4Kd;>UQa&qO=Av_5+%qY<__su3A&D3m!A7|TP$_6S~tVns@>_AUy zgeS|>Gf`v{vN4JoA=?~tPVAg)V*T{fpS*o?tlb`}w~QoCt22o`EK_ubPg`a} z9W%2myCPf?E>;N~+Q(>ZuiHMdWW^S-IvXmalH zy0Mf#hZup*a!#ry!*rJ3)|u>zerIeKegr}bTV0m3gf*QCXE7ug;65eTJcH;-zGU_4>D8+lUG~Dvlxd|*tzKQO9x+pztC__37_)jc zH<{JROj*@l8H?wIa4cIhy=J;tGCf_JE~lrbbFS5~2-)tFJWo%KHK)lnjqzh;GsjJp z6Vv@dreh&B?y}uiyT!VCV6mdC(i>WYSWN|NiZ`oil|Z-M4RqVaK(}oSbkkrJ!@x{n zn{E<1S@5e=E%j&Lf*+KeS;@W9Y}xYIhWsjZunc)IXR;CUQ42lfC}MHWW#o^T=c@)q zu857DTtUqS*16HZt#bq2Iyca*a|7KvH_)wf1Km1b38(d)0I^|1Mmoy9wPC5XVH~Ld zahIa+%rn!Crf#Mi=w^B(z_fil=^taLBgt*b$53DS#OP8n7HoV|3+IcKUcVt@Jn@3; zTqB$Ff`QHp20AYo=)ACEF*Q|hu!>-|PVQ^5Me#4Tb$o~( z%ih5xv6-jT6o7U!>YgV#SBRAtmuO;GZjiP9n z@l*DVpK*8&ouV8VKjT?89*v(;mdpics!Z21e#*x&eq@&f89$m)nHXGJj2J(r&DQdI zk;&H|RnmcSG+9b@OX(Ot1*=^BXN(^$8ED>-S`TSl8^-)8hDC8SfAsDA8pQ z;@;2-SR3P@R-_d$jeqHS*a@g#X$=r3m@S6=Q!8DIuW~`<{tB1lVNs2BGbi-F(KRj% zMGd`w{Y9?9+Wuk-TpDVRyR4P_vKHJne_3miQSkF$*22OuFTxPk5xH_%<;GX57RdT=_9apV9xgoeStc}q^ZlALAI zM@L*}EkYqWyEouTY+YC|eOwZqm0HM;Yxy;;^4ABe{Pq4Szurix(<*=MT4od+q6Fz_ z4Ik~@;>Hwlu#q`PtNid;m96o3F-(ZFijDtzWo}Moo;I({&8@w}K88G+)#Ermh}F>` zW*B3?&rFS#*y)6Znx&3~jthVn57`2f`6-wYX{#x+C+*yN^W5CrewuUr6?RVzd9l!Bk2X~0iXk%R5UM1q9}g9( z4O(z7?_`MZtV_KS2RX5cJWcsSw+&r6Ir;-xubV#JY}*I}Z#&_hCRnk*Xq_`y2y zgK4GNN(4Ww16a&x8qt59DdhWCqq_5K&7-*tqN4wyvs-7XD_l0dDey!jW_*k>POu9E zvuH(@!!_fkk`sbn8BW;&`}B6;`HD`8h@>~Bek`d@mDA(Qtstuk`Ji3T=ry#0nsrSL zz*%JVZ7fjF_-fP@Z3*PV6`dn{;@qp*a@ZA(BA}K<^~@|LLPt?#Ar|ZGU!&LZ1rK5Hx8dKs7V^{VHqBzL zZ_*2dJQ9oH(V#4sY@r*eLQaqfu|+N%Xu?^=mRMP8ZP5XcTTxk>85EMG`UoVz($;;l zmi2Ky>keKIBQ#%c&0+@)bj#Slro|2#=$5g8?I6?4z$sfcG_Yflg9a`&Vj8&I{G0}^ zutjhKSJ@)CfzviC8MxMb76z`fWo!d0Tjn&dH^A$`UIqP?Lj-faY0)H{3zm^hVVR2tL;0jIu zwnn|9Aavo@Igugf;=?p8lR6t`rs{dUUMJEu^8Yzn5NR|dvvV~DrQDW<{L>^zIwvON zP`1lT5JYxoD(QzUMG}J8R`|9U$Ru-;*N{InswQVOpCXdT=lCB-3L%}fLrq#5ozO*U9BB(9gYna{S zhmrcydP|` zJtHg6k1Ad3N}6sXyD`P-N_J?pc9QEe0P9)l&=>c%h3+3GXF|;i{Nkj- z&zGJbprc|xjy|?}&l;6;aLG)V(AVz)Xj$GjPwCqOQ!`-=6^Qu2WZ6ElbS5mdY##zD z_2r$%&4jME;`4w@l=kJ0UaEli0o9fCzdR`-{3D*mmHajUuj7xGsNC;Rk8x|b^~m2Y zRnwRGKAO8JlJ!?(Z4u(=96M1&3BL1p)E0p$scc_=c-hR<#*(c__J>|r>f)dHH-JdC z@vv{1D$bAq(DsYjgZ? zZLC@&wMn(&o@#Uapf+~{TNgY6&=Bg>$Q8qA7c_FHxqY-NhR^~-Q!&k@E3LgNt-U;d zWTjfSy5G8MzJFPV){V63BdcbnPEPa{X$2?hCP2@zMRRNi!XPmE-VLmxQ#)3VwBx5h zwr}-NJAh+5*yF?+=`@CgF~MI>Yq!v;k%gu$n^OJB1H6qBRYtpd&^coSQ-6zN$n__2^BeBR0C+M5|``_HKR^Ob^|K{P>&HbeG&knI6 z%09Dde&7UEbKI->sT0g8*yr?{%c_iw?yq7c_pcKQ3OvLIsS1~{$17aDj`s87FV@Xq z`m~bl`2BWO8_15|TOVb|ec)NncrC;4tXKW7I>T681Nhq~$C3Iq0B8F*b>lF<6aX%d zWBR^ILzz#$2{2}({~v%%Nc_D4ChESJvK<7V&V29*EA)T!OeG?=w^O$b!uDrg7og7p z6-v6_0K@i%&9Q_dn-aFKZBi3Hz;o5dcIcTg+spQZgU?7g-wm`}2=6|7CQOkT@BpBZ z^j!dxR=qhOYsGiYa^VE^$$5w;%lQF-<2)yjNxK72?<2et(6SvpGl|$EKqsi&zXN~- zvB`hHlYM>YX({LXo}#vV;;Ehu@JiZi*nWLa*iSO#C-vaDGt7DbKeBcvgx`CnF{7vP z_6tDAE8C;wuT!qIP0Y;^q3P+@YhAZN&6#gm3ilFys%Eb*C93U4Tp%-$iv$H zDB3qS_mCIWQB;vs?72k-j=kobl`1YUf@vHsUR;gCwxcvSKs<+A)A1)?1jgLbsl z2xYqSOYxQT2~EZl5X*>g6Ns=e)mZ+BV6sEqqR7p$Iw9Twggh%q7Rq3uA71sJ*p4azAuY~q%N$1T= z=69r~>HgYVr(sisYWu9(lUt|~kS)v+{^YOiLB_euOyJNBWjZ(In(;#WtOm!R4N2u81nOkaf=(R$Tc>T!VcY3sqKboATh3v^F&>hoeS2D|$?AiucH?WCV^jGv5yYS&YEq9(TH=MCh9oK@!Hl}S4!<{g6bGvFrr<*b8 zY6@;bPf{sOZuD<0eEcu(zv9*15_YU%_H|BS-0j`2wKpPgFAQ(FY_9q12sv>Cv2}r+ z%~Sy&DM1x8dlY@*)}-+Tid?+HDA+68D-bpGX0hYwX--5ri6TZ+)kRf8Kk% z3n2z;>M5F`*y4^#ZD^8F9rryK+TNUp+~iRq2LzNM@-_?Kll%TiM+ov3izF%#_lo#K$vjT~); z(ZnoKrIkON8X}oCy&2dr;bCCY_L>x-;o;R2?zDA(3@k!-*qCIX%YCIi0rN+?y2>^~ z`-^|MPY8TdFoBvW&lR5Qtjgh3@cSxQ*7`w_b8G`l8q0*qi-DM|4aB6k)F|8>h)IuS zvedZTX}Bqt#+vJEQIUuQa7Be9L59X-n#Q$~AE($H>1j#>qC$hvcp&SIN&6_f4SA8q z;!Mllc6nh7rWqz_`JT{M`ZH~*nbs2iIjbrSt*XeK7v(gphhUzW;%FE2mAfiQ47B0O zx420Ij3kEYc+xZ{Ti7ZqOd)UGQuqjuG{2FEMS7%2DJ-aweSFk+CD!%b~MfA`;5#?skMQJH0cwhj<`_!OZk4LgMBs?K9?|b;yB>5rEwepE|2O0;EF-P z9(8CKrU##`4PQ#geCNGos7z28*c+Vwu)$V=EcN7|wIKsH4qU!ZBJXye(XY>k`A6L2 ztl4GAL6hkD?h!o>lC*LRWRX;|&=!VYxsgSH71FdkU-By2SpAh`)^GCB?)3v&7IxLV zRms-9Pb->Q*-sJ=t+_20prRA(**La`9P7#0avM1jxeDbelAtP`L3(wGeKwVgi4?=v zkP*xD+GVVFv(o7?iMR+;Oo!&;zl;sitTQlJWLikPs49G0@KLQAJ1t|rL{ov3jnI+9 zyN`{0OAJ@gsXmkEu{s-aq$^@54_6%vZ(|5A;$kX(+$;E;B9@#-1(2bvfN2D=5W(!m zvswblktC(+XA{V=l+4H7TFs24&4#js{OAr!S!^kd9B4O%Yd0=Jdepryij7t|Rjr#o zq`9!av{V-jejZCpM5gr@xV4eIYS4#i`QE6dVN<)V3GYRC=speOxA^C830vk4t zEwvdWKn#o%xbn6+QYWT*7_(*QM2&z9OXFHBU|OsN^piO(@2a>KOX_rZ*pY>4m|wk{ zg|!+E-nCjm^$NS3>`bX^3trOLg)a$=q06MHv)mEq&$#H`D;MiJi%qbOV>gr3!hhwU zgsSLU^z@si32(|GaS6%t9&J9;);H$&V!xAWQU8Lj4bj`2Bsx<@=nDy zdBBdVZR+NwCb@kH8;#wo%G%|8s#8BH<+c=Tr_gwXoxxNbqgf2rKhCE=0ipi82;yv| zlB29n&zP?l9Qp2b767Pm|-j;MT!JO)Q*u{i@qhxylnDp$xvT}0kOar zNnM;RZDLk#@DD`GFhxWhC$^=!>S5r7RXUQ)rb0$J)yp+~8}+^#dvwu&rp}2>6t?#7 z#V0|*+=nb^m@G)1z$^;y43b1wl}P}Eri8cY(U=6s)T4hokJX6Q*WV~RPrXwgDLTp7 zS+Pm;IbVzL#?a?3#yr+>KjyQAeT0lS?FoJ&cMB@Q5T9$sTbc%kcQF$mqh}?%tq&Wv z><$ReCo$d}i1CGWG_6+JE$y(*_7b`}Dr$uBdd<|KvJ@90vOzRbKaB>fRL!~4Iq%~s zwQ2gKW$^792DZbuA}wiy*=wSHY&I^9nJz(8X`s+?rnOU7O`9X7YY4~EC6^8KA#7mN zral8FY@#*LcLo{gLs%#&uk*MhjKWRI(J%=$JC2aBU)f`fT}hJE228}F-zc*iok15B z5pgTOA{-PA$%-VAka@-K;2B0={z_cYGy|6yFq*XMY;MrmbkX&=BqH#l1>7^&f}Y*8 z5Z}OtNt7zm2gi{n_>phqgNPqV78}^Gh8XCbZs3H?L3@V&{l0O7G*($-#z3DHVwE*~Vu!?8 zAs`92%qs;ci7>Z=-1rw`!RH{36>vT_cfOpkeLfzkr?0AL1f5)CveCe4%qxLZwL{68 z-Hb)m4tCk6`QAXE-3;`080fQ`K88Jhs&>>CZHMCUVEmzW#0tTe*ujRMCcNwD(X4h%?$@9L6&jVIZ8JVLO;eda7AZX?r=@IXzConz+WpH-FV6cr1 zY{Z{&-F{xY?gsD>*Fh9>Sl1*zj3E{kj45{AI8{SDek7(f&;_G`E*K4T!DyfhMgv_i z#=?dKqf+>v5s?HV!|-HRJNm<90k`NF)%m?0)w~*;nK2d|r;jr2wuGZK%Cy_kz+^p; z+Kf&jz!U)p*tYmW%}AY-p&*;sYMLEoXrVwXGPpJIj|^_v32|^sg2utE+Qq@GwYlNJ zttz@zY^hORDAlcEOLeQ*K$oiqx>anTTg3*tRUC!JkU6}t_5*3zN8Vj zT^7Ea-wBRzm=zZN%)D+3v6U56bU^J_#+qJtxP(TA(%4i4iKcW?mqMO1Wf)Fkz4$-%LZhvnc{-WgJXV0 zAV0p1WAc!E1FpPq!4Hwp9@SV${_}!4kznF<{y6Z8_d&nd6eqm9Fu49<@ z$NWT+eo;i{V);pTbONTC+}2gBbg9#$@7%O}sPd>P3*73-oz4;hyO#7%lY{y(_+JsnL86R;Vo7}*RZ z|D=kyqWg=MeZM-3bY`WqE6pOeF~*M?g{% zT(S7?Brp#bghTMp+T9i#V+%z1VWlYzcTH5Ih)#{(vXQ~XP>e1MyhDV572vh3eT@qb zSTS(Yx*VY05@+%JDNRZJbH-f`SO%8^21*Wq+}a*DAbB0#DJU^B?B9fX2|spA!f)VD z>m3OM0wNgYpIFGqFsrTftP=f*Iyq??&Bk1cS}U1HK(cg{udRHKlZnltZwR7J3F~`LzS6pk>35vGAQ44OwLxGK5 zA04&_n;?%nkH;Ax#YNZmCN9P&XTO;7OAxm?7<1Lc#ocC%r;tE`d>6i7HA5yIF(+5m87CC6qBH3VU8#@4mARfY*oqQU|!^vVtK+P(1Izcm|6S<*N;DK9`G zc_}?c$RQp5NKeKHxCq%KPc=XjA>PgU3lk+x(s-z9T2%(S7A9=lbjdOHtST?MLTST? zuhb4Z!P5eKEMX{=c0@Eh-ok|ZOg8jYptTR_YKuk;Bb;oK6Glt<>s9%|e^eRzHXwgC zHa3=zDQ>`G1#4O&pz$?RVmX*V^No}>1Es9V9wS1p|69^s30SiQWlL??!V(J%Xu-;5X4@=Z=v1Aldc7>ly5@IYqx@=jBfOvkYaFyg zTSn(tHI~=^);k@e*gG8tWS6eyHY9}&dfR_576>_!*rG>OH;$HiyHHycx%3lOFoDz# z{y9hc3aYwIS&AgjNMkZqmoh*`A{Q*8=3xT8EF_P7O>Kv;iL0lXPDV0&SSVOX{HRPh zbM#cR11@Dt>&cJG?Ei{P3M{naj}$JJlgEz4`er z7)%2Rlhwp9K`5pxif4($aOt#Lb5_T%9c@w7BtQt1vnF-MwYQ;cUCsVDosEI6Hu|3n z1w0r;_>IMl*AVX;IuNR?a~PAH8m96KWorJj*~LnQn1Y50$@WFF;Mh|_qcjYhkQ!Th zazbhh>d6UdIaM**OnkduMC$cRXbiea8zjA}+%!u#)g4Y95k&*I9N^RoJHWIvE~+r7 z=_RW^DtT;0d!&ra%SavNC!8h*FjOWE)QN)@>d9LenPZq$2DMSPDRGq5rf7XyOc^=R z1a>XSZ-ry402wr5dcJ{YealI1TTb$jww&Z_E|D~{pIxX*b(P4;bVMzzpSZaikSNa~ z#q=NSqMw^9kgUuWJvxDSb#K0HL^0v0!?Y9=E>zG=M65~EBB)ETYa*g#nc~B3TlB%W z+gbafX?YHI2?33O#kWFB<-{E0EqBF*#f{KL3n@V;C*hk4l%vL`tddeZ{stfA7~bGh zV=uLD@bPX3mF<$IPMr;6jMlB=Q9g_g5r6U_dKcqjd6pOB{#Vw<$Lr&b@$s>7_j4xV zep}-wo0!Ns$XR{Z7dCJ(%CERoF(u=?be@qI7!McwIA9{v$$Rq$SJOY#O1V~$o%O_) zdU7;235MTJ!)d}!w=?pmBYp|3&2o;Bj$cqQh9_b-x3#+Q#EFK#$Cj#CTOsb(Z~J6R zr#!j%)bLUR&!ZaFFb(NuYRdLW)qyM%BQzNEtny=`YWwiC8C*}^FpVNT4H`BUek(O> z+ze3@#g<54@hMVYr+i2$i!z^K z6-8#hRnwDm zwP6XbPQ}{}midE+stJucOmfC~OcTDI9BF#zNW~JxU?^!vG&$`Z&4TBQt5kF`L>A5U zc0_{;6CV}fHK|?^#gOU&1Lj^Y@q>EW>tz8X{d%b*@8M6|7A9muPS6eu zU9UKt!hZyy=JCIDk8E%?Gkzeywq?d87&Gg2sYy@@3%?MBp6S0%c}t=B)hY*s7* zSp73P(o7_37TfME;J8LwP}i9P+8TgmEk1!LHCt#94XtHn*c4m**BN?+PGF@;gG~Wa zMn^2AT{@FcYdd^cr%RI4 z)Sb8ibhI7{J2V;%)nhcOjC#;W%r=ulHiicpyjTb1Wwf#z@u zKjURl-i21^GWaZ)(=LNT722uz#y;i)Wppmo&ZC;)F6frQN%P9?C(Zk~96o8jD-PRM z!J6mG6woQHQhGQrNy<~sS^ZKdb%?(mwqhGBsYldeYGLn%P`cVNt20gT8C3q$fZw9s z-aP{@F?A<4L5+oDtnP8V=;UIZ#gCe8Jdbm>VsM)~Acl_`t)l$lr7Q)O++7p2)x$z9 zMSj?qnVY5a?F>18=6$L15GjO4Ntl?(*HqWXZo$;Kqmw|&y&_VQX@u#2V2DaL9`hle zR%7e(i*bxZ2i+z7kY)NAK)tC~W9AfZ$SHoLI<+wG5w_h~{4OZscX#+!Obid0=*hVo zM^Rp9cX+76W$zr;x;59X6CJ6Oc9@AzkP3b%mXBW<=YxZ>;NJ|@Z5=%wS4@I7+JeD$ z(spwVavP}duxP8^I|hEe9KmhEp6GCRvoq-&JAzwUu!=W^V<#t)w{>(4+d(OaQxobs z&j8?|d?tL__p@KeQN}d;{J<#HK{)%a)1?q13xkVc!WT?JWTcRU$Z}+oA5^R*ijRI0`PfG{UP%` z9P(oaW*h?W!*+!D%M;s$X<{avYT0f8I$7o1*Pg+q^OFho13x()Fu;Kyc=y|w5OExE zu!J4>^DIA2B3=fC;0NuvN zU#3u-q31ZQXN9Wrz!J5y(QoHRGdqnz<@5I49mIA4GilDs!XI|x{`uE(fX`X_{DT}6 z)HL+_f$T_%gP>mRZM$iy5r70gk?4>;-)^V>q{Aid{$^{ z2|BvE%Hz2yo*Wx_9eKu7k;m-I<^6W-d+ZKx3Ge>Q-VHkczBkY56bvK8Tb4&c9JWk@ zBY^&L?w}t`0fOu7c%Y~6+K1Rzo0l=Li_Whu>QynfR zEggeoB)yKqgHDORrj8p_o>2aU!d&zIoajfuVCQH&e7uM=`03$&M{aJYv&<3rmK8>< zB6i$;oF#PTLX@#MD=(H!nS%6>AnWJrR;Z+9ePSMc@I=TZgV14rqscyb(aOw@$sBlY;WlI$xq(`%TFB7ps-j>^JF}A~L{D)2hW) zoEYST^zSX>fRTQ0?uZzPJbz<3|C|$n=un>j5NMTn?{Wan(u##Y0xCN!3coQL7&^@Q=lp!T;oC)9Mb0t1>O5~dT0AJMHVDB`IL{Zv$Kp2JY^<)B2Hp+z^5wH$2m92PmH?u=_>qk zo;QIMPT*-jr9NHRzWvNZx0`@WP+SSXJShBgfOe+bSDs;vNz!dVmTk_HBEk=GpwnqW zcp1PtDpw1CdbTzkll1*(@}p2#lH3&wdw0*mtvt}_>Evod(EEk<>$nLKlofb0L-}54*SvwXG#E#HQ@+=&ML~o zq2~(U7kTC!I2CYf%JT^Tr{jGG?MS8vJt4OHs3%@|3u}IU?9rvSTDI45Gfcnl&#%9g z96FfFNOCn#4DaK1hMV`Z6*Unh42lMT4hW=s zsreCoYAi4p6TxCa<2xKLMWOLPw7IX4?_~VW@*2`J=`^&M&PSS2uJv=MuIFY9@#$hF z{GN5jp}zt=BX-e2B^#&Ay#sCU-anD?K5x3m%4?usf zvBbK_9hUiWB}M*w*B!uLv2pT80vQT?bc;^GVq^l|C{V)FDRqNC^5R{Be0I28AR@$Q z`KuDt2QmNYRLb1%kg(q4EuB+JoYM}7M9)w;m+FA3r&{R;ITOo8)9(sWR!99hJ(@7M zOppt$AAJ_k4wHO?AdNuK{m;Jv{-(9y4+MHe_umG1$Wo8~HDF?G+%SH`*FmMKmuulV zQoPvvsTysXgq6-5%wzC@j^DB)(OxfTtyTVjKvT*;rsJ-nK5(#q^wpdoL^E}KLPHGu-fziQ($b zJy=?waFW&oK>z6Sb?$!P?;F2;=Klab#Zey+Y|WUv9qjM^lIOnni#+_m2zXeap93cm zbU=?!mK~Xe|1K8TuLRRq-z#;{kUaNOP4%GK@l}U`jtD}5_NKgr8iT3DutfDA z49jcO5<8siIzdPUEA~duPCtG9tl{jRJ4z-~Q(mxv=75y$n>|>1Z&#P$+%AP+yv@-qUZH zR=7m8ZqCm{o4>Jc{xj2mC3SG^@Kt}I9`7OXL`v|5dRwa*aXZ8jtI?vo&riFSP zmVuz7zRbLSn$r0-s_E{7ei|N5S=P2>WS=NF279=)&12CVmtzvY0~li+TCHzziU{N& z$m+AWANo|1@$CWnw(9Ny`hY3=H7&R^Edj;=eXl^;21D$H&^g`Rc|-69qIGD8GvE3z zOi?QlP3r~*Hp~%jp#PkdfnJV*ZHojjaLRV18|dd)Xr&rT*w1OvFed^ z&2l(flPsXY!G@DYHEa$}?(+Cj%j;3w271)CfgZJOphs;R=uz7SdepXo9<^~+;jT+31dUGcQ+MPZrCbP#5c5SQz`y!>YKEYVv*>%&QuD4)tDeW;G$k; zzl$J;BtMA&M?vUCBIN>W=UVN4A4i7cOM-&E8sp8vF1rkK#eCKa#+?3xsfiHl)!^^2 z*A`4|ozX(eLA?nj|5hoUn5@mb2&Pgxu%_qWo21?m)R4A{Blz%4Sh5gYHxsVoRiGB_yzx#7)Op z+u=WuS4UiH$qB8?evN}5cCB2O7ggnM(j<0lQH9_x52&)1rAXN#tiOod(ueX_%_2-& z7le;bRhgz5er23S5NjCjTCbnDl}uu>gd{@g2q0@+k?VMIU)G{&ano*5T9?1_k;2NVZT>- ztErQFsAB7Q+*ktm-zz35J(L<(bW-YpMf1kgd3is)Xx^AQoOi-n_U@{4Rtu2E9Ec$G z|FFOgWTg}7HyA1FG)58#)NZt9isBM}j^b0*sU^Rish^suB~S7eq9~ImS#Macu>frj zkAWMm?I})=IC(|KR%2@{Y){m$L2w;PqPMJ5DgqF$CH}hAqZ6r(1tAvt4jx?t{cJvi zH?keA)>~KAP*9t#^}oF9S6=$EUB58<@>lGdz34Zyk;j+rx{woBo2@smY-L$iaIKE_ zrSYD}`xN(*Ym)0kuHWL?&UGeNn@nrD*SI#vYdv>;a{^a`>m)9fp=aR{jyl&Em&(<* zPv-h#u4P>5eY&4Q+BnzKxRmEC%ADYSJeTka&)d0#Q~32Kyuzuw+MzP^r}rzk^qua) zvyN*k*KoV_KH(Q#c5t1|rLu%CaOtnZC7dcJU80A|PIS?I1DC#4o;6%bdpg%}IVxXt zBMd%!IoI>Kp2780E~Tft=<;8v#HrjuxUf^((oa%T;lu`U4s+J*&Q{yp_P>8~rI=e=p)n z-<=FB8tN~-*VxgY`u7wr(Mx}c{%zpXxzyMCJCRGg)1*-46(04CcuardCE?t|HJ`3( zd%~+WYFrK1Uo=$z>+kvC7tW8^Q`>H2#fmuL{UaxT?Zf5Q1A2reG` zJIYsIsmzCZluO2^0xPd@t32g<1L<{u-N3>xyy7W+r%QQ$6`cQpyT08X?&-xRb^8F$ODv!pU-lx0h`!9J`eV$IE zB`?$lm6!N)mNG@t<6)AIao0F`9+&8(zrUb@sovrx)kAbi_18Q7=~;BuJ>>`N=TjZ@ zCtlQF`hNH>Sl6-eAM3l6KL5K^k3`?}obKtnRBw&xlvnjg{iO755bSI3@o{-33F4`* zaS7Kx2AjsyW|+0&#R+_TCk=f8gW<=j6n}U6Eqfbx;XRpjmHVH`H{5QOFPx`xDLv7P zqKz)kAyM!3tuFPSiugk=hG=$xOW&QvC0SQ-DWBS)?<6CYPw$nUc=aS;$zFY{bk#$6 zC6n}*-aRG4$|HQrFP{3vcu)QOuJ|lGsT}2(OcP#xm)ff5giCePpKzvhX!1122Z$Lj6u9`3)K z?aF>7dkK2PuV)uwcdc(d`$Z@PyRr+j^HBzW*hla}JO<2tli;XeCel3es^UsVrpUJe| zJ5_SQj%_J9fg*g=TEy1+R*7Rr$IRFX);f$;35qY(Qd2=i#TtuZtq1`vf`FOtZ>_!0 zIrrv7ZEfd|IlA+#y`R0;UTf{O*IsMy{alilT>h(Rk|gPl#*4H4`;!+Z>HZg|`}wD~ z-+n-p^KVX8@2*OCpS*a&U#qAhFv-vCRg+qh^`_;X_`tpTe&ywbgUi2$_Uvk-t{=-Wy-AX0PNqqjHuE&EH17OV+IwD_@6UD=*{*hy?|ry8F>=X+_RzlhR_78 zf4iYPuIxh#S$%(YRe8&XBDo+-${UKJTBiFhOp@~T2L77oimbAu{LqFXqY}iCIkKV1 zEmI+L`-Y;TO!>y5gt%uTje|!>g>W9}i`8ttS5@wIa#cy-in*Q;l)qP$i5lD(o^Ru+ z{;%h$_pc4l!~N6?-XDQm1gPqVLP@p*p(ooxcINIj^4gweWf!PSx`@BbX4|5qg3vW8 zy(&Z%`K4BvX(Xy@+L)iJq)Cz{O-Z%hO>>wP(LI=$GbYr}ssC5Lt;iVt#s6b0^F0i` zy6Dx)Nk+U}bxDZ|6xkK!P5pjl-@5ji#cjst5SVaOc2zRFyC)8SuyfoOT=_memlyiRz48HdXNtyX_ViLrLCa=r-owf423;YelFHs zF>JQ7d?=Q#F8@A)Ylj`5KE%6z*sLhO80+?ibBGxszBO7@5J)+?mMklUXqw z-IYUWt#)YzX4gg$tnF6IcDK^RCSz6iNg!LtC}_}2Q&b59jjot|rN7Ks2$)^xvrcVo zYirYHzs$n)R({jf*4C|!ty{4J_ChA5xnk?&)~zeIZq2u%;#O?dqP?{ft;N=0Epnc+ z0b7`rvIm;^n+4Wv^bM?=1{&BfRWh(;1F{(&&A^OWgDp{>Q<6xhE=5@`R+O0DK8GpS zG_e;H_EK@JkzHN@R%~PqtQl1X)=ex1Hf%%;Y?a?LL}_QTwK*^%M4Oxu88!pmuo>uv z%|JJ7n<68N4V$Bzq5~8GEwv6R?M-1|g1PI|yO=y?g!9*{*fLf=YJ)FRcj0_l+p_@B z)dEr-EFe$Ww{Afzo>l^07&2F%{NncJ#inZlvAT>$>>*2Kw1=CddM6DF zr8dU01{+;8n)e3QO@szEhF}ZZ&61avt1d3nA`J`Pu7&oQt+N8MzX8M9j_mIu=mTq| z4YM)^x}49Bl=bSPDzkBs${>)|TsL#g>5kDHRs%CCyQqmpThM|LWSTuBp#;C&Y!u!hH(0tkGoR+=Nv%ZE7A>%Fd-U z8(^a4GQ(4Lm`Cl4*D>+wqJ<+^EpG&;8Ir{~C&5`UoDAVqXxOXJ6en`BTy}9UVO2!F zP*=qbNWcvJXS#s!XJjILT47guV}#Rb>}vOr-MQmmchiD}62EHmY-N9^iBOs>#k<>Q znm^`J1b^$yCUrLL=ARxKX&q^rB9m-~3lZ%=R@DL@=~7e{+RVc-liAHBg-UskEtwT} z&$2Ad&>OjOw9+JR?>0BCHivUbL9J}eA_)d%vBFQ9!n5dCi+WJAQ|fgM}c44iT82t%?W49SJ#rjqI?V!*{sZD`F!)f_P`lM~L=mzK+eGtj*l8HDsfsNsObIA6Z%v3SaoV?<@iCuGS}x8xJSMof&_h}42|ch}64r;Iww0sfm? z^5oQ(JQ-ujUAe!CXilCAjFhYQv&U-UzGM zvJ`0icr2}(b+l4<+zf1$e;X0ONc8EQ${r5rJe);dir|n!)ycgNbiU$>?lVDQsP*Hs zJM^v)JM=yv+Gls1ADy30jLE1NyZOn29EkChbJ}PL1JSF^EiGM{uC)Al9PNn9VkH&$+>5WM6|HVn z)4gibeQ_&o0EA{T+y>T+Ap>0n3~boSU|`GE3j?QYsWY5(7Y3oI2I2yN1DQUdE*4Qe zD4}%_-9SXCM1*t#_9BWl@+X`6GqBF~O=Nul`idM_R~Q*szaxUax&+n-!s`rWGc%Np z`+ZS1D_t>8DH|rVRb47GEg_3#HW;Xi3*QZ_rc)r?nyA#5kVrvTLP|`g#Yc;J!l1Il zA>GdTmssP7IUX)xDR{1u;V5a2FQM1`ImT?V)2^^S$wX~bqecBtF7oWfU;xF@Mps_> z3%TLKsWjsfflR1iHY5l8uOrecIg(D-Xi2D;}x#YEYaO9}iFjUGS zyo1m%4;71@jRZC>Z50cK@{Ltl;+M+V*2SMVOSEn-86aO!|3TAdq?=k%ADN63!@%>O zp2Yu`bHR>mf{G5z$yu(9i0Z7MBx5Y8y&CASj6rB7bvDo~sex`u4RlLtpj%P{-IB)c zMl7inEtx}jXd8nEGa%q(9a1=PvI+$6vC8=XamXUh6#^yBy0&i&j6n+Kq;9^qmHK|0 zfh~C)1TmWT2vfUFT4WW2^J1(8kOvW`n#26_-~paP z8RgPe*;mTgkm@wdf@)@s8~&5J4{9^$UXy=ZL%9v(%103TG>-_EO!J@6)@2df#`XGC z#IW8xo4qtnOmR&#)G-oqm5?$Lm`Nq1j0UbmCs6D`3!XBGtiaKKvzBkUHJ@j zu%kEb^~hNIcm%WqhCnagKeA<;*h#dk65eRi}}xOro_#fFV7SJ*W6g{i?JB^pd6_hU^3 z-0L&Yy*>lo=12JjuWv-p-DZXN#S)t}j+&9ShhwER1GqQ9xvsTxP}&W6=5?Uog@#@2BDPis zpQS_gQp;=~FIvmPDMF7MJ*<(e91lI+lv%x{KqgtG`8`vug)57R|nAWlZ-R%`+*3HC-%a6l2`sTil_UAY+ zR$#{soEw$beI&b*^TvX4vXf_y><$)lypy=v8a|3==~r)yJrp6=LzL0Z&wV33B*z;tvo@Mk*K@zct1v}2n{qtO^@0qL9?aVN(Ar>&z3Q**m- z>u%K*AuK+7mO5fh;t^|dMadCsyN!j|vYUT4ha=UAJ^R`@c{`W2*E%<^w6m<8HJK+F z^UW_AH5f2L!)$oTsF5TM>AzLOmR#DdLmXF(GL6Aj^NgXjCxJmm*pOEkkcA=V6E7B~V@n5AnGgF(Hn3<{T1Ys9?KT&R8N{5iXZsxr*iFTPf#E=?I#f_zO$4uK%pyeM7amU$O$hSYzG5 zQxELBCALj9#G;MCfJJK!3q2FUuowk~#k%Yv&OPmEr&5$md*AZ?rfEgr%T2KAME99y zm~l&3?o733D3V2!fl2qZXQJVm4w9JEE^iCHNOsX}zs~ih2rkI=+&&aW}tjGPXM74#wQTAqwM!9L$ z4#Sgf9Xe{ox={*EJ%3YNHE&1+Vqq4t9*x@KoOzfi*9~0oRam7N(owm7Vr?ZQO z4N-dPTH24Ni+NC7RbDBU`;|St_PV()2MC?%uJ$)e^tfoR<5Zzp)WL~$b;2$-{WYTWfot7_caufZtLx64nAEvdg0-;|csk?sH`}Mvcy+8vFY3EB2CpuaH)#w66GWq# zl_zP!dD}=MujSH)hqC&7P(kcVrQ6HyRjJ7kXb#_5Zc6~4l_E2 zRXNQba?XWj zYS2(aIyL03io=nGYkn+I;tGYJ%U|N?2DKpjgv|BzOWQALUUNnZv zxqL-|axPy*H!a7nM(sdMZFjrUnre;=wM@XpR5ex9__fNbBEz{{8i{RGYq62iQ&rVV zm!e2fj2DWD`WRrtL0cmyKi{+8mPtqsTM}%9BF*# zNG0CJa0;cY@zk_;;6>L=o78nB_R@7-mO4mF zSppZ@YvkLSx8H8c(I%*;qQ!Tu6}pG6);|JKyEbr4+a-~PWX*!GXp>%UanfZoi4~g_ zxxmX$*K*BW)lwH0og10*99dh;mt&D&Wyp)_C0ac#BiXPx%L&XBd!t0hAil2mI?6Y_A@xz^ku=qVe}ty|38 z`ifpJA)4PD)AcB!hz+z2VU?MYp|W1r+`EKUj|(lDDw8Imgys;UWd-Oa&l^C5J2`!0t^l63%Kg3BE z?Qs28l$uAin3noDTEe;)X$cb5&9Wv|1~sdS`5;tIZjA}op7^g_(AYXZRpka1SKyoF zd1#>vKeo%}%l!F>6nELKPa9OxuDQ!Li|0mmWX4rLUHSE6ser;e4tF8CDf0PBycxB$ zhVvjDUD#+ODVmb$>6)Q%# z)K`HOrV0f=`6|dFJ#eFtOUrC~fNO-tBr0W>c2G1lo(T~6?WU4O90WZf8bQ|>iDY78 zBAsAyGuOYD>!<-na@)GA`!L9~8mLVv?M@S|+4$(igh1b|nh+hEL0YTil^VoY%p+r7 z_4-=44Xl{YWT1Z_AXodsS=F*V>;0MB6x73*B`v#UO~yr9*2L zilO=ce(*7KKKN*2r-d#CxQ!kun>sLS>!B$gZ|7+WAs^##oK)a(S$1yI;dl z(s#TF&s(zpuPyw`EYXQl4h&JHxRVK2@}(x*0}g-^Cp^aj5PRY_(4 z5sU=@-P~t24uy<`rgElZYUEes7Z+(aQIw(ip@X6%XYN?YrCOTuEP2+D%BrY5ixWbx z7~V|QK7^I3&c+fEr5e%GqG*RT%3OdVzeFO}4wCI8DbOTKv?%pg7zswg$aR==a3E`S zTPgQn4vEF?1;s@Kgg2!AZO`2Rq6tAbqgvYL(FEHJ3albqquy-$u^-CClM8ie$8!4i`;gX+(E?imF+=d!zAK|CsQXDpwWDI($tr zcvCHwHTkTtn0hy>_0b#BEm`smTU}Y<6oc01N<`_UC12`k)kp={_l5M?nbRh6k*>1> zsZ5w$I<1fkFx;mCU9o`h!p7CwJq)-+Pq`eWN|w5dRi34&p3;M<=3+mkqO`h(MXP-t7;7>}ewEyY zzhYl(8R#b(2G%WJHqb928|a_Tm@PvY?LwWdcj|(T6^0&Mt+l})3Ya1z*SZE|)>xR4 zPA(Crta0*wQI?u+a#q8xs3vF5*A8g>*qfYMZq;hAeCJxet-zOW+1kZ!)Zk8jaJ7TA za?8ZlNj!1V4kF{v2&Z_-Ip4OHcQI?Vwo4Q33)C_tERYej4^_*A;8I%x@#LXQoBKKZ zE7PC_+OA!WsgEFK`fqzQMt$X)mrkd(X~W`~Chw{GRt- zzcan&7k4fB;-<1k=8ByezJAzemaBO@O{@C{iV+7SmCz-2&4G+>WuS{8EoK+^7bLR{D_%Qe#>BmAVqSQa`-IZVcJ`3_rE&)}_qPT!tSC%-vS#k(p}At(Y=Mjd zyG>Iy%!NdwY3&t(+E5Uu&%LTCon`hy;LzHAAt)GUxM4y#%+mk#4ZfUt>GR?lfliD zBc!)dMQ`{s{pd)7jgBn57|uH;-O?6JNd^hM^6yC0X%x8h1~fFe-758@vKXtu$fi(` zbz0ExSh~4X#)2ey(P_0>O+}^=dLdex)@nX|<%y-^!=6qnU}HAmyQq7c()W;JADP4ygn$!hB;X96Y4|9h=pcHScoI|FsfD(35p4<)v@}iHn!pw5 z4^%M#rU&MKOMx^TU^y38MOlrd$1t|)GP4!a4wbIiJcijeJ7YeFX~TC#f+k*OpI4f&_27_zQweQ>A%c|uzL6#85 zVzL@tsYABF_WD(UMDZgiFXoNjWmjWjm_+V!U{ zI0w^_z8l9y-|YQ#Dqd7&u3lS|@(oJ9bPT&z6ETvhAsGgzGRKhQI|_ElF4l6T-cwee z7)%`IjH-?XRH_s;t{}v?XrF4aeoyQhIiWJ^kCJ6&rBMC!$J8H06|#L=42KeL+82aq zt2}+(Vnli6?MS`#w=NjMkKY;IIIv zn`$(y7byywZ*IgXy71{R;NSMOv{J~=>2zeFmLO+>8UYL^>@elXKhD>E_Ql%btDxq} z`c+UszkLVSTKH^&2#i-j0fXWj<=MzgbA1i6h|>&BKF)MDJ~hc`@3*xu#OiAh@ziW{ z+DiRHZ3De816#JOV&Ig`O#?edqAPw@+86wcAU?=Aqe;-sOtF*cK&I29NY5Dd%cz0( z&&E=}jB2HRG08x`4``s@2Q<*{0~+Y}0S)x~fCl<~Km+|gpn-lL&?Y<-#`}PbG)F#ev762)Aa&G2$Hg>}VX$c!QVik8!V~1`{hMs2ZQCB-{;m+%Oww zEm=D;KFv1~aLb1eu4A?%{x2AS^4T~66TvH>k8dhi%&S#71*uTy3KKNlZFk}4XIV_J zY!nbuhajX!V2vG-udKjYdkP*{YlM~aqYfQ|DCd~0rU)yT@4gQgLhL~g9I1}pKP^aep{s3cByP@uxbNjoz#O9Q&NEhBx>zJ}qaCcy}MMDT2bXweUnWi~zTBUMT^5g=fn1SmHIm zmQa=a<0_Ylwph)KwLbbkTxUasqeqr4qY^WM$BW8f)VOHdL!B;GRuLBZ!H!B7>_r}5 zbDrAty}Co(Md4LvhR$Algl&?{{xrn@PG>3T*XFTpdlqzlcHM+ZuCb_6`PVn+%Wb@p z%*tDvif1ReD8aa<<#!RY{^*m(y*ISx%jOs;IkxJ+eV>gc69cn=VPa#H>`FA50XN^VlcZv=1}pIoMXzXKhU#<&gTUvZ|M zmCG3w-rYx++>J7SRDw9sU+t0@yn|X6KdrXb`a{d-r#54X{FeeK@lW^-A&Sk1J+NGa zd*uD2WDsksl>hTw+){Dl!NumvVpj-ow@$q{x3XjhJ)1xBV)nyXhCzpIR2@*i0x?$!*r zyXk>zI{G6fqda|N_59SvC|`+IaFlKYln)&BSKEG$?QGl!tga&;YZmeGQy@FAW`qym zz{i#QP?`Q(%#kU!HKK~#e1_UQJZN+C>1uPvpv_~~^?#Imv!kbnW=7`w z&k&iDPUff1&=?Oj{not5HRVC=uL6+=*9!%uM`;jAxN0AW-n3rkI4S>hz3!ehqwV;8 zZek2=$M0+iw&Nl2tYE(8<*PP`{u|FT71sp*_Qo(%zY5@U`gglwnqLiomWMh0P@$s$F@goU)?Si-cP<5U_1J(Q0-;=$|KK=HU9``g%IBNjCua@iXl7Yii1545GG+8K-rfatnyNic z{VM$$A78&MQMGU0mPB;7vQ#ZHY}fSr!LS`wt+sGH_$44$h!bBn3@_eS9^6&b=C1?!()ZwBWa{kue~}GXIv8Lh=IwXz!Up-cLt^hWN3~O0o<5jG2m1X-MbIK! zbTzf6_*0V@8?)v8zcN&A%>U$0GW>BJ*#xY^okqncKS!aD@w)`pMeGSRV<}X}#{|!) z^!*O0@Tfo=gZJD8B)##!?DS2?`tSFZN7b9=W1{-1W%WM>1RsqdA1yJVO80&-yplh` z_KwO9f8NTb2i5=ZnkHAA_3Clqbe;4{%|WG`R!5wdnIU|FTi{9g_D6CZfND7+cbwiJ z35m0J?G^syi9C+c9Rhu1Z}<_QPtsd|1O~ih?cbZrR32UBev&-M=qWvx;-VGF&{vY> zN!W{x+pYtLY2YsDRv7`bg$`Ny!*oxP`%b(%YkpV`DD}S-y7y+=&xPs6AUI^+>eKh^Jn;eSdH)#d8PCLc z`v%-fljZLMpq&!-!=N=Ib1h}jD>r36gfF|v|Ke|Ehbn?4VbYSH3;h@v%&b3lnuHVn zuqvU*fn&GUG+5>mzG0;aE4qXa;o3g!WR&VL_D8P+klvjL@AA+}^|X8-%oC5C$y~|d z>mGYk)xB4?(um&W@cs8yf!Q1JOIMjTybj3u2S7n2lcVM(|1p()>HY9(X~!Wdbg1BZ zdG~7dyk*bM>t2PRi$!uiv?eSDHv-U&a9#^&YtQQdBzqgwYjIrDvUy2Z5zL!gqoVqN zrJFK1(u)tT6%{w)!1^TpYpXa^q~GZulOj>)FRkXE;Gi;~h6(-Dtdh z((->#dprmcaEBFq`m7kx9;K=g`2@fnxIaO(N6+$bzB~~Ed0*|{cXlwpHvqV?dvz@O zvLjV5#{d~)6X`8ykHyr-^?jP8^8kLbkKHjK^V*I8Xq3f@fdH=q@EE)wt39yIeIW1W zAi2*Z!gv6PYZyQeo-3zUMVsf#`)RJiM>hqnISB9ttqn$}J0hDTzc&#@e9YII8v$*- zxf?d0bX(S0IrX3ibeek=gT9k|3<`f=F2yl-R)l_s#W&Y=ZdGVQC*IO zy2;CEw$LwJAN26#=nBLQS%3BZe$CRQ_ zI1KgLhg2J5Y?VLmm9bPeaTP1pWg6X0rEAS!IxLXkrT=4UYAVnVgj_0bQZ1d!;0+|Xl zx=leDGZXL@fij*hshd5^i+2d})!|x!m=KfYFUwFL;pRMpN}mTE64nQu)4oc=ns!(s zdZzlhTJh3Tt@k5@Xl|NbD@aux^>xacF}OyM8?7IF7SQ%KUn58}5On|bFM+>h9Q=Vm zC-k7A#iLewLh)o&?X0}PBhDi#)hJiWLzH;84?`L}wNa&~4c9UBK+&K@=&u%Zx)Fa^ zpgHBYEBXrlz>z^D))$0nhQ=Q%{@%KsmZj&^&fI_lQ^+h`H&zCjB$R8S7)5QcEM=%&z+!>xhcQ4ul%TI z)a`u=TYZhVO(08_ir?Wd&?5?QuhT>$7#3`Ub^LK4-_R1{H!5oZ!qFcB9dSW@LWJE9 zY?R&k19IQVJPiG^D9HCHK#i$+J??Nka1m1dR}~Wf?9MhOL+O&b_m8a`N=NH9jI+Bobh=GIm^l_e=)YfJhuE`7btRV>n{>GK zJyKBAD45T(tGG_$H(#qeTUQcx_Vu6N_$5vYDuX+*dZWM4uKfo7@-xfmyLRwu`SeWlrB5TQmVK=_WdONwlrWrQg!likhcZL1ir ztLx1T|8ehsdg;sd{^G*RFWb9t#jhuevM=3xIh#rK=7CksBuO&Tn(&+n&on%zc;=)w z=}gjZkY-6wBeke>I?oE}{E#;ARGTwMHPYE6^`X4*2uGDPK~leJdk*OjNy|y`eSB`B zY?AbIB-OcqJ{vqwBMGnYyqP4N!mpq33a6gpLw)F{_bW+ir>F3&C+#GS@~!s~zvQx; z^bC^v626k8Uxy@|>L(`2Lw!fN=y?`NZB=I-No7wbjrODdCAVqP%Sq2CJ(KiQlFH*# za`|@L!-0>VB2qz$^aC0${~9ax`Dl-RsrXc2)fH~_r+TlUyb3S}Ed0VN zol-j`)%j&`{wtnpI~$(rS2*<3Gv)=Wp2kj3{qz)WJ$Hns>M2EiiSIQxRWITZEz0XB zIM#b19jcDzo!-Z%brbiK8*tD)K^Kub%4pH`E)+YTJYiw8s7_}FJLk}DWdq@>yPA(JcV~7 zfx86u+ zajf)|3{>}7q`1DS&)+18KFLbINN1F{p8Bb+lEzbYN9!pc-@kx\02\00\00\03\00\00\00\09\00\00\00\09\00\00\00r\01\00\00\01\00\00\00\9a\00\00\00\0a\00\00\00\b0\00\00\00\02\00\00\006\00\00\00\0e\00\00\00 \00\00\00\09\00\00\00\10\00\00\00\03\00\00\00.\00\00\00\0a\00\00\006\00\00\00\09\00\00\00\07\00\00\00\02\00\00\00%\00\00\00\0d\00\00\00\02\00\00\00\09\00\00\00\06\00\00\00\01\00\00\00-\00\00\00\00\00\00\00\0d\00\00\00\02\00\00\001\00\00\00\0d\00\00\00\09\00\00\00\03\00\00\00\02\00\00\00\0b\00\00\00S\00\00\00\0b\00\00\00\07\00\00\00\00\00\00\00\a1\00\00\00\0b\00\00\00\06\00\00\00\09\00\00\00\07\00\00\00\03\00\00\008\00\00\00\01\00\00\00\02\00\00\00\06\00\00\00\03\00\00\00\01\00\00\00\03\00\00\00\02\00\00\00\0a\00\00\00\00\00\00\00\0b\00\00\00\01\00\00\00\03\00\00\00\06\00\00\00\04\00\00\00\04\00\00\00\c1\00\00\00\11\00\00\00\0a\00\00\00\09\00\00\00\05\00\00\00\00\00\00\00R\00\00\00\13\00\00\00\0d\00\00\00\09\00\00\00\d6\00\00\00\06\00\00\00\03\00\00\00\08\00\00\00\1c\00\00\00\01\00\00\00S\00\00\00\10\00\00\00\10\00\00\00\09\00\00\00R\00\00\00\0c\00\00\00\09\00\00\00\09\00\00\00T\00\00\00\0e\00\00\00\05\00\00\00\09\00\00\00\f3\00\00\00\0e\00\00\00\a6\00\00\00\09\00\00\00G\00\00\00\05\00\00\00\02\00\00\00\01\00\00\00\03\00\00\00\03\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\01\00\00\00\0d\00\00\00\09\00\00\00x\00\00\00\06\00\00\00\03\00\00\00\06\00\00\00\04\00\00\00\00\00\00\00\1d\00\00\00\09\00\00\00)\00\00\00\06\00\00\00\02\00\00\00\03\00\00\00\09\00\00\00\00\00\00\00\0a\00\00\00\0a\00\00\00/\00\00\00\0f\00\00\00\96\01\00\00\07\00\00\00\02\00\00\00\07\00\00\00\11\00\00\00\09\00\00\009\00\00\00\15\00\00\00\02\00\00\00\0d\00\00\00{\00\00\00\05\00\00\00\04\00\00\00\00\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\06\00\00\00\02\00\00\00\00\00\00\00\09\00\00\00\09\00\00\001\00\00\00\04\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\04\00\00\00\09\00\00\00\09\00\00\00J\01\00\00\03\00\00\00jK\00\00\09\00\00\00\87\00\00\00\04\00\00\00<\00\00\00\06\00\00\00\1a\00\00\00\09\00\00\00\f6\03\00\00\00\00\00\00\02\00\00\006\00\00\00\08\00\00\00\03\00\00\00R\00\00\00\00\00\00\00\0c\00\00\00\01\00\00\00\acL\00\00\01\00\00\00\c7\14\00\00\04\00\00\00\04\00\00\00\05\00\00\00\09\00\00\00\07\00\00\00\03\00\00\00\06\00\00\00\1f\00\00\00\03\00\00\00\95\00\00\00\02\00\00\00\8a\05\00\001\00\00\00\01\02\00\006\00\00\00\05\00\00\001\00\00\00\09\00\00\00\00\00\00\00\0f\00\00\00\00\00\00\00\17\00\00\00\04\00\00\00\02\00\00\00\0e\00\00\00Q\05\00\00\06\00\00\00\02\00\00\00\10\00\00\00\03\00\00\00\06\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\04\00\00\00\06\01\00\00\06\00\00\00\0a\00\00\00\09\00\00\00\a3\01\00\00\0d\00\00\00\d7\05\00\00\06\00\00\00n\00\00\00\06\00\00\00\06\00\00\00\09\00\00\00\97\12\00\00\09\00\00\00\07\05\0c\00\ef") - (data (;2;) (i32.const 4024) "p|\00\00\01\00\00\00\02\00\00\00\03")) + (data (;1;) (i32.const 1076) "\0b\00\00\00\02\00\00\00\19\00\00\00\02\00\00\00\12\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\0e\00\00\00\03\00\00\00\0d\00\00\00#\00\00\00z\00\00\00F\00\00\004\00\00\00\0c\01\00\00\1c\00\00\00\04\00\00\000\00\00\000\00\00\00\1f\00\00\00\0e\00\00\00\1d\00\00\00\06\00\00\00%\00\00\00\0b\00\00\00\1d\00\00\00\03\00\00\00#\00\00\00\05\00\00\00\07\00\00\00\02\00\00\00\04\00\00\00+\00\00\00\9d\00\00\00\13\00\00\00#\00\00\00\05\00\00\00#\00\00\00\05\00\00\00'\00\00\00\09\00\00\003\00\00\00\9d\00\00\006\01\00\00\0a\00\00\00\15\00\00\00\0b\00\00\00\07\00\00\00\99\00\00\00\05\00\00\00\03\00\00\00\00\00\00\00\02\00\00\00+\00\00\00\02\00\00\00\01\00\00\00\04\00\00\00\00\00\00\00\03\00\00\00\16\00\00\00\0b\00\00\00\16\00\00\00\0a\00\00\00\1e\00\00\00B\00\00\00\12\00\00\00\02\00\00\00\01\00\00\00\0b\00\00\00\15\00\00\00\0b\00\00\00\19\00\00\00G\00\00\007\00\00\00\07\00\00\00\01\00\00\00A\00\00\00\00\00\00\00\10\00\00\00\03\00\00\00\02\00\00\00\02\00\00\00\02\00\00\00\1c\00\00\00+\00\00\00\1c\00\00\00\04\00\00\00\1c\00\00\00$\00\00\00\07\00\00\00\02\00\00\00\1b\00\00\00\1c\00\00\005\00\00\00\0b\00\00\00\15\00\00\00\0b\00\00\00\12\00\00\00\0e\00\00\00\11\00\00\00o\00\00\00H\00\00\008\00\00\002\00\00\00\0e\00\00\002\00\00\00\0e\00\00\00#\00\00\00]\01\00\00)\00\00\00\07\00\00\00\01\00\00\00O\00\00\00\1c\00\00\00\0b\00\00\00\00\00\00\00\09\00\00\00\15\00\00\00k\00\00\00\14\00\00\00\1c\00\00\00\16\00\00\00\0d\00\00\004\00\00\00L\00\00\00,\00\00\00!\00\00\00\18\00\00\00\1b\00\00\00#\00\00\00\1e\00\00\00\00\00\00\00\03\00\00\00\00\00\00\00\09\00\00\00\22\00\00\00\04\00\00\00\00\00\00\00\0d\00\00\00/\00\00\00\0f\00\00\00\03\00\00\00\16\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00$\00\00\00\11\00\00\00\02\00\00\00\18\00\00\00U\00\00\00\06\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\03\00\00\00\02\00\00\00\0e\00\00\00\02\00\00\00\09\00\00\00\08\00\00\00.\00\00\00'\00\00\00\07\00\00\00\03\00\00\00\01\00\00\00\03\00\00\00\15\00\00\00\02\00\00\00\06\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\04\00\00\00\04\00\00\00\00\00\00\00\13\00\00\00\00\00\00\00\0d\00\00\00\04\00\00\00\9f\00\00\004\00\00\00\13\00\00\00\03\00\00\00\15\00\00\00\02\00\00\00\1f\00\00\00/\00\00\00\15\00\00\00\01\00\00\00\02\00\00\00\00\00\00\00\b9\00\00\00.\00\00\00*\00\00\00\03\00\00\00%\00\00\00/\00\00\00\15\00\00\00\00\00\00\00<\00\00\00*\00\00\00\0e\00\00\00\00\00\00\00H\00\00\00\1a\00\00\00\e6\00\00\00+\00\00\00u\00\00\00?\00\00\00 \00\00\00\07\00\00\00\03\00\00\00\00\00\00\00\03\00\00\00\07\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\17\00\00\00\10\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00_\00\00\00\07\00\00\00\03\00\00\00&\00\00\00\11\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\1d\00\00\00\00\00\00\00\0b\00\00\00'\00\00\00\08\00\00\00\00\00\00\00\16\00\00\00\00\00\00\00\0c\00\00\00-\00\00\00\14\00\00\00\00\00\00\00#\00\00\008\00\00\00\08\01\00\00\08\00\00\00\02\00\00\00$\00\00\00\12\00\00\00\00\00\00\002\00\00\00\1d\00\00\00q\00\00\00\06\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00%\00\00\00\16\00\00\00\00\00\00\00\1a\00\00\00\05\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\1f\00\00\00\0f\00\00\00\00\00\00\00H\01\00\00\12\00\00\00\be\00\00\00\00\00\00\00P\00\00\00\99\03\00\00g\00\00\00n\00\00\00\12\00\00\00\c3\00\00\00\bd\0a\00\00.\04\00\00\d2\0f\00\00F\02\00\00\ba!\00\008\02\00\00\08\00\00\00\1e\00\00\00r\00\00\00\1d\00\00\00\13\00\00\00/\00\00\00\11\00\00\00\03\00\00\00 \00\00\00\14\00\00\00\06\00\00\00\12\00\00\00\b1\02\00\00?\00\00\00\81\00\00\00J\00\00\00\06\00\00\00\00\00\00\00C\00\00\00\0c\00\00\00A\00\00\00\01\00\00\00\02\00\00\00\00\00\00\00\1d\00\00\00\f7\17\00\00\09\00\00\00\d5\04\00\00+\00\00\00\08\00\00\00\f8\22\00\00\1e\01\00\002\00\00\00\02\00\00\00\12\00\00\00\03\00\00\00\09\00\00\00\8b\01\00\00\05\09\00\00j\00\00\00\06\00\00\00\0c\00\00\00\04\00\00\00\08\00\00\00\08\00\00\00\09\00\00\00g\17\00\00T\00\00\00\02\00\00\00F\00\00\00\02\00\00\00\01\00\00\00\03\00\00\00\00\00\00\00\03\00\00\00\01\00\00\00\03\00\00\00\03\00\00\00\02\00\00\00\0b\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\06\00\00\00\02\00\00\00@\00\00\00\02\00\00\00\03\00\00\00\03\00\00\00\07\00\00\00\02\00\00\00\06\00\00\00\02\00\00\00\1b\00\00\00\02\00\00\00\03\00\00\00\02\00\00\00\04\00\00\00\02\00\00\00\00\00\00\00\04\00\00\00\06\00\00\00\02\00\00\00S\01\00\00\03\00\00\00\18\00\00\00\02\00\00\00\18\00\00\00\02\00\00\00\1e\00\00\00\02\00\00\00\18\00\00\00\02\00\00\00\1e\00\00\00\02\00\00\00\18\00\00\00\02\00\00\00\1e\00\00\00\02\00\00\00\18\00\00\00\02\00\00\00\1e\00\00\00\02\00\00\00\18\00\00\00\02\00\00\00\07\00\00\005\09\00\00,\00\00\00\0b\00\00\00\06\00\00\00\11\00\00\00\00\00\00\00r\01\00\00+\00\00\00\15\05\00\00\c4\00\00\00<\00\00\00C\00\00\00\08\00\00\00\00\00\00\00\b5\04\00\00\03\00\00\00\02\00\00\00\1a\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\00\00\00\00\03\00\00\00\00\00\00\00\02\00\00\00\09\00\00\00\02\00\00\00\03\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\07\00\00\00\00\00\00\00\05\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\02\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\00\00\00\00\03\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\00\00\00\00\03\00\00\00\03\00\00\00\02\00\00\00\06\00\00\00\02\00\00\00\03\00\00\00\02\00\00\00\03\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\09\00\00\00\02\00\00\00\10\00\00\00\06\00\00\00\02\00\00\00\02\00\00\00\04\00\00\00\02\00\00\00\10\00\00\00E\11\00\00\dd\a6\00\00#\00\00\004\10\00\00\0c\00\00\00\dd\00\00\00\03\00\00\00\81\16\00\00\0f\00\00\000\1d\00\00 \0c\00\00\1d\02\00\00\e3\05\00\00J\13\00\00\fd\01\00\00\00\00\00\00\e3\00\00\00\00\00\00\00\96\00\00\00\04\00\00\00&\01\00\00\09\00\00\00X\05\00\00\02\00\00\00\02\00\00\00\01\00\00\00\06\00\00\00\03\00\00\00)\00\00\00\02\00\00\00\05\00\00\00\00\00\00\00\a6\00\00\00\01\00\00\00>\02\00\00\03\00\00\00\09\00\00\00\09\00\00\00r\01\00\00\01\00\00\00\9a\00\00\00\0a\00\00\00\b0\00\00\00\02\00\00\006\00\00\00\0e\00\00\00 \00\00\00\09\00\00\00\10\00\00\00\03\00\00\00.\00\00\00\0a\00\00\006\00\00\00\09\00\00\00\07\00\00\00\02\00\00\00%\00\00\00\0d\00\00\00\02\00\00\00\09\00\00\00\06\00\00\00\01\00\00\00-\00\00\00\00\00\00\00\0d\00\00\00\02\00\00\001\00\00\00\0d\00\00\00\09\00\00\00\03\00\00\00\02\00\00\00\0b\00\00\00S\00\00\00\0b\00\00\00\07\00\00\00\00\00\00\00\a1\00\00\00\0b\00\00\00\06\00\00\00\09\00\00\00\07\00\00\00\03\00\00\008\00\00\00\01\00\00\00\02\00\00\00\06\00\00\00\03\00\00\00\01\00\00\00\03\00\00\00\02\00\00\00\0a\00\00\00\00\00\00\00\0b\00\00\00\01\00\00\00\03\00\00\00\06\00\00\00\04\00\00\00\04\00\00\00\c1\00\00\00\11\00\00\00\0a\00\00\00\09\00\00\00\05\00\00\00\00\00\00\00R\00\00\00\13\00\00\00\0d\00\00\00\09\00\00\00\d6\00\00\00\06\00\00\00\03\00\00\00\08\00\00\00\1c\00\00\00\01\00\00\00S\00\00\00\10\00\00\00\10\00\00\00\09\00\00\00R\00\00\00\0c\00\00\00\09\00\00\00\09\00\00\00T\00\00\00\0e\00\00\00\05\00\00\00\09\00\00\00\f3\00\00\00\0e\00\00\00\a6\00\00\00\09\00\00\00G\00\00\00\05\00\00\00\02\00\00\00\01\00\00\00\03\00\00\00\03\00\00\00\02\00\00\00\00\00\00\00\02\00\00\00\01\00\00\00\0d\00\00\00\09\00\00\00x\00\00\00\06\00\00\00\03\00\00\00\06\00\00\00\04\00\00\00\00\00\00\00\1d\00\00\00\09\00\00\00)\00\00\00\06\00\00\00\02\00\00\00\03\00\00\00\09\00\00\00\00\00\00\00\0a\00\00\00\0a\00\00\00/\00\00\00\0f\00\00\00\96\01\00\00\07\00\00\00\02\00\00\00\07\00\00\00\11\00\00\00\09\00\00\009\00\00\00\15\00\00\00\02\00\00\00\0d\00\00\00{\00\00\00\05\00\00\00\04\00\00\00\00\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\06\00\00\00\02\00\00\00\00\00\00\00\09\00\00\00\09\00\00\001\00\00\00\04\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\04\00\00\00\09\00\00\00\09\00\00\00J\01\00\00\03\00\00\00jK\00\00\09\00\00\00\87\00\00\00\04\00\00\00<\00\00\00\06\00\00\00\1a\00\00\00\09\00\00\00\f6\03\00\00\00\00\00\00\02\00\00\006\00\00\00\08\00\00\00\03\00\00\00R\00\00\00\00\00\00\00\0c\00\00\00\01\00\00\00\acL\00\00\01\00\00\00\c7\14\00\00\04\00\00\00\04\00\00\00\05\00\00\00\09\00\00\00\07\00\00\00\03\00\00\00\06\00\00\00\1f\00\00\00\03\00\00\00\95\00\00\00\02\00\00\00\8a\05\00\001\00\00\00\01\02\00\006\00\00\00\05\00\00\001\00\00\00\09\00\00\00\00\00\00\00\0f\00\00\00\00\00\00\00\17\00\00\00\04\00\00\00\02\00\00\00\0e\00\00\00Q\05\00\00\06\00\00\00\02\00\00\00\10\00\00\00\03\00\00\00\06\00\00\00\02\00\00\00\01\00\00\00\02\00\00\00\04\00\00\00\06\01\00\00\06\00\00\00\0a\00\00\00\09\00\00\00\a3\01\00\00\0d\00\00\00\d7\05\00\00\06\00\00\00n\00\00\00\06\00\00\00\06\00\00\00\09\00\00\00\97\12\00\00\09\00\00\00\07\05\0c\00\ef\00\00\00d\00e\00f\00a\00u\00l\00t") + (data (;2;) (i32.const 4048) "_\00_\00e\00s\00M\00o\00d\00u\00l\00e\00\08\00\00\00\0a\00\00\00\0c\00\00\00\0a\00\00\00\0a\00\00\00\0a\00\00\00\0a\00\00\00\0a\00\00\00\0d\00\00\00\09\00\00\00\0b") + (data (;3;) (i32.const 4112) "@}\00\00\03\00\00\00\04\00\00\00\05")) diff --git a/src/lexer.c b/src/lexer.c index dab3336..0a14224 100755 --- a/src/lexer.c +++ b/src/lexer.c @@ -6,7 +6,7 @@ const uint16_t __empty_char = '\0'; const uint16_t* EMPTY_CHAR = &__empty_char; // tracked depth of template and brackets #define STACK_DEPTH 2048 -enum TokenType { ClassToken = 1, ExportObjectToken = 2 }; +enum TokenType { ClassToken = 1, ExportObjectToken = 2, WebpackTableToken = 4 }; const uint16_t* source; bool lastSlashWasDivision; @@ -24,6 +24,53 @@ bool allowMinifiedEnumerable; static bool extended; static uint16_t* exportObjectPos; +typedef struct WebpackDependency { + const uint16_t* start; + const uint16_t* end; + bool star; + struct WebpackDependency* next; +} WebpackDependency; + +typedef struct WebpackModule { + const uint16_t* start; + const uint16_t* end; + const uint16_t* target; + uint32_t targetLength; + const uint16_t* module; + uint32_t moduleLength; + uint32_t hash; + uint32_t depth; + uint8_t visited; + uint8_t requested; + bool queued; + Slice* exports; + Slice* exportsTail; + Slice* reexports; + Slice* reexportsTail; + Slice* imports; + Slice* importsTail; + WebpackDependency* dependencies; + WebpackDependency* dependenciesTail; + struct WebpackModule* next; + struct WebpackModule* bucketNext; + struct WebpackModule* queueNext; +} WebpackModule; + +static uint16_t* webpackTablePos; +static uint16_t* webpackFactoryPos; +static const uint16_t* webpackEntryStart; +static const uint16_t* webpackEntryEnd; +static WebpackModule* webpackModules; +static WebpackModule* webpackModulesTail; +static WebpackModule* webpackCurrent; +static WebpackModule* webpackPending; +static WebpackModule webpackInlineRoot; +static WebpackModule** webpackBuckets; +static uint32_t webpackCapacity, webpackCount; +static bool webpackPublished, webpackInvalid, webpackInline; +static void (*webpackAddExport)(const uint16_t*, const uint16_t*); +static void (*webpackAddReexport)(const uint16_t*, const uint16_t*); + uint16_t* lastReexportStart; uint16_t* lastReexportEnd; @@ -48,6 +95,365 @@ void (*clearReexports)(); static bool isStatementBrace (uint16_t* curPos, uint16_t depth); static uint16_t readExportExpressionStart (); +static bool readWebpackToken (uint16_t ch); +static void readWebpackFactory (); +static void finishWebpack (); +static void captureWebpackExport (const uint16_t* start, const uint16_t* end); +static void captureWebpackReexport (const uint16_t* start, const uint16_t* end); +static void addWebpackDependency (const uint16_t* start, const uint16_t* end, bool star); +static bool readWebpackCall (const uint16_t** start, const uint16_t** end); + +typedef struct WebpackText { + const uint16_t* pos; + const uint16_t* end; + uint16_t pending; +} WebpackText; + +static WebpackText webpackText (const uint16_t* start, const uint16_t* end) { + if (start < end && (*start == '\'' || *start == '"')) { start++; end--; } + return (WebpackText){ start, end, 0 }; +} + +static uint32_t readWebpackUnit (WebpackText* text) { + if (text->pending) { + uint32_t unit = text->pending; + text->pending = 0; + return unit; + } + while (text->pos < text->end) { + uint32_t unit = *text->pos++; + if (unit != '\\' || text->pos == text->end) return unit; + unit = *text->pos++; + if (unit == '\n' || unit == '\r') { + if (unit == '\r' && text->pos < text->end && *text->pos == '\n') text->pos++; + continue; + } + if (unit == 'u' || unit == 'x') { + uint32_t count = unit == 'u' ? 4 : 2; + bool braces = unit == 'u' && text->pos < text->end && *text->pos == '{'; + if (braces) { text->pos++; count = 6; } + unit = 0; + while (count-- && text->pos < text->end && *text->pos != '}') { + uint16_t digit = *text->pos++; + unit = unit * 16 + (digit <= '9' ? digit - '0' : (digit | 32) - 'a' + 10); + } + if (braces && text->pos < text->end && *text->pos == '}') text->pos++; + if (unit > 0xffff) { + text->pending = 0xdc00 + ((unit - 0x10000) & 0x3ff); + unit = 0xd800 + ((unit - 0x10000) >> 10); + } + } + else if (unit >= '0' && unit <= '7') { + unit -= '0'; + for (uint32_t i = 0; i < 2 && text->pos < text->end && *text->pos >= '0' && + *text->pos <= '7' && unit * 8 + *text->pos - '0' <= 255; i++) + unit = unit * 8 + *text->pos++ - '0'; + } + else { + switch (unit) { + case 'n': unit = '\n'; break; + case 'r': unit = '\r'; break; + case 't': unit = '\t'; break; + case 'b': unit = '\b'; break; + case 'f': unit = '\f'; break; + case 'v': unit = '\v'; break; + } + } + return unit; + } + return UINT32_MAX; +} + +static uint32_t webpackHash (const uint16_t* start, const uint16_t* end) { + WebpackText text = webpackText(start, end); + uint32_t hash = 2166136261u, unit; + while ((unit = readWebpackUnit(&text)) != UINT32_MAX) hash = (hash ^ unit) * 16777619u; + return hash; +} + +static bool webpackEqual (const uint16_t* start, const uint16_t* end, + const uint16_t* other, const uint16_t* otherEnd) { + WebpackText left = webpackText(start, end), right = webpackText(other, otherEnd); + uint32_t unit; + do { + unit = readWebpackUnit(&left); + if (unit != readWebpackUnit(&right)) return false; + } while (unit != UINT32_MAX); + return true; +} + +static WebpackModule* findWebpackModule (const uint16_t* start, const uint16_t* end) { + if (!webpackCapacity) return NULL; + uint32_t hash = webpackHash(start, end); + for (WebpackModule* module = webpackBuckets[hash & (webpackCapacity - 1)]; module; module = module->bucketNext) + if (module->hash == hash && webpackEqual(start, end, module->start, module->end)) return module; + return NULL; +} + +static WebpackModule* createWebpackModule (const uint16_t* start, const uint16_t* end) { + if (!webpackCapacity || webpackCount == webpackCapacity / 2) { + if (webpackCapacity > UINT32_MAX / 2 / sizeof(WebpackModule*)) { + syntaxError(12); + return NULL; + } + webpackCapacity = webpackCapacity ? webpackCapacity * 2 : 16; + size_t bytes = webpackCapacity * sizeof(WebpackModule*); + ensureAnalysisCapacity(bytes); + webpackBuckets = (WebpackModule**)(uintptr_t)analysis_head; + analysis_head += bytes; + for (uint32_t i = 0; i < webpackCapacity; i++) webpackBuckets[i] = NULL; + for (WebpackModule* module = webpackModules; module; module = module->next) { + uint32_t bucket = module->hash & (webpackCapacity - 1); + module->bucketNext = webpackBuckets[bucket]; + webpackBuckets[bucket] = module; + } + } + if (findWebpackModule(start, end)) webpackInvalid = true; + ensureAnalysisCapacity(sizeof(WebpackModule)); + WebpackModule* module = (WebpackModule*)(uintptr_t)analysis_head; + analysis_head += sizeof(WebpackModule); + *module = (WebpackModule){ .start = start, .end = end, .hash = webpackHash(start, end) }; + uint32_t bucket = module->hash & (webpackCapacity - 1); + module->bucketNext = webpackBuckets[bucket]; + webpackBuckets[bucket] = module; + if (webpackModulesTail) webpackModulesTail->next = module; + else webpackModules = module; + webpackModulesTail = module; + webpackCount++; + return module; +} + +static void captureWebpackSlice (const uint16_t* start, const uint16_t* end, Slice** first, Slice** last) { + ensureAnalysisCapacity(sizeof(Slice)); + Slice* slice = (Slice*)(uintptr_t)analysis_head; + analysis_head += sizeof(Slice); + *slice = (Slice){ start, end, NULL }; + if (*last) (*last)->next = slice; + else *first = slice; + *last = slice; +} + +static void captureWebpackExport (const uint16_t* start, const uint16_t* end) { + captureWebpackSlice(start, end, &webpackCurrent->exports, &webpackCurrent->exportsTail); +} + +static void captureWebpackReexport (const uint16_t* start, const uint16_t* end) { + captureWebpackSlice(start, end, &webpackCurrent->reexports, &webpackCurrent->reexportsTail); +} + +static void addWebpackDependency (const uint16_t* start, const uint16_t* end, bool star) { + WebpackModule* owner = webpackCurrent ? webpackCurrent : &webpackInlineRoot; + ensureAnalysisCapacity(sizeof(WebpackDependency)); + WebpackDependency* dependency = (WebpackDependency*)(uintptr_t)analysis_head; + analysis_head += sizeof(WebpackDependency); + *dependency = (WebpackDependency){ start, end, star, NULL }; + if (owner->dependenciesTail) owner->dependenciesTail->next = dependency; + else owner->dependencies = dependency; + owner->dependenciesTail = dependency; +} + +static bool readWebpackId (const uint16_t** start, const uint16_t** finish) { + uint16_t ch = commentWhitespace(); + *start = pos; + if (ch == '\'' || ch == '"') { stringLiteral(ch); pos++; } + else if (ch >= '0' && ch <= '9') { while (*pos >= '0' && *pos <= '9') pos++; } + else if (!identifier(ch)) return false; + *finish = pos; + return !error; +} + +static bool readWebpackCall (const uint16_t** start, const uint16_t** finish) { + uint16_t* revert = pos; + if (str_eq18(pos + 1, '_', 'w', 'e', 'b', 'p', 'a', 'c', 'k', '_', 'r', 'e', 'q', 'u', 'i', 'r', 'e', '_', '_')) { + pos += 19; + if (commentWhitespace() == '(') { + pos++; + uint16_t ch = commentWhitespace(); + if ((ch == '\'' || ch == '"' || ch >= '0' && ch <= '9') && + readWebpackId(start, finish) && commentWhitespace() == ')') { + pos++; + ch = commentWhitespace(); + if (pos > end || ch == ';' || ch == ',' || ch == ')' || ch == '}') return true; + } + } + } + pos = revert; + return false; +} + +static uint32_t webpackCharAt (const uint16_t* at) { + if (at > end) return 0; + uint16_t ch = *at; + return (ch & 0xfc00) == 0xd800 ? (((ch & 0x3ff) << 10) | (*(at + 1) & 0x3ff)) + 0x10000 : ch; +} + +static bool matchesWebpackBinding (const uint16_t* binding, uint32_t length) { + return binding && (size_t)(end - pos + 1) >= length && *pos == *binding && + memcmp(pos, binding, length * sizeof(uint16_t)) == 0 && !isIdentifierChar(webpackCharAt(pos + length)); +} + +static bool readWebpackToken (uint16_t ch) { + if (webpackCurrent) { + if (matchesWebpackBinding(webpackCurrent->target, webpackCurrent->targetLength)) { + tryParseExportsDotAssign(false, webpackCurrent->targetLength); + return true; + } + if (matchesWebpackBinding(webpackCurrent->module, webpackCurrent->moduleLength)) { + tryParseModuleExportsDotAssign(webpackCurrent->moduleLength); + return true; + } + } + if (ch != '_') return false; + uint16_t* start = pos; + if (str_eq18(pos + 1, '_', 'w', 'e', 'b', 'p', 'a', 'c', 'k', '_', 'm', 'o', 'd', 'u', 'l', 'e', 's', '_', '_') && + !isIdentifierChar(webpackCharAt(pos + 19))) { + pos += 19; + if (commentWhitespace() == '=') { + pos++; + uint16_t next = commentWhitespace(); + if (next == '(') next = readExportExpressionStart(); + if (next == '{') { + if (webpackTablePos) webpackInvalid = true; + else webpackTablePos = pos; + } + } + pos = start; + return false; + } + if (str_eq18(pos + 1, '_', 'w', 'e', 'b', 'p', 'a', 'c', 'k', '_', 'e', 'x', 'p', 'o', 'r', 't', 's', '_', '_') && + !isIdentifierChar(webpackCharAt(pos + 19))) { + if (webpackTablePos && !webpackCurrent) { + pos += 19; + if (commentWhitespace() == '=') { + pos++; + webpackInline = commentWhitespace() == '{'; + webpackEntryStart = webpackEntryEnd = NULL; + const uint16_t *id, *idEnd; + if (readWebpackCall(&id, &idEnd)) { webpackEntryStart = id; webpackEntryEnd = idEnd; } + } + } + pos = start; + tryParseExportsDotAssign(false, 19); + return true; + } + if (!str_eq18(pos + 1, '_', 'w', 'e', 'b', 'p', 'a', 'c', 'k', '_', 'r', 'e', 'q', 'u', 'i', 'r', 'e', '_', '_') || + isIdentifierChar(webpackCharAt(pos + 19))) return false; + if ((webpackCurrent ? openTokenDepth == webpackCurrent->depth : webpackInline) && + readWebpackCall(&starExportBinding_.specifier_start, &starExportBinding_.specifier_end)) { + WebpackModule* owner = webpackCurrent ? webpackCurrent : &webpackInlineRoot; + captureWebpackSlice(starExportBinding_.specifier_start, starExportBinding_.specifier_end, + &owner->imports, &owner->importsTail); + tryBacktrackAddStarExportBinding(start - 1); + return true; + } + pos = start + 19; + if (commentWhitespace() == '.') { + pos++; + if (commentWhitespace() == 'd') { + pos++; + if (commentWhitespace() == '(') { + pos++; + if (readExportsOrModuleDotExports(commentWhitespace()) && commentWhitespace() == ',') { + pos++; + if (commentWhitespace() == '{') exportObjectPos = pos; + } + } + } + } + pos = start; + return false; +} + +static void readWebpackFactory () { + uint16_t* revert = pos; + const uint16_t *id, *idEnd, *module = NULL, *moduleEnd = NULL, *target = NULL, *targetEnd = NULL, *ignored, *ignoredEnd; + pos++; + bool found = readWebpackId(&id, &idEnd); + if (found && commentWhitespace() == ':') { + pos++; + uint16_t ch = commentWhitespace(); + if (ch == '(') { + uint16_t* opening = pos++; + ch = commentWhitespace(); + if (ch != '(' && !(ch == 'f' && str_eq7(pos + 1, 'u', 'n', 'c', 't', 'i', 'o', 'n'))) { pos = opening; ch = '('; } + } + bool fn = ch == 'f' && str_eq7(pos + 1, 'u', 'n', 'c', 't', 'i', 'o', 'n'); + if (fn) { pos += 8; ch = commentWhitespace(); } + if (ch == '(') { + pos++; + if (commentWhitespace() != ')') readWebpackId(&module, &moduleEnd); + ch = commentWhitespace(); + if (ch == ',') { + pos++; + readWebpackId(&target, &targetEnd); + ch = commentWhitespace(); + if (ch == ',') { pos++; readWebpackId(&ignored, &ignoredEnd); ch = commentWhitespace(); } + } + if (ch == ')') { + pos++; + ch = commentWhitespace(); + if (!fn && ch == '=' && *(pos + 1) == '>') { pos += 2; ch = commentWhitespace(); } + if (ch == '{') { + webpackFactoryPos = pos; + webpackPending = createWebpackModule(id, idEnd); + if (webpackPending) { + webpackPending->target = target; + webpackPending->targetLength = target ? targetEnd - target : 0; + webpackPending->module = module; + webpackPending->moduleLength = module ? moduleEnd - module : 0; + } + } + } + } + } + if (found && (!webpackFactoryPos || webpackFactoryPos <= revert)) webpackInvalid = true; + if (!error) pos = revert; +} + +static void finishWebpack () { + WebpackModule* first = NULL; + WebpackModule* last = NULL; + if (webpackPublished && (webpackEntryStart || webpackInline) && !webpackInvalid) { + first = last = webpackEntryStart ? findWebpackModule(webpackEntryStart, webpackEntryEnd) : &webpackInlineRoot; + if (!first) webpackInvalid = true; + else { first->requested = 2; first->queued = true; } + } + while (first && !webpackInvalid) { + WebpackModule* module = first; + first = first->queueNext; + if (!first) last = NULL; + module->queued = false; + uint8_t flag = module->requested & 2 ? 2 : 1; + if (module->visited & flag) continue; + module->visited |= flag; + static const uint16_t defaultName[] = { 'd', 'e', 'f', 'a', 'u', 'l', 't' }; + static const uint16_t esModuleName[] = { '_', '_', 'e', 's', 'M', 'o', 'd', 'u', 'l', 'e' }; + for (Slice* slice = module->exports; slice; slice = slice->next) + if (flag == 2 || !webpackEqual(slice->start, slice->end, defaultName, defaultName + 7) && + !webpackEqual(slice->start, slice->end, esModuleName, esModuleName + 10)) + webpackAddExport(slice->start, slice->end); + for (Slice* slice = module->reexports; slice; slice = slice->next) + webpackAddReexport(slice->start, slice->end); + for (WebpackDependency* dependency = module->dependencies; dependency; dependency = dependency->next) { + WebpackModule* target = findWebpackModule(dependency->start, dependency->end); + if (!target) { webpackInvalid = true; break; } + uint8_t requested = flag == 1 || dependency->star ? 1 : 2; + target->requested |= requested; + if (target->queued || target->visited & requested) continue; + target->queued = true; + target->queueNext = NULL; + if (last) last->queueNext = target; + else first = target; + last = target; + } + } + if (!webpackPublished || !webpackEntryStart && !webpackInline || webpackInvalid) { + for (WebpackModule* module = webpackModules; module; module = module->next) { + for (Slice* slice = module->exports; slice; slice = slice->next) webpackAddExport(slice->start, slice->end); + for (Slice* slice = module->reexports; slice; slice = slice->next) webpackAddReexport(slice->start, slice->end); + } + } +} static inline __attribute__((always_inline)) bool isTokenRunChar (uint16_t ch) { return (uint16_t)((ch | 32) - 'a') < 26 || (uint16_t)(ch - '0') < 10 || @@ -80,11 +486,13 @@ uint32_t parseCJS (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(co } // Note: parsing is based on the _assumption_ that the source is already valid -uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(const uint16_t*, const uint16_t*), void (*_addReexport)(const uint16_t*, const uint16_t*), void (*_addUnsafeGetter)(const uint16_t*, const uint16_t*), void (*_clearReexports)(), uint32_t options) { +static __attribute__((always_inline)) uint32_t parseCJSMode (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(const uint16_t*, const uint16_t*), void (*_addReexport)(const uint16_t*, const uint16_t*), void (*_addUnsafeGetter)(const uint16_t*, const uint16_t*), void (*_clearReexports)(), uint32_t options, bool extendedMode) { + if (addExport == captureWebpackExport) addExport = webpackAddExport; + if (addReexport == captureWebpackReexport) addReexport = webpackAddReexport; source = _source; sourceLen = _sourceLen; allowMinifiedEnumerable = (options & 1) != 0; - extended = (options & 2) != 0; + extended = extendedMode; if (_addExport) addExport = _addExport; if (_addReexport) @@ -105,6 +513,18 @@ uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_ad lastStarExport = NULL; nextBraceIsClass = false; exportObjectPos = 0; + webpackTablePos = NULL; + if (extendedMode) { + webpackTablePos = webpackFactoryPos = NULL; + webpackEntryStart = webpackEntryEnd = NULL; + webpackModules = webpackModulesTail = webpackCurrent = webpackPending = NULL; + webpackBuckets = NULL; + webpackCapacity = webpackCount = 0; + webpackPublished = webpackInvalid = webpackInline = false; + webpackInlineRoot = (WebpackModule){0}; + webpackAddExport = addExport; + webpackAddReexport = addReexport; + } pos = (uint16_t*)(source - 1); uint16_t ch = '\0'; @@ -128,7 +548,10 @@ uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_ad if (ch == 32 || ch < 14 && ch > 8) continue; - if (openTokenDepth == 0) { + if (extendedMode && keywordStart(pos) && readWebpackToken(ch)) + goto skipTokenRun; + + if (openTokenDepth == 0 || extendedMode && webpackCurrent && webpackCurrent->depth == openTokenDepth) { switch (ch) { case 'i': if (str_eq5(pos + 1, 'm', 'p', 'o', 'r', 't') && keywordStart(pos)) @@ -173,7 +596,7 @@ uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_ad case 'e': if (str_eq5(pos + 1, 'x', 'p', 'o', 'r', 't') && keywordStart(pos)) { if (*(pos + 6) == 's') - tryParseExportsDotAssign(false); + tryParseExportsDotAssign(false, 7); else if (openTokenDepth == 0) throwIfExportStatement(); } @@ -184,29 +607,32 @@ uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_ad goto skipTokenRun; case 'm': if (str_eq5(pos + 1, 'o', 'd', 'u', 'l', 'e') && keywordStart(pos)) - tryParseModuleExportsDotAssign(); + tryParseModuleExportsDotAssign(6); goto skipTokenRun; case 'O': if (str_eq5(pos + 1, 'b', 'j', 'e', 'c', 't') && keywordStart(pos)) - tryParseObjectDefineOrKeys(openTokenDepth == 0); + tryParseObjectDefineOrKeys(openTokenDepth == 0 || extendedMode && + (webpackCurrent ? webpackCurrent->depth == openTokenDepth : webpackInline)); goto skipTokenRun; case '(': if (!pushOpenToken(lastTokenPos)) return error; break; case '[': - if (extended && !pushOpenToken(lastTokenPos)) + if (extendedMode && !pushOpenToken(lastTokenPos)) return error; break; case ']': - if (extended) { + if (extendedMode) { if (openTokenDepth == 0) return syntaxError(8), error; openTokenDepth--; } break; case ',': - if (extended && openTokenDepth && (openTokenTypeStack[openTokenDepth - 1] & ExportObjectToken)) + if (extendedMode && openTokenDepth && (openTokenTypeStack[openTokenDepth - 1] & WebpackTableToken)) + readWebpackFactory(); + if (extendedMode && openTokenDepth && (openTokenTypeStack[openTokenDepth - 1] & ExportObjectToken)) readExportProperty(); break; case ')': @@ -219,15 +645,32 @@ uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_ad return error; openTokenTypeStack[openTokenDepth - 1] = nextBraceIsClass ? ClassToken : 0; nextBraceIsClass = false; - if (extended && pos == exportObjectPos) { + if (extendedMode && pos == exportObjectPos) { openTokenTypeStack[openTokenDepth - 1] |= ExportObjectToken; exportObjectPos = 0; readExportProperty(); } + if (extendedMode && pos == webpackTablePos) { + openTokenTypeStack[openTokenDepth - 1] |= WebpackTableToken; + readWebpackFactory(); + } + else if (extendedMode && pos == webpackFactoryPos) { + webpackCurrent = webpackPending; + webpackCurrent->depth = openTokenDepth; + firstStarExport = lastStarExport = NULL; + addExport = captureWebpackExport; + addReexport = captureWebpackReexport; + } break; case '}': if (openTokenDepth == 0) return syntaxError(2), error; + if (extendedMode && webpackCurrent && webpackCurrent->depth == openTokenDepth) { + webpackCurrent = NULL; + firstStarExport = lastStarExport = NULL; + addExport = webpackAddExport; + addReexport = webpackAddReexport; + } if (openTokenDepth-- == templateDepth) { templateDepth = templateStack[--templateStackDepth]; templateString(); @@ -268,7 +711,7 @@ uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_ad lastToken == ')' && isParenKeyword(openTokenPosStack[openTokenDepth]) || lastToken == '}' && (isExpressionTerminator(openTokenPosStack[openTokenDepth]) || (openTokenTypeStack[openTokenDepth] & ClassToken) || - extended && isStatementBrace(openTokenPosStack[openTokenDepth], openTokenDepth)) || + extendedMode && isStatementBrace(openTokenPosStack[openTokenDepth], openTokenDepth)) || lastToken == '/' && lastSlashWasDivision || isExpressionKeyword(lastTokenPos) || !lastToken) { @@ -302,10 +745,17 @@ uint32_t parseCJSWithOptions (uint16_t* _source, uint32_t _sourceLen, void (*_ad return error; } + if (webpackTablePos) finishWebpack(); // success return 0; } +uint32_t parseCJSWithOptions (uint16_t* source, uint32_t sourceLen, void (*addExport)(const uint16_t*, const uint16_t*), void (*addReexport)(const uint16_t*, const uint16_t*), void (*addUnsafeGetter)(const uint16_t*, const uint16_t*), void (*clearReexports)(), uint32_t options) { + if (options & 2) + return parseCJSMode(source, sourceLen, addExport, addReexport, addUnsafeGetter, clearReexports, options, true); + return parseCJSMode(source, sourceLen, addExport, addReexport, addUnsafeGetter, clearReexports, options, false); +} + void tryBacktrackAddStarExportBinding (uint16_t* bPos) { while (*bPos == ' ' && bPos > source) bPos--; @@ -914,7 +1364,16 @@ void tryParseObjectDefineOrKeys (bool keys) { while (binding) { if (id_len == binding->id_end - binding->id_start && 0 == memcmp(id_start, binding->id_start, id_len * sizeof(uint16_t))) { - addReexport(binding->specifier_start, binding->specifier_end); + bool internal = false; + if (extended) { + WebpackModule* owner = webpackCurrent ? webpackCurrent : &webpackInlineRoot; + for (Slice* slice = owner->imports; slice; slice = slice->next) + if (slice->start == binding->specifier_start) { internal = true; break; } + } + if (internal) + addWebpackDependency(binding->specifier_start, binding->specifier_end, true); + else + addReexport(binding->specifier_start, binding->specifier_end); pos = revertPos; return; } @@ -929,8 +1388,18 @@ void tryParseObjectDefineOrKeys (bool keys) { bool readExportsOrModuleDotExports (uint16_t ch) { uint16_t* revertPos = pos; - if (ch == 'm' && str_eq5(pos + 1, 'o', 'd', 'u', 'l', 'e')) { - pos += 6; + if (extended) { + if (webpackCurrent && matchesWebpackBinding(webpackCurrent->target, webpackCurrent->targetLength)) { + pos += webpackCurrent->targetLength; + return true; + } + if (!webpackCurrent && ch == '_' && + str_eq18(pos + 1, '_', 'w', 'e', 'b', 'p', 'a', 'c', 'k', '_', 'e', 'x', 'p', 'o', 'r', 't', 's', '_', '_') && + !isIdentifierChar(webpackCharAt(pos + 19))) { pos += 19; return true; } + } + bool moduleAlias = extended && webpackCurrent && matchesWebpackBinding(webpackCurrent->module, webpackCurrent->moduleLength); + if (moduleAlias || ch == 'm' && str_eq5(pos + 1, 'o', 'd', 'u', 'l', 'e')) { + pos += moduleAlias ? webpackCurrent->moduleLength : 6; ch = commentWhitespace(); if (ch != '.') { pos = revertPos; @@ -949,23 +1418,23 @@ bool readExportsOrModuleDotExports (uint16_t ch) { } } -void tryParseModuleExportsDotAssign () { - pos += 6; +void tryParseModuleExportsDotAssign (uint32_t length) { + pos += length; uint16_t* revertPos = pos - 1; uint16_t ch = commentWhitespace(); if (ch == '.') { pos++; ch = commentWhitespace(); if (ch == 'e' && str_eq6(pos + 1, 'x', 'p', 'o', 'r', 't', 's')) { - tryParseExportsDotAssign(true); + tryParseExportsDotAssign(true, 7); return; } } pos = revertPos; } -void tryParseExportsDotAssign (bool assign) { - pos += 7; +void tryParseExportsDotAssign (bool assign, uint32_t length) { + pos += length; uint16_t* revertPos = pos - 1; uint16_t ch = commentWhitespace(); switch (ch) { @@ -1008,6 +1477,23 @@ void tryParseExportsDotAssign (bool assign) { pos++; ch = commentWhitespace(); if (extended) { + if (ch == '_') { + uint16_t* start = pos; + const uint16_t *id, *idEnd; + if (webpackTablePos && readWebpackCall(&id, &idEnd)) { + if (webpackCurrent) addWebpackDependency(id, idEnd, false); + else { webpackEntryStart = id; webpackEntryEnd = idEnd; webpackPublished = true; } + } + pos = start; + if (webpackTablePos && !webpackCurrent && + str_eq18(pos + 1, '_', 'w', 'e', 'b', 'p', 'a', 'c', 'k', '_', 'e', 'x', 'p', 'o', 'r', 't', 's', '_', '_') && + !isIdentifierChar(webpackCharAt(pos + 19))) { + pos += 19; + uint16_t next = commentWhitespace(); + if (pos > end || next == ';' || next == ',' || next == ')' || next == '}') webpackPublished = true; + else webpackInvalid = true; + } + } if (ch == '(') ch = readExportExpressionStart(); if (ch == '{') exportObjectPos = pos; else if (ch == 'r') tryParseRequire(ExportAssign); diff --git a/test/fixtures/webpack/LICENSE b/test/fixtures/webpack/LICENSE new file mode 100644 index 0000000..b862434 --- /dev/null +++ b/test/fixtures/webpack/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. +Copyright Contributors to the Jest project. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/fixtures/webpack/jest-watcher.cjs b/test/fixtures/webpack/jest-watcher.cjs new file mode 100644 index 0000000..6795cc0 --- /dev/null +++ b/test/fixtures/webpack/jest-watcher.cjs @@ -0,0 +1,556 @@ +/*! + * /** + * * Copyright (c) Meta Platforms, Inc. and affiliates. + * * + * * This source code is licensed under the MIT license found in the + * * LICENSE file in the root directory of this source tree. + * * / + */ +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ "./src/BaseWatchPlugin.ts": +/***/ ((__unused_webpack_module, exports) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +class BaseWatchPlugin { + _stdin; + _stdout; + constructor({ + stdin, + stdout + }) { + this._stdin = stdin; + this._stdout = stdout; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + apply(_hooks) {} + getUsageInfo(_globalConfig) { + return null; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + onKey(_key) {} + run(_globalConfig, _updateConfigAndRun) { + return Promise.resolve(); + } +} +var _default = exports["default"] = BaseWatchPlugin; + +/***/ }), + +/***/ "./src/JestHooks.ts": +/***/ ((__unused_webpack_module, exports) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +class JestHooks { + _listeners; + _subscriber; + _emitter; + constructor() { + this._listeners = { + onFileChange: [], + onTestRunComplete: [], + shouldRunTestSuite: [] + }; + this._subscriber = { + onFileChange: fn => { + this._listeners.onFileChange.push(fn); + }, + onTestRunComplete: fn => { + this._listeners.onTestRunComplete.push(fn); + }, + shouldRunTestSuite: fn => { + this._listeners.shouldRunTestSuite.push(fn); + } + }; + this._emitter = { + onFileChange: fs => { + for (const listener of this._listeners.onFileChange) listener(fs); + }, + onTestRunComplete: results => { + for (const listener of this._listeners.onTestRunComplete) listener(results); + }, + shouldRunTestSuite: async testSuiteInfo => { + const result = await Promise.all(this._listeners.shouldRunTestSuite.map(listener => listener(testSuiteInfo))); + return result.every(Boolean); + } + }; + } + isUsed(hook) { + return this._listeners[hook]?.length > 0; + } + getSubscriber() { + return this._subscriber; + } + getEmitter() { + return this._emitter; + } +} +var _default = exports["default"] = JestHooks; + +/***/ }), + +/***/ "./src/PatternPrompt.ts": +/***/ ((__unused_webpack_module, exports) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +function _ansiEscapes() { + const data = _interopRequireDefault(require("ansi-escapes")); + _ansiEscapes = function () { + return data; + }; + return data; +} +function _chalk() { + const data = _interopRequireDefault(require("chalk")); + _chalk = function () { + return data; + }; + return data; +} +function _jestUtil() { + const data = require("jest-util"); + _jestUtil = function () { + return data; + }; + return data; +} +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const { + CLEAR +} = _jestUtil().specialChars; +const usage = entity => `\n${_chalk().default.bold('Pattern Mode Usage')}\n` + ` ${_chalk().default.dim('\u203A Press')} Esc ${_chalk().default.dim('to exit pattern mode.')}\n` + ` ${_chalk().default.dim('\u203A Press')} Enter ` + `${_chalk().default.dim(`to filter by a ${entity} regex pattern.`)}\n` + '\n'; +const usageRows = usage('').split('\n').length; +class PatternPrompt { + _currentUsageRows; + constructor(_pipe, _prompt, _entityName = '') { + this._pipe = _pipe; + this._prompt = _prompt; + this._entityName = _entityName; + this._currentUsageRows = usageRows; + } + run(onSuccess, onCancel, options) { + this._pipe.write(_ansiEscapes().default.cursorHide); + this._pipe.write(CLEAR); + if (typeof options?.header === 'string' && options.header) { + this._pipe.write(`${options.header}\n`); + this._currentUsageRows = usageRows + options.header.split('\n').length; + } else { + this._currentUsageRows = usageRows; + } + this._pipe.write(usage(this._entityName)); + this._pipe.write(_ansiEscapes().default.cursorShow); + this._prompt.enter(this._onChange.bind(this), onSuccess, onCancel); + } + _onChange(_pattern, _options) { + this._pipe.write(_ansiEscapes().default.eraseLine); + this._pipe.write(_ansiEscapes().default.cursorLeft); + } +} +exports["default"] = PatternPrompt; + +/***/ }), + +/***/ "./src/TestWatcher.ts": +/***/ ((__unused_webpack_module, exports) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +function _emittery() { + const data = _interopRequireDefault(require("emittery")); + _emittery = function () { + return data; + }; + return data; +} +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +class TestWatcher extends _emittery().default { + state; + _isWatchMode; + constructor({ + isWatchMode + }) { + super(); + this.state = { + interrupted: false + }; + this._isWatchMode = isWatchMode; + } + async setState(state) { + Object.assign(this.state, state); + await this.emit('change', this.state); + } + isInterrupted() { + return this.state.interrupted; + } + isWatchMode() { + return this._isWatchMode; + } +} +exports["default"] = TestWatcher; + +/***/ }), + +/***/ "./src/constants.ts": +/***/ ((__unused_webpack_module, exports) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.KEYS = void 0; +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const isWindows = process.platform === 'win32'; +const KEYS = exports.KEYS = { + ARROW_DOWN: '\u001B[B', + ARROW_LEFT: '\u001B[D', + ARROW_RIGHT: '\u001B[C', + ARROW_UP: '\u001B[A', + BACKSPACE: Buffer.from(isWindows ? '08' : '7f', 'hex').toString(), + CONTROL_C: '\u0003', + CONTROL_D: '\u0004', + CONTROL_U: '\u0015', + ENTER: '\r', + ESCAPE: '\u001B' +}; + +/***/ }), + +/***/ "./src/index.ts": +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +var _exportNames = { + BaseWatchPlugin: true, + JestHook: true, + PatternPrompt: true, + TestWatcher: true, + Prompt: true +}; +Object.defineProperty(exports, "BaseWatchPlugin", ({ + enumerable: true, + get: function () { + return _BaseWatchPlugin.default; + } +})); +Object.defineProperty(exports, "JestHook", ({ + enumerable: true, + get: function () { + return _JestHooks.default; + } +})); +Object.defineProperty(exports, "PatternPrompt", ({ + enumerable: true, + get: function () { + return _PatternPrompt.default; + } +})); +Object.defineProperty(exports, "Prompt", ({ + enumerable: true, + get: function () { + return _Prompt.default; + } +})); +Object.defineProperty(exports, "TestWatcher", ({ + enumerable: true, + get: function () { + return _TestWatcher.default; + } +})); +var _BaseWatchPlugin = _interopRequireDefault(__webpack_require__("./src/BaseWatchPlugin.ts")); +var _JestHooks = _interopRequireDefault(__webpack_require__("./src/JestHooks.ts")); +var _PatternPrompt = _interopRequireDefault(__webpack_require__("./src/PatternPrompt.ts")); +var _TestWatcher = _interopRequireDefault(__webpack_require__("./src/TestWatcher.ts")); +var _constants = __webpack_require__("./src/constants.ts"); +Object.keys(_constants).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _constants[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _constants[key]; + } + }); +}); +var _Prompt = _interopRequireDefault(__webpack_require__("./src/lib/Prompt.ts")); +var _patternModeHelpers = __webpack_require__("./src/lib/patternModeHelpers.ts"); +Object.keys(_patternModeHelpers).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _patternModeHelpers[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _patternModeHelpers[key]; + } + }); +}); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } + +/***/ }), + +/***/ "./src/lib/Prompt.ts": +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _constants = __webpack_require__("./src/constants.ts"); +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +class Prompt { + _entering; + _value; + _onChange; + _onSuccess; + _onCancel; + _offset; + _promptLength; + _selection; + constructor() { + // Copied from `enter` to satisfy TS + this._entering = true; + this._value = ''; + this._selection = null; + this._offset = -1; + this._promptLength = 0; + + /* eslint-disable @typescript-eslint/no-empty-function */ + this._onChange = () => {}; + this._onSuccess = () => {}; + this._onCancel = () => {}; + /* eslint-enable */ + } + _onResize = () => { + this._onChange(); + }; + enter(onChange, onSuccess, onCancel) { + this._entering = true; + this._value = ''; + this._onSuccess = onSuccess; + this._onCancel = onCancel; + this._selection = null; + this._offset = -1; + this._promptLength = 0; + this._onChange = () => onChange(this._value, { + max: 10, + offset: this._offset + }); + this._onChange(); + process.stdout.on('resize', this._onResize); + } + setPromptLength(length) { + this._promptLength = length; + } + setPromptSelection(selected) { + this._selection = selected; + } + put(key) { + switch (key) { + case _constants.KEYS.ENTER: + this._entering = false; + this._onSuccess(this._selection ?? this._value); + this.abort(); + break; + case _constants.KEYS.ESCAPE: + this._entering = false; + this._onCancel(this._value); + this.abort(); + break; + case _constants.KEYS.ARROW_DOWN: + this._offset = Math.min(this._offset + 1, this._promptLength - 1); + this._onChange(); + break; + case _constants.KEYS.ARROW_UP: + this._offset = Math.max(this._offset - 1, -1); + this._onChange(); + break; + case _constants.KEYS.ARROW_LEFT: + case _constants.KEYS.ARROW_RIGHT: + break; + case _constants.KEYS.CONTROL_U: + this._value = ''; + this._offset = -1; + this._selection = null; + this._onChange(); + break; + default: + this._value = key === _constants.KEYS.BACKSPACE ? this._value.slice(0, -1) : this._value + key; + this._offset = -1; + this._selection = null; + this._onChange(); + break; + } + } + abort() { + this._entering = false; + this._value = ''; + process.stdout.removeListener('resize', this._onResize); + } + isEntering() { + return this._entering; + } +} +exports["default"] = Prompt; + +/***/ }), + +/***/ "./src/lib/patternModeHelpers.ts": +/***/ ((__unused_webpack_module, exports) => { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.printPatternCaret = printPatternCaret; +exports.printRestoredPatternCaret = printRestoredPatternCaret; +function _ansiEscapes() { + const data = _interopRequireDefault(require("ansi-escapes")); + _ansiEscapes = function () { + return data; + }; + return data; +} +function _chalk() { + const data = _interopRequireDefault(require("chalk")); + _chalk = function () { + return data; + }; + return data; +} +function _stringLength() { + const data = _interopRequireDefault(require("string-length")); + _stringLength = function () { + return data; + }; + return data; +} +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +function printPatternCaret(pattern, pipe) { + const inputText = `${_chalk().default.dim(' pattern \u203A')} ${pattern}`; + pipe.write(_ansiEscapes().default.eraseDown); + pipe.write(inputText); + pipe.write(_ansiEscapes().default.cursorSavePosition); +} +function printRestoredPatternCaret(pattern, currentUsageRows, pipe) { + const inputText = `${_chalk().default.dim(' pattern \u203A')} ${pattern}`; + pipe.write(_ansiEscapes().default.cursorTo((0, _stringLength().default)(inputText), currentUsageRows - 1)); + pipe.write(_ansiEscapes().default.cursorRestorePosition); +} + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ var __webpack_exports__ = __webpack_require__("./src/index.ts"); +/******/ module.exports = __webpack_exports__; +/******/ +/******/ })() +; \ No newline at end of file diff --git a/test/webpack.js b/test/webpack.js new file mode 100644 index 0000000..9027115 --- /dev/null +++ b/test/webpack.js @@ -0,0 +1,302 @@ +const assert = require('node:assert/strict'); +const { readFileSync } = require('node:fs'); +const { join } = require('node:path'); +const vm = require('node:vm'); + +let parse; +const options = { mode: 'extended' }; + +/** @param {string} modules */ +function bundle (modules) { + return `(() => { + const __webpack_modules__ = { ${modules} }; + const cache = {}; + function __webpack_require__(id) { + if (cache[id]) return cache[id].exports; + const module = cache[id] = { exports: {} }; + __webpack_modules__[id](module, module.exports, __webpack_require__); + return module.exports; + } + const __webpack_exports__ = __webpack_require__('entry'); + module.exports = __webpack_exports__; + })();`; +} + +/** @param {string} id */ +function star (id) { + return `var dep = __webpack_require__(${JSON.stringify(id)}); + Object.keys(dep).forEach(function (key) { + if (key === 'default' || key === '__esModule') return; + exports[key] = dep[key]; + });`; +} + +suite('Webpack extended detection', () => { + suiteSetup(async () => { + if (process.env.WASM) { + const lexer = await import('../dist/lexer.mjs'); + await lexer.init(); + parse = lexer.parse; + } else if (process.env.WASM_SYNC) { + parse = require('../dist/lexer.js').parse; + } else { + parse = require('../lexer.js').parse; + } + }); + + test('published jest-watcher@30.2.0 bundle excludes internal default exports', () => { + const source = readFileSync(join(__dirname, 'fixtures/webpack/jest-watcher.cjs'), 'utf8'); + const result = parse(source, 'jest-watcher.cjs', options); + assert.ok(!result.exports.includes('default')); + assert.deepEqual(result.exports.sort(), [ + 'BaseWatchPlugin', 'JestHook', 'KEYS', 'PatternPrompt', 'Prompt', 'TestWatcher', + '__esModule', 'printPatternCaret', 'printRestoredPatternCaret' + ].sort()); + assert.deepEqual(result.reexports, []); + assert.deepEqual(parse(source).exports, [ + 'default', 'KEYS', 'printPatternCaret', 'printRestoredPatternCaret' + ]); + }); + + test('entry exports exclude unrelated factories', () => { + const source = bundle(` + unused: (module, exports) => { exports.hidden = 1; }, + entry: (module, exports) => { exports.visible = 2; } + `); + const module = { exports: {} }; + vm.runInNewContext(source, { module }); + const result = parse(source, 'bundle.cjs', options); + assert.ok(!result.exports.includes('hidden')); + assert.deepEqual(result.exports, Object.keys(module.exports)); + assert.deepEqual(result.reexports, []); + }); + + const factories = [ + 'entry: (module, exports) => { exports.visible = 1; }', + 'entry: function(module, exports) { exports.visible = 1; }', + 'entry: (m, e) => { e.visible = 1; }', + 'entry: (m) => { m.exports = { visible: 1 }; }', + 'entry: () => {}' + ]; + for (const factory of factories) { + test(`factory ${factory}`, () => { + const source = bundle(`unused: (m, e) => { e.hidden = 1; }, ${factory}`); + const module = { exports: {} }; + vm.runInNewContext(source, { module }); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports, Object.keys(module.exports)); + }); + } + + test('Webpack getter maps use the factory export binding', () => { + const source = bundle(` + unused: (m, __webpack_exports__) => { + __webpack_require__.d(__webpack_exports__, { hidden: () => 0 }); + }, + entry: (m, __webpack_exports__) => { + __webpack_require__.d(__webpack_exports__, { visible: () => 1, ['quoted']: () => 2 }); + } + `); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible', 'quoted'], reexports: [] }); + }); + + test('descriptors can target the module parameter', () => { + const source = bundle(`entry: (m, e) => { + Object.defineProperties(m.exports, { visible: { value: 1 } }); + }`); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + + test('inlined entries exclude unrelated factories', () => { + const source = bundle(`entry: (m, exports) => { exports.hidden = 1; }`) + .replace("const __webpack_exports__ = __webpack_require__('entry');", + 'const __webpack_exports__ = {}; __webpack_exports__.visible = 1;'); + const module = { exports: {} }; + vm.runInNewContext(source, { module }); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports, Object.keys(module.exports)); + }); + + test('inlined entry star reexports follow internal dependencies', () => { + const source = bundle(` + unused: (m, exports) => { exports.hidden = 1; }, + dependency: (m, exports) => { + exports.default = 1; exports.__esModule = true; exports.visible = 2; + } + `).replace("const __webpack_exports__ = __webpack_require__('entry');", ` + const __webpack_exports__ = {}; + (() => { var exports = __webpack_exports__; ${star('dependency')} })();`); + const module = { exports: {} }; + vm.runInNewContext(source, { module }); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports, Object.keys(module.exports)); + }); + + test('unknown inlined entry star reexports retain candidates', () => { + const source = bundle(` + unused: (m, exports) => { exports.hidden = 1; }, + dependency: (m, exports) => { exports.visible = 1; } + `).replace("const __webpack_exports__ = __webpack_require__('entry');", ` + const __webpack_exports__ = {}; + (() => { var exports = __webpack_exports__; ${star('unknown')} })();`); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports.sort(), ['hidden', 'visible']); + }); + + test('inlined entries ignore non-reexported internal dependencies', () => { + const source = bundle('dependency: (m, exports) => { exports.hidden = 1; }') + .replace("const __webpack_exports__ = __webpack_require__('entry');", ` + const __webpack_exports__ = {}; + __webpack_require__('dependency'); + __webpack_exports__.visible = 1;`); + const module = { exports: {} }; + vm.runInNewContext(source, { module }); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports, Object.keys(module.exports)); + }); + + test('internal whole-module reexports retain default', () => { + const source = bundle(` + dependency: (m, e) => { e.default = 1; e.visible = 2; }, + entry: (m, e) => { m.exports = __webpack_require__('dependency'); } + `); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['default', 'visible'], reexports: [] }); + }); + + test('internal star reexports exclude default and __esModule', () => { + const source = bundle(` + unused: (m, exports) => { exports.hidden = 1; }, + dependency: (m, exports) => { + exports.default = 1; exports.__esModule = true; exports.visible = 2; + }, + entry: (m, exports) => { exports.own = 1; ${star('dependency')} } + `); + const module = { exports: {} }; + vm.runInNewContext(source, { module }); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports, Object.keys(module.exports)); + }); + + test('star reexports terminate on cycles', () => { + const source = bundle(` + dependency: (m, exports) => { exports.second = 2; ${star('entry')} }, + entry: (m, exports) => { exports.first = 1; ${star('dependency')} } + `); + const module = { exports: {} }; + vm.runInNewContext(source, { module }); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports, Object.keys(module.exports)); + }); + + test('whole-module reexports upgrade a prior star visit', () => { + const source = bundle(` + leaf: (m, exports) => { exports.default = 1; exports.visible = 2; }, + middle: (module, exports) => { module.exports = __webpack_require__('leaf'); }, + entry: (module, exports) => { ${star('leaf')} module.exports = __webpack_require__('middle'); } + `); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports.sort(), ['default', 'visible']); + }); + + test('external reexports belong to the selected entry', () => { + const source = bundle(` + unused: (module) => { module.exports = require('hidden'); }, + entry: (module) => { module.exports = require('visible'); } + `); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: [], reexports: ['visible'] }); + }); + + test('numeric module identifiers', () => { + const source = bundle(` + 0: (m, e) => { e.hidden = 1; }, + 1: (m, e) => { e.visible = 1; } + `).replace("__webpack_require__('entry')", '__webpack_require__(1)'); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + + test('quoted and escaped module identifiers', () => { + const source = bundle(` + unused: (m, e) => { e.hidden = 1; }, + 'entr\\u0079': (m, e) => { e.visible = 1; } + `); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + + for (const id of ['', '__proto__', '\u{1f4a9}', 'a"b', 'a\\b']) { + test(`module identifier ${JSON.stringify(id)}`, () => { + const source = bundle(` + unused: (m, exports) => { exports.hidden = 1; }, + ${JSON.stringify(id)}: (m, exports) => { exports.visible = 1; } + `).replace("__webpack_require__('entry')", `__webpack_require__(${JSON.stringify(id)})`); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + } + + test('module maps grow without losing factories', () => { + const factories = []; + for (let i = 0; i < 200; i++) factories.push(`${i}: (m, exports) => { exports.hidden = 1; }`); + factories.push('entry: (m, exports) => { exports.visible = 1; }'); + assert.deepEqual(parse(bundle(factories.join(',')), 'bundle.cjs', options), + { exports: ['visible'], reexports: [] }); + }); + + test('direct startup reexport', () => { + const source = bundle(` + unused: (m, e) => { e.hidden = 1; }, + entry: (m, e) => { e.visible = 1; } + `).replace("const __webpack_exports__ = __webpack_require__('entry');\n module.exports = __webpack_exports__;", + "module.exports = __webpack_require__('entry');"); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + + test('unknown entries retain candidate detection', () => { + const source = bundle('entry: (m, exports) => { exports.visible = 1; }') + .replace("__webpack_require__('entry');\n module.exports", "__webpack_require__('unknown');\n module.exports"); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + + for (const suffix of ['.nested', ' + other', '[key]']) { + test(`modified startup expression ${suffix} retains candidates`, () => { + const source = bundle(`unused: (m, exports) => { exports.hidden = 1; }, + entry: (m, exports) => { exports.visible = 1; }`) + .replace("__webpack_require__('entry');", `__webpack_require__('entry')${suffix};`); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports.sort(), ['hidden', 'visible']); + }); + test(`modified published value ${suffix} retains candidates`, () => { + const source = bundle(`unused: (m, exports) => { exports.hidden = 1; }, + entry: (m, exports) => { exports.visible = 1; }`) + .replace('module.exports = __webpack_exports__;', `module.exports = __webpack_exports__${suffix};`); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports.sort(), ['hidden', 'visible']); + }); + } + + test('unknown internal reexports retain candidate detection', () => { + const source = bundle(` + unused: (m, exports) => { exports.hidden = 1; }, + entry: (module, exports) => { exports.visible = 1; module.exports = __webpack_require__('unknown'); } + `); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports.sort(), ['hidden', 'visible']); + }); + + test('unpublished tables retain candidate detection', () => { + const source = bundle('entry: (m, exports) => { exports.visible = 1; }') + .replace('module.exports = __webpack_exports__;', ''); + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + + test('duplicate module identifiers retain candidate detection', () => { + const source = bundle(`entry: (m, exports) => { exports.first = 1; }, + entry: (m, exports) => { exports.second = 2; }`); + assert.deepEqual(parse(source, 'bundle.cjs', options).exports.sort(), ['first', 'second']); + }); + + test('comments, strings, and templates cannot create a module table', () => { + const source = ` + // __webpack_modules__ = { entry: (m, exports) => { exports.fake = 1; } }; + const text = '__webpack_modules__ = {'; + const template = \`__webpack_modules__ = {\`; + exports.visible = 1; + `; + assert.deepEqual(parse(source, 'bundle.cjs', options), { exports: ['visible'], reexports: [] }); + }); + + test('bundle detection resets after a scanner error', () => { + const source = 'var __webpack_modules__ = { entry: (m, e) => { e.hidden = 1;'; + assert.throws(() => parse(source, 'broken.cjs', options), /broken.cjs/); + assert.deepEqual(parse('exports.visible = 1;'), { exports: ['visible'], reexports: [] }); + assert.deepEqual(parse(bundle('entry: (m, e) => { e.visible = 1; }'), 'bundle.cjs', options), + { exports: ['visible'], reexports: [] }); + }); +});