From d812ad1fa93b294d0225469115538350aa3637c8 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 25 May 2024 20:14:08 +0800 Subject: [PATCH] `prefer-includes`: Check `.lastIndexOf()` (#2368) --- docs/rules/prefer-includes.md | 60 +++--- readme.md | 2 +- rules/prefer-includes.js | 11 +- test/prefer-includes.mjs | 24 +-- test/snapshots/prefer-includes.mjs.md | 251 +++++++++++++++++++++++- test/snapshots/prefer-includes.mjs.snap | Bin 1588 -> 1888 bytes 6 files changed, 294 insertions(+), 54 deletions(-) diff --git a/docs/rules/prefer-includes.md b/docs/rules/prefer-includes.md index f23d27aec3..4c8f48d323 100644 --- a/docs/rules/prefer-includes.md +++ b/docs/rules/prefer-includes.md @@ -1,4 +1,4 @@ -# Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence +# Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). @@ -7,7 +7,7 @@ -All built-ins have `.includes()` in addition to `.indexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()`. +All built-ins have `.includes()` in addition to `.indexOf()` and `.lastIndexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()` and `.lastIndexOf()`. [`Array#some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) is intended for more complex needs. If you are just looking for the index where the given item is present, the code can be simplified to use [`Array#includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). This applies to any search with a literal, a variable, or any expression that doesn't have any explicit side effects. However, if the expression you are looking for relies on an item related to the function (its arguments, the function self, etc.), the case is still valid. @@ -16,99 +16,103 @@ This rule is fixable, unless the search expression in `Array#some()` has side ef ## Fail ```js -[].indexOf('foo') !== -1; +array.indexOf('foo') !== -1; ``` ```js -x.indexOf('foo') != -1; +array.indexOf('foo') !== -1; ``` ```js -str.indexOf('foo') > -1; +string.lastIndexOf('foo') !== -1; ``` ```js -'foobar'.indexOf('foo') >= 0; +array.lastIndexOf('foo') !== -1; ``` ```js -x.indexOf('foo') === -1 +foo.indexOf('foo') != -1; ``` ```js -const isFound = foo.some(x => x === 'foo'); +foo.indexOf('foo') >= 0; ``` ```js -const isFound = foo.some(x => 'foo' === x); +foo.indexOf('foo') > -1; ``` ```js -const isFound = foo.some(x => { - return x === 'foo'; -}); +foo.indexOf('foo') === -1 ``` -## Pass +```js +foo.some(x => x === 'foo'); +``` ```js -const str = 'foobar'; +foo.some(x => 'foo' === x); ``` ```js -str.indexOf('foo') !== -n; +foo.some(x => { + return x === 'foo'; +}); ``` +## Pass + ```js -str.indexOf('foo') !== 1; +foo.indexOf('foo') !== -n; ``` ```js -!str.indexOf('foo') === 1; +foo.indexOf('foo') !== 1; ``` ```js -!str.indexOf('foo') === -n; +foo.indexOf('foo') === 1; ``` ```js -str.includes('foo'); +foo.includes('foo'); ``` ```js -[1,2,3].includes(4); +foo.includes(4); ``` ```js -const isFound = foo.includes('foo'); +foo.includes('foo'); ``` ```js -const isFound = foo.some(x => x == undefined); +foo.some(x => x == undefined); ``` ```js -const isFound = foo.some(x => x !== 'foo'); +foo.some(x => x !== 'foo'); ``` ```js -const isFound = foo.some((x, index) => x === index); +foo.some((x, index) => x === index); ``` ```js -const isFound = foo.some(x => (x === 'foo') && isValid()); +foo.some(x => (x === 'foo') && isValid()); ``` ```js -const isFound = foo.some(x => y === 'foo'); +foo.some(x => y === 'foo'); ``` ```js -const isFound = foo.some(x => y.x === 'foo'); +foo.some(x => y.x === 'foo'); ``` ```js -const isFound = foo.some(x => { +foo.some(x => { const bar = getBar(); return x === bar; }); diff --git a/readme.md b/readme.md index 163a4b7572..9d715b4905 100644 --- a/readme.md +++ b/readme.md @@ -188,7 +188,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | βœ… | | πŸ’‘ | | [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | βœ… | | | | [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | βœ… | πŸ”§ | πŸ’‘ | -| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ | +| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ | | [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | πŸ”§ | | | [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | βœ… | πŸ”§ | | | [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. | βœ… | | πŸ’‘ | diff --git a/rules/prefer-includes.js b/rules/prefer-includes.js index 0cfd14a6c2..e9e878beb0 100644 --- a/rules/prefer-includes.js +++ b/rules/prefer-includes.js @@ -5,9 +5,9 @@ const {isLiteral} = require('./ast/index.js'); const MESSAGE_ID = 'prefer-includes'; const messages = { - [MESSAGE_ID]: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.', + [MESSAGE_ID]: 'Use `.includes()`, rather than `.{{method}}()`, when checking for existence.', }; -// Ignore {_,lodash,underscore}.indexOf +// Ignore `{_,lodash,underscore}.{indexOf,lastIndexOf}` const ignoredVariables = new Set(['_', 'lodash', 'underscore']); const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name); const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1; @@ -30,6 +30,9 @@ const getProblem = (context, node, target, argumentsNodes) => { return { node: memberExpressionNode.property, messageId: MESSAGE_ID, + data: { + method: node.left.callee.property.name, + }, fix(fixer) { const replacement = `${isNegativeResult(node) ? '!' : ''}${targetSource}.includes(${argumentsSource.join(', ')})`; return fixer.replaceText(node, replacement); @@ -49,7 +52,7 @@ const create = context => { context.on('BinaryExpression', node => { const {left, right, operator} = node; - if (!isMethodNamed(left, 'indexOf')) { + if (!isMethodNamed(left, 'indexOf') && !isMethodNamed(left, 'lastIndexOf')) { return; } @@ -86,7 +89,7 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence.', + description: 'Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence.', recommended: true, }, fixable: 'code', diff --git a/test/prefer-includes.mjs b/test/prefer-includes.mjs index 0ea72fd7fe..963d87149e 100644 --- a/test/prefer-includes.mjs +++ b/test/prefer-includes.mjs @@ -5,20 +5,22 @@ const {test} = getTester(import.meta); test.snapshot({ valid: [ - 'str.indexOf(\'foo\') !== -n', - 'str.indexOf(\'foo\') !== 1', - 'str.indexOf(\'foo\') === -2', - '!str.indexOf(\'foo\') === 1', - '!str.indexOf(\'foo\') === -n', + ...[ + 'str.indexOf(\'foo\') !== -n', + 'str.indexOf(\'foo\') !== 1', + 'str.indexOf(\'foo\') === -2', + '!str.indexOf(\'foo\') === 1', + '!str.indexOf(\'foo\') === -n', + 'null.indexOf(\'foo\') !== 1', + 'something.indexOf(foo, 0, another) !== -1', + '_.indexOf(foo, bar) !== -1', + 'lodash.indexOf(foo, bar) !== -1', + 'underscore.indexOf(foo, bar) !== -1', + ].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]), 'str.includes(\'foo\')', '\'foobar\'.includes(\'foo\')', '[1,2,3].includes(4)', - 'null.indexOf(\'foo\') !== 1', 'f(0) < 0', - 'something.indexOf(foo, 0, another) !== -1', - '_.indexOf(foo, bar) !== -1', - 'lodash.indexOf(foo, bar) !== -1', - 'underscore.indexOf(foo, bar) !== -1', ], invalid: [ '\'foobar\'.indexOf(\'foo\') !== -1', @@ -32,7 +34,7 @@ test.snapshot({ '(a || b).indexOf(\'foo\') === -1', 'foo.indexOf(bar, 0) !== -1', 'foo.indexOf(bar, 1) !== -1', - ], + ].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]), }); const {snapshot, typescript} = tests({ diff --git a/test/snapshots/prefer-includes.mjs.md b/test/snapshots/prefer-includes.mjs.md index 92011629b7..97207ce93e 100644 --- a/test/snapshots/prefer-includes.mjs.md +++ b/test/snapshots/prefer-includes.mjs.md @@ -25,7 +25,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(2): str.indexOf('foo') != -1 +## invalid(2): 'foobar'.lastIndexOf('foo') !== -1 + +> Input + + `␊ + 1 | 'foobar'.lastIndexOf('foo') !== -1␊ + ` + +> Output + + `␊ + 1 | 'foobar'.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | 'foobar'.lastIndexOf('foo') !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(3): str.indexOf('foo') != -1 > Input @@ -46,7 +67,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(3): str.indexOf('foo') > -1 +## invalid(4): str.lastIndexOf('foo') != -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') != -1␊ + ` + +> Output + + `␊ + 1 | str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') != -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(5): str.indexOf('foo') > -1 > Input @@ -67,7 +109,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(4): str.indexOf('foo') == -1 +## invalid(6): str.lastIndexOf('foo') > -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') > -1␊ + ` + +> Output + + `␊ + 1 | str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') > -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(7): str.indexOf('foo') == -1 > Input @@ -88,7 +151,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(5): 'foobar'.indexOf('foo') >= 0 +## invalid(8): str.lastIndexOf('foo') == -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') == -1␊ + ` + +> Output + + `␊ + 1 | !str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') == -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(9): 'foobar'.indexOf('foo') >= 0 > Input @@ -109,7 +193,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(6): [1,2,3].indexOf(4) !== -1 +## invalid(10): 'foobar'.lastIndexOf('foo') >= 0 + +> Input + + `␊ + 1 | 'foobar'.lastIndexOf('foo') >= 0␊ + ` + +> Output + + `␊ + 1 | 'foobar'.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | 'foobar'.lastIndexOf('foo') >= 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(11): [1,2,3].indexOf(4) !== -1 > Input @@ -130,7 +235,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(7): str.indexOf('foo') < 0 +## invalid(12): [1,2,3].lastIndexOf(4) !== -1 + +> Input + + `␊ + 1 | [1,2,3].lastIndexOf(4) !== -1␊ + ` + +> Output + + `␊ + 1 | [1,2,3].includes(4)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [1,2,3].lastIndexOf(4) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(13): str.indexOf('foo') < 0 > Input @@ -151,7 +277,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(8): ''.indexOf('foo') < 0 +## invalid(14): str.lastIndexOf('foo') < 0 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') < 0␊ + ` + +> Output + + `␊ + 1 | !str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') < 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(15): ''.indexOf('foo') < 0 > Input @@ -172,7 +319,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(9): (a || b).indexOf('foo') === -1 +## invalid(16): ''.lastIndexOf('foo') < 0 + +> Input + + `␊ + 1 | ''.lastIndexOf('foo') < 0␊ + ` + +> Output + + `␊ + 1 | !''.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | ''.lastIndexOf('foo') < 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(17): (a || b).indexOf('foo') === -1 > Input @@ -193,7 +361,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(10): foo.indexOf(bar, 0) !== -1 +## invalid(18): (a || b).lastIndexOf('foo') === -1 + +> Input + + `␊ + 1 | (a || b).lastIndexOf('foo') === -1␊ + ` + +> Output + + `␊ + 1 | !(a || b).includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | (a || b).lastIndexOf('foo') === -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(19): foo.indexOf(bar, 0) !== -1 > Input @@ -214,7 +403,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(11): foo.indexOf(bar, 1) !== -1 +## invalid(20): foo.lastIndexOf(bar, 0) !== -1 + +> Input + + `␊ + 1 | foo.lastIndexOf(bar, 0) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.includes(bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.lastIndexOf(bar, 0) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(21): foo.indexOf(bar, 1) !== -1 > Input @@ -235,6 +445,27 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` +## invalid(22): foo.lastIndexOf(bar, 1) !== -1 + +> Input + + `␊ + 1 | foo.lastIndexOf(bar, 1) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.includes(bar, 1)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.lastIndexOf(bar, 1) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + ## invalid(1): values.some(x => x === "foo") > Input diff --git a/test/snapshots/prefer-includes.mjs.snap b/test/snapshots/prefer-includes.mjs.snap index dd84e89630d7c91ac1afa011e0da3df6927367c1..b5991d96583a7b8f3fe6296f51a7af0199c83684 100644 GIT binary patch literal 1888 zcmV-m2cP&sRzVzI(lQ>Pb%${Or1lAxi#ux(x)A%(Bq0uKKo_Ipy58#3Sfd2;FKG$~A*lC^| zM?Ux5&-d$d&vpD;quMrWH?5zS4|Ka_xAk78W!%x)t$JEjwqT`UnpLf{l4}`ty?3#Z zW+zq@SSpucRc%>@cH3&2w)NRTy{_5X<+bM$&nMU?c`Wfl@?;7?h5djiDZQ;#^|pS& zxZSmRoE(Y+-Z-^(x2{_}5RcHf*tJI?eiTD2e33|1!sG$~un+F%gW(6>w{+MY4Xf;C zp`+PNy#sbrGX_5mqp@E;Z0ZKontJVK%h-d4*#W)RvTWU`={cTi>wKqUc0kRm4)&ab zy(nUDYnFW>9QsX!EHjQw8Yv?D2MGTY4}N(*v;^T~5Ti~l#R*4>lClMs-SJw56o7^% zyPm?Xpa?Ic2*Jc3p{w^faOwCX%<-8#?T}sLWJwp0bc<+qCj>UK7_%q*6Y})Kgn(F@ zga>nsMn3M~K200BLXuuZj9!=fxDpBEFVWR^ZRY846)}`^AW;tNR`?|2(Lv9%M4NmU`9Q$rJ*5YMS~w;*}23QAbGr86y8U zOh4^IKStZ1bX8y*%4=%2m@Qp*#a)wcXz~c!5{D)Y5Pqo~h2S$^qmh4ykbmkRmuG-k z$7l4>mc1w-7S;2?TtbZA7-K{RnLX-X5bAHnQPWwFxaZMctCEf44pmjM2-Iux)?|mS zC5K!!AEL`YmNzClsCdkwyH%`JZxK;20y1M5qg5=;X+4#IWTI<$_K*L&>UhAX*W)JoWGVcAv zh-{E=dG;f&rHd+MY4y;SR{dDh zt=~9n7UVFlR*#6Qi!xXHP@cfmQIej-*gm*pG`1rCr+!CkcXcagnYZ+G56Ttj4c3(9 zhG{M_{y@ChyhxZ$$Ht1lNNlm;xxW?awI96hH~~VUcB<0$5|mbkZq+)9lwpo_q>meeiwS zNu&TaSb))7D06upTueI~#|0r~6fRBV;Va1(QKH9^;S;R{U}vsgjKJ5i3F^IdE{m^N zu*PoeCfD2gE#0scxC{JV=Vu0^SGA6(BT7bFX3UR}F>fS=cG_=vv74Tm;iku$TAlGHb*a0zr(1)Wt9f+` z-qUZlwVEzYr)4+sv~!ZZ43WGmhwJ_#x5Ix(kbe`Q{7{*K9V#4SpP9;v{l_O<%ojZ` zzi!C)N31#ydjz6)p`R-TN22aV`X#OLcEuY6_biW*i1(zeq&KCw+mwR%9*~%yBtZiI z7DARK@XKOFCxGN1jZjI&Ex|eQ)gbrwZWw8{z`6Imkge9)>#`f%%C&W4&u%Kv_o{Lg zaeUh;BRITMbVH5e+{%<{m(XbEgGX~=1`UIC{S)bWkdO>inQU5aDV%-X3*NpB-cJf6 zyWMPA*VOBz+V3L%=O$O%3GOHITWFA-2}G7e$J+jmw0$~-w!$#;kTio{c9InPhH0k7 zDtd34Cw;>pghp4AsD3UiC;gWm9w@4kceMJl@kInt^vd}Ry;$XKz_h{B20)J+g!W7m ze$)ncfPxFqHT635m0V40w+BC8tlrRTb`~_lWY3`AVn{lr!_o2H*Z%{ANRwagH~;|RAh!_! literal 1588 zcmV-42Fv+DRzV1ER<(rQ&C5Tb$= z(srw&I?1H*ZfaMyvjmzTv6MR!AP|Tb1bsr{i6!~j(2j!}t`mR2P#^15--c9>2E z=|E7Y)@s*@&4idjW4G-LB!1#0mOhWfDq(B^0O*4Md$0e4cWnZPgJqS&G+3I`Bo;VL z&2V4!)##h|o5X-xlhhvS#t}433rI(|9b(i-hMB7GSe9vlnpHjQH$3clr3AKP1?>|8 z7lV?`7ok>=!t+RBJms&@>@I^WU4MXgeq^tEWD8=l6(QJF3q6y;HwtppSvvL6la{I0^!TVK!Q146jS~VQU2JY zEVlq7o*&^AkMM@LT5OAwIgQx7B(D|Q;xxWwrT79(|4?2jwo&fR4{F?lx=HIqU{48| zzKWcB(c9Xh$>GyAo(NYIK0~uRUQG}#?gO14PrvoOrIaA4LAMK4CHPsmNb5UVMMPL! zQ{tS^#J%qrV-{9^6aJASr<9;!n!aRJv(k{07vLO%wZdS@6-2-jNaH!A!F;Z2mKgLm zXzoXh^N95qQ6>g>RS{nG(seai(H6vUc}io$~CVuIcHvEk(`ko^X6ROkIVtnPA%fJErU;mt)75*8F`wO5f{tN zguUdu7}lhDhhd99V)I5e+D6UMO#_l0C3HA;5WNjSI6}zIBrLz8uzV{DOBmDU9iB7( zn4Zmo3dvlWS*_3qdB|i_P=HjHt^{lhNjfUhQHPG!=%`$w!*&1RhWoHwrsGXI?$?8C z3U1MV3LU9ANM(a@{s+pNw!;}qAQM$5jq5+9YqEGP zhhLh-v!`r{a=3|1xIYCG{LqWm9!ks)P+~raaxL_z<@qjpY6&+v*4XNlf3dsmqa$Lw zo~v241n-cOmR2LYJG#@vZf7LN7lHA9IQ;C-vvhcZ0`gxZR6bNDV229F&~K_?#rek^ z&S&%9cF*v)f082wt&aU3LD0Kwq8*ePeMsVzM7vQ%TGu7*N>Bv%EZ?IbK9;tUL6hd) zrsVirLf$@!fdc#`ge)z65yv;ZGJ@s3q8n-zZ!3$Ub{DO-Gk!H6=D1~WT>nCGJ&#Bhs$9%1 zzZ6ct?u|db^{+KUgxzWC_P%-`jP~1z|DB6R+Y=uZ$Bws}zPjb`O7ZJp0FN*5t(n@snr-up6s^nd*zHIm-f+z;V`I{fk zyzm^Nxwj2~t{b@aOygc^?ha6J0oqp&pr>SNTC3&0-mN|$H75<4Vbc5O*O<~<+A*o$ zPJ>NqrcuAmW-HJWB+m}(y m#dmGyXOt_$>uPN?RQZE+@*(LIE=MQ0um1