From 5d90d73b71188fb0f538f5fe9f6862d5bef3dd48 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Mon, 21 Nov 2022 00:17:52 +0800 Subject: [PATCH] `no-useless-spread`: Check cloning inline arrays (#1980) --- docs/rules/no-useless-spread.md | 15 + rules/no-useless-spread.js | 160 ++++++--- test/no-useless-spread.mjs | 64 +++- test/snapshots/no-useless-spread.mjs.md | 376 ++++++++++++++++++++++ test/snapshots/no-useless-spread.mjs.snap | Bin 4449 -> 5471 bytes 5 files changed, 571 insertions(+), 44 deletions(-) diff --git a/docs/rules/no-useless-spread.md b/docs/rules/no-useless-spread.md index d314de2d88..73b1099ad7 100644 --- a/docs/rules/no-useless-spread.md +++ b/docs/rules/no-useless-spread.md @@ -12,6 +12,7 @@ - Spread an array literal as elements of an array literal - Spread an array literal as arguments of a call or a `new` call - Spread an object literal as properties of an object literal + - Use spread syntax to clone an array created inline - The following builtins accept an iterable, so it's unnecessary to convert the iterable to an array: @@ -65,6 +66,14 @@ function * foo() { } ``` +```js +function foo(bar) { + return [ + ...bar.map(x => x * 2), + ]; +} +``` + ## Pass ```js @@ -116,3 +125,9 @@ function * foo() { yield * anotherGenerator(); } ``` + +```js +function foo(bar) { + return bar.map(x => x * 2); +} +``` diff --git a/rules/no-useless-spread.js b/rules/no-useless-spread.js index 570b1dfc3c..ad363b3e96 100644 --- a/rules/no-useless-spread.js +++ b/rules/no-useless-spread.js @@ -6,17 +6,27 @@ const { methodCallSelector, } = require('./selectors/index.js'); const typedArray = require('./shared/typed-array.js'); -const {removeParentheses, fixSpaceAroundKeyword} = require('./fix/index.js'); +const { + removeParentheses, + fixSpaceAroundKeyword, + addParenthesizesToReturnOrThrowExpression, +} = require('./fix/index.js'); +const isOnSameLine = require('./utils/is-on-same-line.js'); +const { + isParenthesized, +} = require('./utils/parentheses.js'); const SPREAD_IN_LIST = 'spread-in-list'; const ITERABLE_TO_ARRAY = 'iterable-to-array'; const ITERABLE_TO_ARRAY_IN_FOR_OF = 'iterable-to-array-in-for-of'; const ITERABLE_TO_ARRAY_IN_YIELD_STAR = 'iterable-to-array-in-yield-star'; +const CLONE_ARRAY = 'clone-array'; const messages = { [SPREAD_IN_LIST]: 'Spread an {{argumentType}} literal in {{parentDescription}} is unnecessary.', [ITERABLE_TO_ARRAY]: '`{{parentDescription}}` accepts iterable as argument, it\'s unnecessary to convert to an array.', [ITERABLE_TO_ARRAY_IN_FOR_OF]: '`for…of` can iterate over iterable, it\'s unnecessary to convert to an array.', [ITERABLE_TO_ARRAY_IN_YIELD_STAR]: '`yield*` can delegate iterable, it\'s unnecessary to convert to an array.', + [CLONE_ARRAY]: 'Unnecessarily cloning an array.', }; const uselessSpreadInListSelector = matches([ @@ -26,7 +36,7 @@ const uselessSpreadInListSelector = matches([ 'NewExpression > SpreadElement.arguments > ArrayExpression.argument', ]); -const iterableToArraySelector = [ +const singleArraySpreadSelector = [ 'ArrayExpression', '[elements.length=1]', '[elements.0.type="SpreadElement"]', @@ -49,12 +59,47 @@ const uselessIterableToArraySelector = matches([ methodCallSelector({object: 'Object', method: 'fromEntries', argumentsLength: 1}), ]), ' > ', - `${iterableToArraySelector}.arguments:first-child`, + `${singleArraySpreadSelector}.arguments:first-child`, ].join(''), - `ForOfStatement > ${iterableToArraySelector}.right`, - `YieldExpression[delegate=true] > ${iterableToArraySelector}.argument`, + `ForOfStatement > ${singleArraySpreadSelector}.right`, + `YieldExpression[delegate=true] > ${singleArraySpreadSelector}.argument`, ]); +const uselessArrayCloneSelector = [ + `${singleArraySpreadSelector} > .elements:first-child > .argument`, + matches([ + // Array methods returns a new array + methodCallSelector([ + 'concat', + 'copyWithin', + 'filter', + 'flat', + 'flatMap', + 'map', + 'slice', + 'splice', + ]), + // `String#split()` + methodCallSelector('split'), + // `Object.keys()` and `Object.values()` + methodCallSelector({object: 'Object', methods: ['keys', 'values'], argumentsLength: 1}), + // `await Promise.all()` and `await Promise.allSettled` + [ + 'AwaitExpression', + methodCallSelector({ + object: 'Promise', + methods: ['all', 'allSettled'], + argumentsLength: 1, + path: 'argument', + }), + ].join(''), + // `Array.from()`, `Array.of()` + methodCallSelector({object: 'Array', methods: ['from', 'of']}), + // `new Array()` + newExpressionSelector('Array'), + ]), +].join(''); + const parentDescriptions = { ArrayExpression: 'array literal', ObjectExpression: 'object literal', @@ -81,6 +126,59 @@ function getCommaTokens(arrayExpression, sourceCode) { }); } +function * unwrapSingleArraySpread(fixer, arrayExpression, sourceCode) { + const [ + openingBracketToken, + spreadToken, + thirdToken, + ] = sourceCode.getFirstTokens(arrayExpression, 3); + + // `[...value]` + // ^ + yield fixer.remove(openingBracketToken); + + // `[...value]` + // ^^^ + yield fixer.remove(spreadToken); + + const [ + commaToken, + closingBracketToken, + ] = sourceCode.getLastTokens(arrayExpression, 2); + + // `[...value]` + // ^ + yield fixer.remove(closingBracketToken); + + // `[...value,]` + // ^ + if (isCommaToken(commaToken)) { + yield fixer.remove(commaToken); + } + + /* + ```js + function foo() { + return [ + ...value, + ]; + } + ``` + */ + const {parent} = arrayExpression; + if ( + (parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement') + && parent.argument === arrayExpression + && !isOnSameLine(openingBracketToken, thirdToken) + && !isParenthesized(arrayExpression, sourceCode) + ) { + yield * addParenthesizesToReturnOrThrowExpression(fixer, parent, sourceCode); + return; + } + + yield * fixSpaceAroundKeyword(fixer, arrayExpression, sourceCode); +} + /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { const sourceCode = context.getSourceCode(); @@ -138,15 +236,15 @@ const create = context => { continue; } - // `call([foo, , bar])` - // ^ Replace holes with `undefined` + // `call(...[foo, , bar])` + // ^ Replace holes with `undefined` yield fixer.insertTextBefore(commaToken, 'undefined'); } }, }; }, - [uselessIterableToArraySelector](array) { - const {parent} = array; + [uselessIterableToArraySelector](arrayExpression) { + const {parent} = arrayExpression; let parentDescription = ''; let messageId = ITERABLE_TO_ARRAY; switch (parent.type) { @@ -173,42 +271,18 @@ const create = context => { } return { - node: array, + node: arrayExpression, messageId, data: {parentDescription}, - * fix(fixer) { - if (parent.type === 'ForOfStatement') { - yield * fixSpaceAroundKeyword(fixer, array, sourceCode); - } - - const [ - openingBracketToken, - spreadToken, - ] = sourceCode.getFirstTokens(array, 2); - - // `[...iterable]` - // ^ - yield fixer.remove(openingBracketToken); - - // `[...iterable]` - // ^^^ - yield fixer.remove(spreadToken); - - const [ - commaToken, - closingBracketToken, - ] = sourceCode.getLastTokens(array, 2); - - // `[...iterable]` - // ^ - yield fixer.remove(closingBracketToken); - - // `[...iterable,]` - // ^ - if (isCommaToken(commaToken)) { - yield fixer.remove(commaToken); - } - }, + fix: fixer => unwrapSingleArraySpread(fixer, arrayExpression, sourceCode), + }; + }, + [uselessArrayCloneSelector](node) { + const arrayExpression = node.parent.parent; + return { + node: arrayExpression, + messageId: CLONE_ARRAY, + fix: fixer => unwrapSingleArraySpread(fixer, arrayExpression, sourceCode), }; }, }; diff --git a/test/no-useless-spread.mjs b/test/no-useless-spread.mjs index adb975c1be..981ea530a1 100644 --- a/test/no-useless-spread.mjs +++ b/test/no-useless-spread.mjs @@ -9,7 +9,6 @@ test.snapshot({ 'const array = [[]]', 'const array = [{}]', 'const object = ({...[]})', - 'const array = [...[].map(x => x)]', 'foo([])', 'foo({})', 'new Foo([])', @@ -241,6 +240,69 @@ test.snapshot({ ], }); +// Cloning an immediate array +test.snapshot({ + valid: [ + '[...not.array]', + '[...not.array()]', + '[...array.unknown()]', + '[...Object.notReturningArray(foo)]', + '[...NotObject.keys(foo)]', + '[...Int8Array.from(foo)]', + '[...Int8Array.of()]', + '[...new Int8Array(3)]', + '[...Promise.all(foo)]', + '[...Promise.allSettled(foo)]', + '[...await Promise.all(foo, extraArgument)]', + ], + invalid: [ + '[...foo.concat(bar)]', + '[...foo.copyWithin(-2)]', + '[...foo.filter(bar)]', + '[...foo.flat()]', + '[...foo.flatMap(bar)]', + '[...foo.map(bar)]', + '[...foo.slice(1)]', + '[...foo.splice(1)]', + '[...foo.split("|")]', + '[...Object.keys(foo)]', + '[...Object.values(foo)]', + '[...Array.from(foo)]', + '[...Array.of()]', + '[...new Array(3)]', + '[...await Promise.all(foo)]', + '[...await Promise.allSettled(foo)]', + outdent` + function foo(bar) { + return[...Object.keys(bar)]; + } + `, + outdent` + function foo(bar) { + return[ + ...Object.keys(bar) + ]; + } + `, + outdent` + function foo(bar) { + return[ + ...( + Object.keys(bar) + ) + ]; + } + `, + outdent` + function foo(bar) { + return([ + ...Object.keys(bar) + ]); + } + `, + ], +}); + test.babel({ valid: [], invalid: [ diff --git a/test/snapshots/no-useless-spread.mjs.md b/test/snapshots/no-useless-spread.mjs.md index 56475ab77f..672be64118 100644 --- a/test/snapshots/no-useless-spread.mjs.md +++ b/test/snapshots/no-useless-spread.mjs.md @@ -1553,3 +1553,379 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^^^^^^^^ \`yield*\` can delegate iterable, it's unnecessary to convert to an array.␊ 3 | }␊ ` + +## Invalid #1 + 1 | [...foo.concat(bar)] + +> Output + + `␊ + 1 | foo.concat(bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.concat(bar)]␊ + | ^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #2 + 1 | [...foo.copyWithin(-2)] + +> Output + + `␊ + 1 | foo.copyWithin(-2)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.copyWithin(-2)]␊ + | ^^^^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #3 + 1 | [...foo.filter(bar)] + +> Output + + `␊ + 1 | foo.filter(bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.filter(bar)]␊ + | ^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #4 + 1 | [...foo.flat()] + +> Output + + `␊ + 1 | foo.flat()␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.flat()]␊ + | ^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #5 + 1 | [...foo.flatMap(bar)] + +> Output + + `␊ + 1 | foo.flatMap(bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.flatMap(bar)]␊ + | ^^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #6 + 1 | [...foo.map(bar)] + +> Output + + `␊ + 1 | foo.map(bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.map(bar)]␊ + | ^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #7 + 1 | [...foo.slice(1)] + +> Output + + `␊ + 1 | foo.slice(1)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.slice(1)]␊ + | ^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #8 + 1 | [...foo.splice(1)] + +> Output + + `␊ + 1 | foo.splice(1)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.splice(1)]␊ + | ^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #9 + 1 | [...foo.split("|")] + +> Output + + `␊ + 1 | foo.split("|")␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...foo.split("|")]␊ + | ^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #10 + 1 | [...Object.keys(foo)] + +> Output + + `␊ + 1 | Object.keys(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...Object.keys(foo)]␊ + | ^^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #11 + 1 | [...Object.values(foo)] + +> Output + + `␊ + 1 | Object.values(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...Object.values(foo)]␊ + | ^^^^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #12 + 1 | [...Array.from(foo)] + +> Output + + `␊ + 1 | Array.from(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...Array.from(foo)]␊ + | ^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #13 + 1 | [...Array.of()] + +> Output + + `␊ + 1 | Array.of()␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...Array.of()]␊ + | ^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #14 + 1 | [...new Array(3)] + +> Output + + `␊ + 1 | new Array(3)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...new Array(3)]␊ + | ^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #15 + 1 | [...await Promise.all(foo)] + +> Output + + `␊ + 1 | await Promise.all(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...await Promise.all(foo)]␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #16 + 1 | [...await Promise.allSettled(foo)] + +> Output + + `␊ + 1 | await Promise.allSettled(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...await Promise.allSettled(foo)]␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + ` + +## Invalid #17 + 1 | function foo(bar) { + 2 | return[...Object.keys(bar)]; + 3 | } + +> Output + + `␊ + 1 | function foo(bar) {␊ + 2 | return Object.keys(bar);␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo(bar) {␊ + > 2 | return[...Object.keys(bar)];␊ + | ^^^^^^^^^^^^^^^^^^^^^ Unnecessarily cloning an array.␊ + 3 | }␊ + ` + +## Invalid #18 + 1 | function foo(bar) { + 2 | return[ + 3 | ...Object.keys(bar) + 4 | ]; + 5 | } + +> Output + + `␊ + 1 | function foo(bar) {␊ + 2 | return (␊ + 3 | Object.keys(bar)␊ + 4 | );␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo(bar) {␊ + > 2 | return[␊ + | ^␊ + > 3 | ...Object.keys(bar)␊ + | ^^^^^^^^^^^^^^^^^^^^^␊ + > 4 | ];␊ + | ^^^ Unnecessarily cloning an array.␊ + 5 | }␊ + ` + +## Invalid #19 + 1 | function foo(bar) { + 2 | return[ + 3 | ...( + 4 | Object.keys(bar) + 5 | ) + 6 | ]; + 7 | } + +> Output + + `␊ + 1 | function foo(bar) {␊ + 2 | return (␊ + 3 | (␊ + 4 | Object.keys(bar)␊ + 5 | )␊ + 6 | );␊ + 7 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo(bar) {␊ + > 2 | return[␊ + | ^␊ + > 3 | ...(␊ + | ^^^^^^␊ + > 4 | Object.keys(bar)␊ + | ^^^^^^␊ + > 5 | )␊ + | ^^^^^^␊ + > 6 | ];␊ + | ^^^ Unnecessarily cloning an array.␊ + 7 | }␊ + ` + +## Invalid #20 + 1 | function foo(bar) { + 2 | return([ + 3 | ...Object.keys(bar) + 4 | ]); + 5 | } + +> Output + + `␊ + 1 | function foo(bar) {␊ + 2 | return (␊ + 3 | Object.keys(bar)␊ + 4 | );␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo(bar) {␊ + > 2 | return([␊ + | ^␊ + > 3 | ...Object.keys(bar)␊ + | ^^^^^^^^^^^^^^^^^^^^^␊ + > 4 | ]);␊ + | ^^^ Unnecessarily cloning an array.␊ + 5 | }␊ + ` diff --git a/test/snapshots/no-useless-spread.mjs.snap b/test/snapshots/no-useless-spread.mjs.snap index 8d2ab2b2c85cec7a45b3f6d0e7cca8f03526506a..76b405a9b868e62d5c647610e61c86f663c014f1 100644 GIT binary patch literal 5471 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 VR*OrT?QosP?|%;|5NShV008PZyI24K literal 4449 zcmV-n5uWZrRzVt{>-ye$z00000000Bs zTnShc*V;ZD>jH>~;KFioTv?1Mn}`dj6t`-Nf=GWX=nxW!gak-X5u;V9RWAy)(pIb1 zDpjvqweGvt1zf9Ds@C0t-rLsQ_Nv#b{%6jdgv?|n1e)+ae|dZ#CogBt@}2jb@0)K< zc<2PcNYFHDM);0(z22DX6*g=C=#r%nFh6e+tUz$u>g(}%bGHrBx4zl;fuH;l#O!{8 zjt&60Ik(hR4c>XF*WIv$oOaq}5VJ`HzNku%k*kxvznph4uEVI3dH0JeAZ7;=lq2|- z4R7K+**PP+>yWnhoDWWgn0-i))Chns6Hncu=1uRKQ|&uueul>eh}o3{v)=%~uk(rM zzBj!C)NPKOijAx+gqYn#5ZV|3sRMPX&x9xKhj~=|_y@eO9b#5P5ZMHPUWYdRa`0`H z*O4V%FGf%M{UpS!jNlei;_=>7*WBkV9?@b5cp2n+9Afq*L26R~q89uT7qiOY%8Y~I z-Hy2{93W;(2w*b+`cp1_)|~7A+oXF}1K)N1dei@h`drzK5?m3bWhFcxBVDM37%-61C(#{N&$V3;^A{-M9CdRimQ zP9XS|Aj27eVZpw~mZ=U6{_fZOSw1(u351xPP4GLyJKsikI`OKJ^4*;mzn9$$j)RyT z(wv2x3jpt4K9;ZS>^7?1z{6t~?bKQ zI@}9tEA{qnyG5TJHV>*HW^)NHA|#adNV@n*vr94S9-Kb(IN?)>*;Z~Wt|5%pRct68 z@!C%M0N#B2|D7Eckz-9I9`eIjr9 zQRlBuzf3;$cZgYE4;CLIWGFLll}CrJ%`LwlH+RMH8xXU{31+kgAm`u16Rva$Q{Cyj z;n|Sa*T00AT~6TC1_153Cf!05(m7{#bUeJ}_@qjR*)W2=2)b9vnfjjFe*AR7y~7d-c?A>tC*3UHt26h}m+2Y3)#nFJpT|`e+h5q~8C&dhXuu zAZEWIi0}lUFt&};ruGX?Mn&EazyIv(JcwDE;5Jht?S8>IcU^w-LwAQw89w3=#B8D$ zi}MH{E^(N?eow`*8F%i@bQqr!1TpL9&7!RjQe*krV2^ztc5y76q1FVQ`wC(6v$rrnPE{Rt3XeBN*~G0E!;rxiX;&b-*W`W)I`7&e>WF~WT9 z(M$Uk*ZW)oo=7f;SY%)0ur$U~UFu**i-sF(fPZEpTn!uiLU5VIQya@qq>eE8GT zzS}yKl{&p_b8TlxDa7nbf_EtZ7S5bj6x*bD;;=Gpgy+wjydY+k1g8)_yWFhx)!pCE znRh&Sh2!G)TS3e^cVKZ5Vd?p^6HhMw;#k?9#ogj6W_=7X`0&E zIZxm7&Ks3es4!U+fS4^Ia77MnF~mQh}okALxYenx#aq4TaUiyx48U$Oi}e`Eg@zn5jX?`u&dGYOIiNeqZUgi z-n`jOfz~9$`V*{XN(5a$85;Pib<0gUsmIe1NTm#Wf*>jcfZZPc)7O69(xbY1nRePQ zx5^-9H3a`b*r$EAaqOh#6$_h%RR1GpQ3}MYcPNWF2q$v>xp=8s)^cuP(csd?C5{lY zdk8YZ066)&#S>ji@5qkvVX|wUzt4o2{emE_2h!ojPEr*$x1V!(?0~%8NhJ`o`v`R5 z0Gy7$+jU@9zd}t}=7uxF-kt+7yO_YaCjb|2Tz;@9?m%Bf%>L~Ue%yg}AchSi*okm4 z?C0l+J4?6yGebEkzxvAy5VOA$OpZXRyf`g=`M%kjnCn*)=X=~}4>7xfphYhLsyzPH z{l{uAm(k1O9<>R$>JKp+POu%}wyT@+y@4M%t|*zEy?Kq_3y9e&g7Lip_@!}2>dubP z%IL{QzMZQ2trNuTdV-=psD$I;_*+kU=IlJM#^p->d>@F}3W5cFks3ei+cx4O?vePrh%Ak-;c!wgohI^ryMQX?O)mE@zaC{ zA3=!O75!Q49)L=$JD#-L^VR;8eT}Dt7G)iVn0-O;Q#A6rhbjNL`%5OBp1rf;dhW`F z5VPPd7E=)(y{4XKWk$@ZjM=sPl1|?rVs<@2U<}$N1|)seyMu$5VN@i zT?Zm>nO2zP?(;*{?eR}e`|aM1A_s;YO0X5-MGNPjOS2CyOnCiu_JM+dRS>g(5L6CA zRttUFWs7q1*s!5dHzj`Vts%=2=mrDO2sFj9twUqr=Fq{3qJpU@lv+NK8l=(q(-JCA zohDZ()$%k6l`08v;uM!VMRAHtrP49z7N%q}DodfCDU{(SB_)N5&(4+8X%wxd=-ga7 zpHeAx@?2U)DH$R?FH5f0X(^=^HL8}&WKdv!VCFk}HZUdxqqBo8~T{hgD%r~CdPm1XeHO{1- zpFda?Fpi>SGI=&L7*ZoeYmGycAl8pFNJ^(+IyOO`t79$=_Ti1)lQVWr>Ug?wmWaXx8nBI_nxzFCg5{mhc z3eodArXn3P7GqW%DXKTqs4dr9dtsFstimlkQCw`sIK}Ro>uu0no?50;YSdI9rBM3^ zPz8t&LA+zWQm#rvtYI-Sz9JO;g1gzl)j+hOC~ifjJBB2=4;i4N;P!y_2Yg%LI7)^F znkHAt(`lWYs_{cs?*>A>c4%!B6wT;_77&xdC?>pb_YT&xdZKx1vZai&t!&diB5uXs z1$Y)o5nA_lTJeLdm8ZWy^Uvrp0s_!K5vlIBTB@ik1m<7;&{2vWF?|cby&sb z_u6U|Gj-WdAQ8k0v^HNYqtIp?8ATN+G`SQ#kyh#`e|CguA}Ec**l<7}-qwE9U=XXS z;aaAT*aMk0MnX*>+h`Q*PLC@^8@&b%x<0!-$1*`i$>#F8&4N6trfP|?BD=(XgPiJ|cu&pO$zv4?tsDti zC0D2GGI$H@R(*5uLN$voK=IYs!0GsQ=J5e-o~%sgg$L|9WFrKDVcKST|0e#fhabKq zi@fZS*j{wjzQ52}dVkR%uPcb`9q7 zI*Z1)H4k9iK`#-FY2I_P`(6 z>u}~e0AKcGE}4r6lUsY?Yi9I8_==^2gV_(M%r$P-5z}m=n8j^k5;w=>!h^%vihEP! zp_k!$8XJf(ZJ<|810g}i210PV%&s27w8|5lybe~0GFYYN5gbnx=IT93wHTLaOW45V zH^N4i)L1#$)XWm2os&fkZ5*DdwHfCqYH>-8$@R6{Jgq)Tft>XEDJQ zR_CV_n^@i$`55eOo>CP!Vk4MVwOwH9!|M}?$ON8PGc7z(3+3Ex*2Vr#)}IM5iGW2; z18k`4X#nF>j5X%~?0f=%o0Bv>Zs=PktSTd*m zd+EkosUis_)AwO^I^y6fszOFp*5;UvDKEQQt^=pPoZLKKHoQevVDTI;m8xg&5$v>0 z(_tdIsTVdQ-_42G)21eMH*?B7gN_q? z`E9SSxzs%`xXkh!Zz~dm4VW3M$4kvM*1puVKF!+@skda~QZ2f4>CiubOD~`Ff zn3`bVaO~}5i1};EJCV@eiH-a9<`$}JTTyIV#=zYyO;L}qU98u7;cgqF6@|r6SSFI7 z6bOTDvjV+EJ(%8-yEXd`N`ttq&f@M?*yF#KkN3sD5<#0shxxVp+=lSCVVph-oV!)2;^jJ>N}eX>5Pqoj=x$9F zb^i*Lpt}&L*?yj*)>F+YRsByjqkC<3su|T~KP%MsO$nic=Cr*wgui%H*42urjOUl( zI_uAx38kxqN7*2e88-&i8V_|_I1J8pekg{i-sL04jDkQ*| zug0Muo2@pas`Z8}HQP67O@q7z<>*kiu^8Tza&psuI>xoPS@d