eslint-plugin-zappie 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # eslint-plugin-zappie
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 79deec3: Hold every template to the same accessibility, semantics, performance and CSS bar.
8
+
9
+ - `eslint-plugin-zappie`: `zappie/no-lazy-overlay` reports `React.lazy` and `Suspense` and points at `deferred()` from `@zappie/foundations`. It is an error in `recommended`.
10
+ - `@zappie/check`: `lint` fails when a project turns down a `jsx-a11y` rule from `recommended`. A new `css` step reports `css/important` and `css/class-patch` as errors, and `css/duplicate-keyframes`, `css/duplicate-selector`, `css/arbitrary-token`, `css/literal-token`, `css/unused-custom-property`, `css/reduced-motion` and `css/unreadable` as warnings.
11
+ - `@zappie/verify`: V13 fails on a missing main landmark, a page without exactly one h1, a dialog without a name, a positive tabindex, a phone control under 44×44 px and focus that doesn't return after the item or cart closes. V16 times each marked overlay from tap to first paint on a phone profile. V17 checks each marked overlay's focus, Escape and scroll lock. Both are skipped while a menu has no overlay markers. V14 records `menuCssKbGz`. `findRegressions` compares two reports' metrics.
12
+ - `@zappie/starter`: menu controls are at least 44×44 px on a phone.
13
+
3
14
  ## 0.1.2
4
15
 
5
16
  ### Patch Changes
package/README.md CHANGED
@@ -21,17 +21,20 @@ export default [...zappie.configs.recommended];
21
21
 
22
22
  ## Rules
23
23
 
24
- | Rule | What it asks for |
25
- | ---------------------------- | ------------------------------------------------------------- |
26
- | `zappie/no-direct-api` | Call Zappie through the SDK. |
27
- | `zappie/no-money-literals` | Use SDK prices and totals in your storefront. |
28
- | `zappie/use-format-money` | Format SDK Money with `format.money()` or `<Money>`. Fixable. |
29
- | `zappie/cart-via-sdk` | Keep your cart state in the SDK. |
30
- | `zappie/checkout-via-sdk` | Open checkout through the Zappie SDK. |
31
- | `zappie/no-custom-auth` | Sign customers in through the Zappie SDK. |
32
- | `zappie/no-tracking-scripts` | Forward analytics through Zappie. |
33
- | `zappie/overlay-primitive` | Use Overlay to keep dialogs and sheets in the URL. |
34
- | `zappie/verify-markers` | Mark shopping actions so verify can find them. Fixable. |
24
+ | Rule | What it asks for |
25
+ | ---------------------------- | ---------------------------------------------------------------- |
26
+ | `zappie/no-direct-api` | Call Zappie through the SDK. |
27
+ | `zappie/no-money-literals` | Use SDK prices and totals in your storefront. |
28
+ | `zappie/use-format-money` | Format SDK Money with `format.money()` or `<Money>`. Fixable. |
29
+ | `zappie/cart-via-sdk` | Keep your cart state in the SDK. |
30
+ | `zappie/checkout-via-sdk` | Open checkout through the Zappie SDK. |
31
+ | `zappie/no-custom-auth` | Sign customers in through the Zappie SDK. |
32
+ | `zappie/no-tracking-scripts` | Forward analytics through Zappie. |
33
+ | `zappie/overlay-primitive` | Use Overlay to keep dialogs and sheets in the URL. |
34
+ | `zappie/verify-markers` | Mark shopping actions so verify can find them. Fixable. |
35
+ | `zappie/no-lazy-overlay` | Load overlays with `deferred()`, not `React.lazy` or `Suspense`. |
36
+
37
+ `recommended` also turns on the recommended `jsx-a11y` rules as the accessibility baseline. `zappie check` fails a project that turns any of them down.
35
38
 
36
39
  Typed rules use TypeScript's project service when it's available, and fall back to syntax checks when it isn't.
37
40
 
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ declare const noCustomAuth: _typescript_eslint_utils_ts_eslint.RuleModule<"auth"
17
17
  type Message$1 = "direct" | "internal" | "storefront" | "checkout" | "customer";
18
18
  declare const noDirectApi: _typescript_eslint_utils_ts_eslint.RuleModule<Message$1, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
19
19
 
20
+ declare const noLazyOverlay: _typescript_eslint_utils_ts_eslint.RuleModule<"lazy" | "suspense", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
21
+
20
22
  declare const noMoneyLiterals: _typescript_eslint_utils_ts_eslint.RuleModule<"arithmetic" | "literal" | "scale", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
21
23
 
22
24
  declare const noTrackingScripts: _typescript_eslint_utils_ts_eslint.RuleModule<"tracking", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
@@ -52,8 +54,9 @@ declare const plugin: {
52
54
  "verify-markers": _typescript_eslint_utils_ts_eslint.RuleModule<"marker", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
53
55
  "item-link-prefetch": _typescript_eslint_utils_ts_eslint.RuleModule<"prefetch", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
54
56
  "image-priority": _typescript_eslint_utils_ts_eslint.RuleModule<"mapped" | "routeLimit", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
57
+ "no-lazy-overlay": _typescript_eslint_utils_ts_eslint.RuleModule<"lazy" | "suspense", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
55
58
  };
56
59
  configs: Record<string, ZappieFlatConfig[]>;
57
60
  };
58
61
 
59
- export { type ZappieFlatConfig, cartViaSdk, checkoutViaSdk, plugin as default, imagePriority, itemLinkPrefetch, noCustomAuth, noDirectApi, noMoneyLiterals, noTrackingScripts, overlayPrimitive, useFormatMoney, verifyMarkers };
62
+ export { type ZappieFlatConfig, cartViaSdk, checkoutViaSdk, plugin as default, imagePriority, itemLinkPrefetch, noCustomAuth, noDirectApi, noLazyOverlay, noMoneyLiterals, noTrackingScripts, overlayPrimitive, useFormatMoney, verifyMarkers };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import Re from"@typescript-eslint/parser";import se from"eslint-plugin-jsx-a11y";import Je from"eslint-plugin-react-hooks";import{ESLintUtils as oe}from"@typescript-eslint/utils";var p=oe.RuleCreator(e=>`https://docs.zappie.ai/lint/${e}`);var pe=/^(cart|basket|bag)(Items|Lines|Count|Total)?$/i,ce=/cart|basket|bag/i;function b(e){if(e.type==="Identifier")return e.name;if(e.type==="MemberExpression"&&e.property.type==="Identifier")return e.property.name}function me(e){return e.type==="Identifier"?e.name:e.type==="ArrayPattern"&&e.elements[0]?.type==="Identifier"?e.elements[0].name:void 0}function le(e){let r=b(e.callee)==="useReducer"?e.arguments[1]:e.arguments[0];return!!(r&&r.type!=="SpreadElement"&&(r.type==="ArrayExpression"||r.type==="Literal"&&typeof r.value=="number"))}var F=p({name:"cart-via-sdk",meta:{type:"problem",docs:{description:"Keep your cart state in the SDK."},schema:[],messages:{cart:"Use useCart(), useCartLine() or the SDK client cart. Keep cart state and storage in the SDK."}},defaultOptions:[],create(e){return{CallExpression(r){let i=b(r.callee),t=r.arguments[0];["getItem","setItem","removeItem","open"].includes(i??"")&&t?.type==="Literal"&&typeof t.value=="string"&&ce.test(t.value)&&e.report({node:r,messageId:"cart"})},VariableDeclarator(r){if(r.init?.type!=="CallExpression")return;let i=me(r.id),t=b(r.init.callee);i&&pe.test(i)&&["useState","useReducer","createContext"].includes(t??"")&&le(r.init)&&e.report({node:r,messageId:"cart"}),i&&/cart/i.test(i)&&["create","createStore","atom"].includes(t??"")&&e.report({node:r,messageId:"cart"})}}}});import{minimatch as ue}from"minimatch";var fe=/(?:api|accounts|checkout|edge)\.zappie\.ai|\/storefront\/v1|\/customer\/graphql/i,de=/googletagmanager\.com|google-analytics\.com|connect\.facebook\.net|analytics\.tiktok\.com|static\.hotjar\.com|cdn\.segment\.com|plausible\.io|clarity\.ms/i,O=/checkout\.paystack\.com|flutterwave|(?:^|\.)stripe\.com/i;function d(e){if(e?.type==="Literal"&&typeof e.value=="string")return e.value;if(e?.type==="TemplateLiteral")return e.quasis.map(r=>r.value.cooked??r.value.raw).join("")}function l(e,r){return e.attributes.find(i=>i.type==="JSXAttribute"&&i.name.type==="JSXIdentifier"&&i.name.name.toLowerCase()===r.toLowerCase())}function u(e){if(e?.value){if(e.value.type==="Literal")return typeof e.value.value=="string"?e.value.value:void 0;if(e.value.type==="JSXExpressionContainer")return d(e.value.expression)}}var R=e=>fe.test(e),J=e=>de.test(e),X=e=>e==="@zappie/sdk/internal"||e.startsWith("@zappie/sdk/internal/");function P(e,r){let i=e.replaceAll("\\","/");return r.some(t=>ue(i,t,{matchBase:!0}))}var ye=new Set(["cc-number","cc-exp","cc-csc","cc-name"]),D=/card.?number|cvv|cvc|expiry/i;function Se(e){return e.startsWith("@paystack/")||e==="react-paystack"||e==="flutterwave-react-v3"||e.startsWith("@stripe/")}var K=p({name:"checkout-via-sdk",meta:{type:"problem",docs:{description:"Open checkout through the Zappie SDK."},schema:[],messages:{checkout:"Use createCheckout().open() from @zappie/sdk/checkout. Let checkout collect payment details."}},defaultOptions:[],create(e){return{ImportDeclaration(r){Se(r.source.value)&&e.report({node:r.source,messageId:"checkout"})},JSXOpeningElement(r){let i=r.name.type==="JSXIdentifier"?r.name.name.toLowerCase():"",t=u(l(r,"autocomplete")),a=u(l(r,"name")),n=u(l(r,"id")),s=u(l(r,i==="iframe"?"src":"href")),o=i==="input"&&(t!==void 0&&ye.has(t.toLowerCase())||a!==void 0&&D.test(a)||n!==void 0&&D.test(n)),c=(i==="iframe"||i==="a")&&s!==void 0&&O.test(s);(o||c)&&e.report({node:r,messageId:"checkout"})}}}});function ge(e){let r=e;for(;r?.parent;){let i=r.parent;if((i.type==="ArrowFunctionExpression"||i.type==="FunctionExpression")&&i.parent?.type==="CallExpression"&&i.parent.callee.type==="MemberExpression"&&i.parent.callee.property.type==="Identifier"&&i.parent.callee.property.name==="map")return i;r=i}}function he(e,r){let i=e.getText(r.parent);if(/\.slice\(\s*0\s*,\s*1\s*\)\.map/.test(i))return!0;let t=r.params[1];return t?.type!=="Identifier"?!1:new RegExp(`${t.name}\\s*(?:={2,3}\\s*0|<\\s*1)`).test(e.getText(r))}var U=p({name:"image-priority",meta:{type:"problem",docs:{description:"Reserve priority for the first phone screen."},schema:[],messages:{mapped:"Remove priority from repeated images or limit it to the first.",routeLimit:"Keep no more than two priority images in a route."}},defaultOptions:[],create(e){let r=[];return{JSXOpeningElement(i){let t=l(i,"priority"),a=t?.value?.type==="JSXExpressionContainer"&&t.value.expression.type==="Literal"&&t.value.expression.value===!1;if(i.name.type!=="JSXIdentifier"||i.name.name!=="Image"||!t||a)return;r.push(i);let n=ge(i);n&&!he(e.sourceCode,n)&&e.report({node:t,messageId:"mapped"})},"Program:exit"(){if(/(?:^|\/)routes?\/[^/]+\.[jt]sx$/.test(e.filename.replaceAll("\\","/"))&&r.length>2)for(let i of r.slice(2))e.report({node:i,messageId:"routeLimit"})}}}});function Te(e){let r=e?.value?.type==="JSXExpressionContainer"?e.value.expression:void 0;if(r?.type==="CallExpression"&&r.callee.type==="Identifier"&&r.callee.name==="itemHref")return!0;let i=u(e);return i!==void 0&&/^\/item(?:\/|$)/.test(i)}var $=p({name:"item-link-prefetch",meta:{type:"problem",docs:{description:"Prefetch item routes before shoppers tap."},schema:[],messages:{prefetch:'Add prefetch="viewport" so this item route is ready to tap.'}},defaultOptions:[],create(e){let r=new Set;return{ImportDeclaration(i){if(["react-router","react-router-dom"].includes(i.source.value))for(let t of i.specifiers)t.type==="ImportSpecifier"&&t.imported.type==="Identifier"&&["Link","NavLink"].includes(t.imported.name)&&r.add(t.local.name)},JSXOpeningElement(i){i.name.type==="JSXIdentifier"&&r.has(i.name.name)&&Te(l(i,"to"))&&!l(i,"prefetch")&&e.report({node:i,messageId:"prefetch"})}}}});var Ee=["**/*.server.*","**/server/**"],I=/otp|verify.?code|access.?token|refresh.?token|id.?token/i,B=new Set(["localStorage","sessionStorage"]),xe=new Set(["window","globalThis","self"]);function V(e){return e.type==="Identifier"?B.has(e.name):e.type==="MemberExpression"&&!e.computed&&e.object.type==="Identifier"&&xe.has(e.object.name)&&e.property.type==="Identifier"&&B.has(e.property.name)}function ke(e){return!e.computed&&e.property.type==="Identifier"?e.property.name:d(e.property)}function ve(e){if(e==="jose"||e==="jsonwebtoken")return"server";if(e==="next-auth"||e.startsWith("next-auth/")||e.startsWith("@auth/")||e==="better-auth"||e.startsWith("better-auth/")||e==="lucia"||e.startsWith("@clerk/")||e==="firebase/auth"||e.startsWith("@supabase/auth-"))return"all"}var q=p({name:"no-custom-auth",meta:{type:"problem",docs:{description:"Sign customers in through the Zappie SDK."},schema:[{type:"object",properties:{serverFiles:{type:"array",items:{type:"string"},uniqueItems:!0}},additionalProperties:!1}],messages:{auth:"Use createCustomer().signIn() from @zappie/sdk/customer to sign customers in."}},defaultOptions:[{}],create(e,[r]){let i=P(e.filename,r.serverFiles??Ee);return{ImportDeclaration(t){let a=ve(t.source.value);(a==="all"||a==="server"&&!i)&&e.report({node:t.source,messageId:"auth"})},JSXOpeningElement(t){if(t.name.type!=="JSXIdentifier"||t.name.name.toLowerCase()!=="input")return;let a=u(l(t,"type")),n=u(l(t,"autocomplete"));(a?.toLowerCase()==="password"||n!==void 0&&["one-time-code","current-password","new-password"].includes(n.toLowerCase()))&&e.report({node:t,messageId:"auth"})},CallExpression(t){if(t.callee.type!=="MemberExpression"||!V(t.callee.object))return;let a=t.arguments[0],n=a?.type==="Identifier"?a.name:d(a??null);n&&I.test(n)&&e.report({node:t,messageId:"auth"})},AssignmentExpression(t){if(t.left.type==="MemberExpression"&&V(t.left.object)){let s=ke(t.left);s&&I.test(s)&&e.report({node:t,messageId:"auth"});return}let a=t.left.type==="MemberExpression"&&t.left.object.type==="Identifier"&&t.left.object.name==="document"&&t.left.property.type==="Identifier"&&t.left.property.name==="cookie",n=d(t.right);a&&n&&I.test(n)&&e.report({node:t,messageId:"auth"})}}}});function be(e){return/checkout\.zappie\.ai|\/checkout(?:\/|$)/i.test(e)?"checkout":/accounts\.zappie\.ai|\/customer\/graphql/i.test(e)?"customer":/\/storefront\/v1/i.test(e)?"storefront":"direct"}var Z=p({name:"no-direct-api",meta:{type:"problem",docs:{description:"Call Zappie through the SDK."},schema:[],messages:{direct:"Call the matching public @zappie/sdk client to reach this Zappie endpoint.",internal:"Use a public SDK entry. Only Zappie-owned packages can import @zappie/sdk/internal.",storefront:"Use createZappie(config).store or .menu to read storefront data.",checkout:"Use createCheckout().open() from @zappie/sdk/checkout.",customer:"Use createCustomer() from @zappie/sdk/customer."}},defaultOptions:[],create(e){let r=i=>{if(i.parent.type==="ImportDeclaration")return;let t=d(i);t&&R(t)&&e.report({node:i,messageId:be(t)})};return{Literal:r,TemplateLiteral:r,ImportDeclaration(i){X(i.source.value)&&e.report({node:i.source,messageId:"internal"})}}}});import{ESLintUtils as Ie}from"@typescript-eslint/utils";var Le=/[₦€£]\s?\d|\$\s?\d|NGN\s?\d|\d+(?:\.\d{2})?\s?(?:naira|NGN)/i;function g(e){return e.trim()!=="$1"&&Le.test(e)}function f(e){if(e.type==="MemberExpression"){if(!e.computed&&e.property.type==="Identifier")return e.property.name;if(e.computed&&e.property.type==="Literal")return typeof e.property.value=="string"?e.property.value:void 0}}var S=e=>f(e)==="amountMinor";function we(e){return e.type==="MemberExpression"&&S(e)?e.object:void 0}function y(e){if(!(e.type!=="BinaryExpression"||e.operator!=="/"||e.right.type!=="Literal"||e.right.value!==100))return we(e.left)}function h(e){try{return Ie.getParserServices(e,!0)}catch{return}}function Ce(e){return e.getName().includes("MoneyBrand")?(e.getDeclarations()??[]).some(r=>{let i=r.getSourceFile().fileName.replaceAll("\\","/");return i.endsWith("/packages/sdk/src/money.ts")||/\/node_modules\/@zappie\/sdk\/.*\.d\.ts$/.test(i)}):!1}function T(e,r){if(!e?.program)return!1;let i=e.program.getTypeChecker(),t=e.esTreeNodeToTSNodeMap.get(r),a=i.getTypeAtLocation(t);return(a.isUnionOrIntersection()?a.types:[a]).some(s=>s.getProperties().some(o=>Ce(o)))}function E(e){return e?.type!=="ObjectExpression"?!1:e.properties.some(r=>r.type==="Property"&&!r.computed&&(r.key.type==="Identifier"&&r.key.name==="style"||r.key.type==="Literal"&&r.key.value==="style")&&r.value.type==="Literal"&&r.value.value==="currency")}var W=p({name:"no-money-literals",meta:{type:"problem",docs:{description:"Use SDK prices and totals in your storefront."},schema:[],messages:{arithmetic:"Use cart.subtotal, line.lineSubtotal, quote.subtotal, quote.lines totals or quote.total. Let the SDK calculate amounts.",literal:"Render SDK Money with format.money() or <Money> instead of a hard-coded amount.",scale:"Use format.money() to display amountMinor. Currencies use 0, 2, 3 or 4 minor-unit digits."}},defaultOptions:[],create(e){let r=h(e),i=t=>S(t)||T(r,t);return{Literal(t){typeof t.value=="string"&&t.parent.type!=="ImportDeclaration"&&g(t.value)&&e.report({node:t,messageId:"literal"})},JSXText(t){g(t.value)&&e.report({node:t,messageId:"literal"})},BinaryExpression(t){["+","-","*","/"].includes(t.operator)&&(y(t)&&t.parent.type==="MemberExpression"&&t.parent.object===t&&f(t.parent)==="toFixed"||(i(t.left)||i(t.right))&&e.report({node:t,messageId:"arithmetic"}))},TemplateLiteral(t){for(let a of t.quasis)g(a.value.cooked??a.value.raw)&&e.report({node:a,messageId:"literal"})},CallExpression(t){t.callee.type==="MemberExpression"&&f(t.callee)==="toFixed"&&t.arguments[0]?.type==="Literal"&&t.arguments[0].value===2&&y(t.callee.object)&&e.report({node:t,messageId:"scale"})}}}});var Me=["react-ga4","react-facebook-pixel","@vercel/analytics","posthog-js"];function Ae(e){return Me.includes(e)||e.startsWith("@segment/")}var _=p({name:"no-tracking-scripts",meta:{type:"problem",docs:{description:"Forward analytics through Zappie."},schema:[],messages:{tracking:"Configure analytics.forward in zappie.config.ts to forward analytics. Remove the tracking script."}},defaultOptions:[],create(e){let r=i=>{if(i.parent.type==="ImportDeclaration")return;let t=d(i);t&&J(t)&&e.report({node:i,messageId:"tracking"})};return{Literal:r,TemplateLiteral:r,ImportDeclaration(i){Ae(i.source.value)&&e.report({node:i.source,messageId:"tracking"})},CallExpression(i){let t=i.callee,a=t.type==="Identifier"&&["gtag","fbq"].includes(t.name),n=t.type==="MemberExpression"&&!t.computed&&t.object.type==="Identifier"&&t.object.name==="dataLayer"&&t.property.type==="Identifier"&&t.property.name==="push";(a||n)&&e.report({node:i,messageId:"tracking"})}}}});var Ne=["modal","sheet","drawer","dialog","item","cart","cart_sheet","cart_edit","cart_clear","search","catalogue_search","catalogue_categories","product_note","modal_id"];function L(e){if(e.type==="Identifier")return e.name;if(e.type==="MemberExpression"&&e.property.type==="Identifier")return e.property.name}function H(e,r){return e.type==="Literal"&&typeof e.value=="string"?[...r].some(i=>new RegExp(`[?&]${i}=`).test(e.value)):e.type==="TemplateLiteral"?e.quasis.some(i=>H({...i,type:"Literal",value:i.value.raw},r)):e.type==="ObjectExpression"?e.properties.some(i=>i.type==="Property"&&(i.key.type==="Identifier"&&r.has(i.key.name)||i.key.type==="Literal"&&typeof i.key.value=="string"&&r.has(i.key.value))):!1}var G=p({name:"overlay-primitive",meta:{type:"problem",docs:{description:"Use Overlay to keep dialogs and sheets in the URL."},schema:[{type:"object",properties:{keys:{type:"array",items:{type:"string"}}},additionalProperties:!1}],messages:{overlay:"Use Overlay or useOverlay for dialogs and sheets in the URL. They handle Back, Forward and focus restoration."}},defaultOptions:[{}],create(e,[r]){let i=new Set([...Ne,...r.keys??[]]),t=new Set,a=n=>{let s=e.sourceCode.getText(n);return s.includes("useSearchParams(")||[...t].some(o=>new RegExp(`\\b${o}\\b`).test(s))};return{VariableDeclarator(n){n.init?.type==="CallExpression"&&L(n.init.callee)==="useSearchParams"?n.id.type==="Identifier"?t.add(n.id.name):n.id.type==="ArrayPattern"&&n.id.elements[0]?.type==="Identifier"&&t.add(n.id.elements[0].name):n.id.type==="Identifier"&&n.init&&a(n.init)&&t.add(n.id.name)},CallExpression(n){let s=L(n.callee);(s==="setSearchParams"||s==="navigate"||s==="pushState"||s==="replaceState"||n.callee.type==="CallExpression"&&L(n.callee.callee)==="useNavigate")&&n.arguments.some(c=>c.type!=="SpreadElement"&&H(c,i))&&e.report({node:n,messageId:"overlay"})},JSXOpeningElement(n){let s=n.name.type==="JSXIdentifier"&&n.name.name==="dialog",o=n.attributes.some(m=>m.type==="JSXAttribute"&&m.name.name==="role"&&m.value?.type==="Literal"&&m.value.value==="dialog"),c=n.attributes.find(m=>m.type==="JSXAttribute"&&m.name.name==="open");(s||o)&&c?.type==="JSXAttribute"&&c.value?.type==="JSXExpressionContainer"&&a(c.value.expression)&&e.report({node:n,messageId:"overlay"})}}}});function w(e){return e.body.find(r=>r.type==="ImportDeclaration"&&r.source.value==="@zappie/sdk")}function C(e){return e?.specifiers.find(r=>r.type==="ImportSpecifier"&&r.imported.type==="Identifier"&&r.imported.name==="format")}function je(e,r,i,t){let a=w(i);if(C(a)?.local.name===t)return;let n=t==="format"?"format":`format as ${t}`,s=a?.specifiers.filter(m=>m.type==="ImportSpecifier");if(a&&a.importKind!=="type"&&s?.length){let m=s.at(-1);if(m)return e.insertTextAfter(m,`, ${n}`)}let o=i.body[0],c=`import { ${n} } from "@zappie/sdk";
2
- `;return o?e.insertTextBefore(o,c):e.insertTextAfter(i,c)}function M(e,r,i,t,a,n){let s=[e.replaceText(t,`${n}.money(${r.getText(a)})`)],o=je(e,r,i,n);return o&&s.push(o),s}function Q(e,r,i){if(C(w(r)))return"imported";let t=e.getScope(i);for(;t;){if(t.variables.some(a=>a.name==="format"))return"conflict";t=t.upper}return"available"}function Y(e){return C(w(e))?.local.name}function ee(e,r){let i=new Set,t=e.getScope(r);for(;t;){for(let a of t.variables)i.add(a.name);t=t.upper}for(let a of["zappieFormat","zappieMoneyFormat","formatMoneyValue"])if(!i.has(a))return a;return"zappieSdkFormat"}function te(e){let r=e.type==="NewExpression"||e.type==="CallExpression"?e.callee:void 0;return r?.type==="MemberExpression"&&r.object.type==="Identifier"&&r.object.name==="Intl"&&f(r)==="NumberFormat"}function ze(e){if(e.callee.type==="MemberExpression"){if(f(e.callee)==="toLocaleString")return y(e.callee.object);if(f(e.callee)==="format")return y(e.arguments[0]??e)}}function Fe(e){let r=e.parent,i=r.type==="MemberExpression"?r.parent:void 0;return!!(i?.type==="CallExpression"&&i.callee===r&&f(r)==="format"&&y(i.arguments[0]??i))}var re=p({name:"use-format-money",meta:{type:"problem",docs:{description:"Format SDK Money with format.money() or <Money>."},fixable:"code",hasSuggestions:!0,schema:[],messages:{currency:"Use format.money(). Intl currency defaults can differ from the API minor-unit digits.",direct:"Render SDK Money with format.money(value) or <Money value={value}>.",minor:"Use format.money(value). Dividing by 100 gives the wrong amount for currencies with 0, 3 or 4 minor-unit digits.",useAlias:"Import format with an unused alias and format this Money value."}},defaultOptions:[],create(e){let r=e.sourceCode,i=h(e),t,a=(s,o,c)=>{let m={node:s,messageId:"minor"};if(Q(r,t,s)==="conflict"){let k=ee(r,s);e.report({...m,suggest:[{messageId:"useAlias",fix:v=>M(v,r,t,o,c,k)}]});return}e.report({...m,fix:k=>{let v=Y(t)??"format";return M(k,r,t,o,c,v)}})},n=s=>{let o=ze(s);return o?(a(s,s,o),!0):!1};return{Program(s){t=s},NewExpression(s){te(s)&&E(s.arguments[1])&&!Fe(s)&&e.report({node:s,messageId:"currency"})},CallExpression(s){if(!n(s)){if(te(s)&&E(s.arguments[1])){e.report({node:s,messageId:"currency"});return}s.callee.type==="MemberExpression"&&f(s.callee)==="toLocaleString"&&s.arguments.some(E)&&e.report({node:s,messageId:"currency"})}},TemplateLiteral(s){for(let o of s.expressions){let c=y(o);c?a(o,o,c):S(o)&&e.report({node:o,messageId:"minor"})}},JSXExpressionContainer(s){s.parent.type==="JSXAttribute"||s.expression.type==="JSXEmptyExpression"||(y(s.expression)||S(s.expression)?e.report({node:s.expression,messageId:"minor"}):T(i,s.expression)&&e.report({node:s.expression,messageId:"direct"}))}}}});var A={add:{name:"data-zappie-action",text:'data-zappie-action="add-to-cart"'},checkout:{name:"data-zappie-checkout",text:"data-zappie-checkout"},signin:{name:"data-zappie-signin",text:"data-zappie-signin"}};function Oe(e,r){let i=r.value?e.getText(r.value):"";if(/\b(?:cart(?:\.actions)?\.add)\s*\(/.test(i))return"add";if(/\b(?:checkout|createCheckout\([^)]*\))\.open\s*\(/.test(i))return"checkout";if(/\b(?:auth|createCustomer\([^)]*\))\.signIn\s*\(/.test(i))return"signin"}var ie=p({name:"verify-markers",meta:{type:"suggestion",docs:{description:"Mark shopping actions so verify can find them."},fixable:"code",schema:[],messages:{marker:"Add {{marker}} so zappie verify can find this shopping action."}},defaultOptions:[],create(e){return{JSXOpeningElement(r){let i=r.attributes.find(a=>a.type==="JSXAttribute"&&a.name.name==="onClick");if(!i)return;let t=Oe(e.sourceCode,i);!t||r.attributes.some(a=>a.type==="JSXAttribute"&&a.name.name===A[t].name)||e.report({node:i,messageId:"marker",data:{marker:A[t].text},fix:a=>a.insertTextAfter(r.name,` ${A[t].text}`)})}}}});var ae={"no-direct-api":Z,"no-tracking-scripts":_,"checkout-via-sdk":K,"no-custom-auth":q,"no-money-literals":W,"use-format-money":re,"overlay-primitive":G,"cart-via-sdk":F,"verify-markers":ie,"item-link-prefetch":$,"image-priority":U},ne=Object.fromEntries(Object.keys(ae).map(e=>[`zappie/${e}`,e==="verify-markers"?"warn":"error"])),Xe={...ne,"zappie/verify-markers":"error"},z={meta:{name:"eslint-plugin-zappie",version:"0.0.0"},rules:ae,configs:{}},x={name:"zappie/rules",files:["**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}"],languageOptions:{parser:Re},plugins:{zappie:z,"jsx-a11y":se,"react-hooks":Je},rules:{...se.flatConfigs.recommended.rules,"react-hooks/rules-of-hooks":"error",...ne}},N={name:"zappie/search-dialogs",files:["**/search/**/*.{jsx,tsx}","**/*search*.{jsx,tsx}"],rules:{"jsx-a11y/no-autofocus":"off"}},j={name:"zappie/typed",files:["**/*.{ts,tsx,mts,cts}"],languageOptions:{parserOptions:{projectService:!0}}};z.configs={recommended:[x,j,N],strict:[{...x,rules:{...x.rules,...Xe}},j,N],"zappie-internal":[x,j,N,{name:"zappie/internal-sdk",files:["packages/sdk/src/**/*.{ts,tsx}"],rules:{"zappie/no-direct-api":"off"}},{name:"zappie/internal-auth-ui",files:["packages/foundations/src/otp/**/*.{ts,tsx}"],rules:{"zappie/no-custom-auth":"off"}},{name:"zappie/internal-money",files:["packages/sdk/src/money.ts","packages/sdk/src/internal/money-math.ts","packages/sdk/src/cart/branch.ts"],rules:{"zappie/no-money-literals":"off","zappie/use-format-money":"off"}},{name:"zappie/internal-overlay",files:["packages/foundations/src/overlay/**/*.{ts,tsx}"],rules:{"zappie/overlay-primitive":"off"}},{name:"zappie/internal-overlay-tests",files:["themes/base/src/cart/cart-review.test.tsx","themes/base/src/cart/core-cart.test.tsx","themes/base/src/cart/fulfilment.test.tsx","themes/base/src/cart/theme-class.test.tsx"],rules:{"zappie/overlay-primitive":"off"}}]};var Xt=z;export{F as cartViaSdk,K as checkoutViaSdk,Xt as default,U as imagePriority,$ as itemLinkPrefetch,q as noCustomAuth,Z as noDirectApi,W as noMoneyLiterals,_ as noTrackingScripts,G as overlayPrimitive,re as useFormatMoney,ie as verifyMarkers};
1
+ import De from"@typescript-eslint/parser";import ae from"eslint-plugin-jsx-a11y";import Pe from"eslint-plugin-react-hooks";import{ESLintUtils as pe}from"@typescript-eslint/utils";var p=pe.RuleCreator(e=>`https://docs.zappie.ai/lint/${e}`);var ce=/^(cart|basket|bag)(Items|Lines|Count|Total)?$/i,me=/cart|basket|bag/i;function b(e){if(e.type==="Identifier")return e.name;if(e.type==="MemberExpression"&&e.property.type==="Identifier")return e.property.name}function le(e){return e.type==="Identifier"?e.name:e.type==="ArrayPattern"&&e.elements[0]?.type==="Identifier"?e.elements[0].name:void 0}function ue(e){let r=b(e.callee)==="useReducer"?e.arguments[1]:e.arguments[0];return!!(r&&r.type!=="SpreadElement"&&(r.type==="ArrayExpression"||r.type==="Literal"&&typeof r.value=="number"))}var O=p({name:"cart-via-sdk",meta:{type:"problem",docs:{description:"Keep your cart state in the SDK."},schema:[],messages:{cart:"Use useCart(), useCartLine() or the SDK client cart. Keep cart state and storage in the SDK."}},defaultOptions:[],create(e){return{CallExpression(r){let i=b(r.callee),t=r.arguments[0];["getItem","setItem","removeItem","open"].includes(i??"")&&t?.type==="Literal"&&typeof t.value=="string"&&me.test(t.value)&&e.report({node:r,messageId:"cart"})},VariableDeclarator(r){if(r.init?.type!=="CallExpression")return;let i=le(r.id),t=b(r.init.callee);i&&ce.test(i)&&["useState","useReducer","createContext"].includes(t??"")&&ue(r.init)&&e.report({node:r,messageId:"cart"}),i&&/cart/i.test(i)&&["create","createStore","atom"].includes(t??"")&&e.report({node:r,messageId:"cart"})}}}});import{minimatch as fe}from"minimatch";var de=/(?:api|accounts|checkout|edge)\.zappie\.ai|\/storefront\/v1|\/customer\/graphql/i,ye=/googletagmanager\.com|google-analytics\.com|connect\.facebook\.net|analytics\.tiktok\.com|static\.hotjar\.com|cdn\.segment\.com|plausible\.io|clarity\.ms/i,F=/checkout\.paystack\.com|flutterwave|(?:^|\.)stripe\.com/i;function d(e){if(e?.type==="Literal"&&typeof e.value=="string")return e.value;if(e?.type==="TemplateLiteral")return e.quasis.map(r=>r.value.cooked??r.value.raw).join("")}function l(e,r){return e.attributes.find(i=>i.type==="JSXAttribute"&&i.name.type==="JSXIdentifier"&&i.name.name.toLowerCase()===r.toLowerCase())}function u(e){if(e?.value){if(e.value.type==="Literal")return typeof e.value.value=="string"?e.value.value:void 0;if(e.value.type==="JSXExpressionContainer")return d(e.value.expression)}}var R=e=>de.test(e),J=e=>ye.test(e),X=e=>e==="@zappie/sdk/internal"||e.startsWith("@zappie/sdk/internal/");function D(e,r){let i=e.replaceAll("\\","/");return r.some(t=>fe(i,t,{matchBase:!0}))}var Se=new Set(["cc-number","cc-exp","cc-csc","cc-name"]),P=/card.?number|cvv|cvc|expiry/i;function ge(e){return e.startsWith("@paystack/")||e==="react-paystack"||e==="flutterwave-react-v3"||e.startsWith("@stripe/")}var K=p({name:"checkout-via-sdk",meta:{type:"problem",docs:{description:"Open checkout through the Zappie SDK."},schema:[],messages:{checkout:"Use createCheckout().open() from @zappie/sdk/checkout. Let checkout collect payment details."}},defaultOptions:[],create(e){return{ImportDeclaration(r){ge(r.source.value)&&e.report({node:r.source,messageId:"checkout"})},JSXOpeningElement(r){let i=r.name.type==="JSXIdentifier"?r.name.name.toLowerCase():"",t=u(l(r,"autocomplete")),n=u(l(r,"name")),a=u(l(r,"id")),s=u(l(r,i==="iframe"?"src":"href")),o=i==="input"&&(t!==void 0&&Se.has(t.toLowerCase())||n!==void 0&&P.test(n)||a!==void 0&&P.test(a)),c=(i==="iframe"||i==="a")&&s!==void 0&&F.test(s);(o||c)&&e.report({node:r,messageId:"checkout"})}}}});function he(e){let r=e;for(;r?.parent;){let i=r.parent;if((i.type==="ArrowFunctionExpression"||i.type==="FunctionExpression")&&i.parent?.type==="CallExpression"&&i.parent.callee.type==="MemberExpression"&&i.parent.callee.property.type==="Identifier"&&i.parent.callee.property.name==="map")return i;r=i}}function Te(e,r){let i=e.getText(r.parent);if(/\.slice\(\s*0\s*,\s*1\s*\)\.map/.test(i))return!0;let t=r.params[1];return t?.type!=="Identifier"?!1:new RegExp(`${t.name}\\s*(?:={2,3}\\s*0|<\\s*1)`).test(e.getText(r))}var U=p({name:"image-priority",meta:{type:"problem",docs:{description:"Reserve priority for the first phone screen."},schema:[],messages:{mapped:"Remove priority from repeated images or limit it to the first.",routeLimit:"Keep no more than two priority images in a route."}},defaultOptions:[],create(e){let r=[];return{JSXOpeningElement(i){let t=l(i,"priority"),n=t?.value?.type==="JSXExpressionContainer"&&t.value.expression.type==="Literal"&&t.value.expression.value===!1;if(i.name.type!=="JSXIdentifier"||i.name.name!=="Image"||!t||n)return;r.push(i);let a=he(i);a&&!Te(e.sourceCode,a)&&e.report({node:t,messageId:"mapped"})},"Program:exit"(){if(/(?:^|\/)routes?\/[^/]+\.[jt]sx$/.test(e.filename.replaceAll("\\","/"))&&r.length>2)for(let i of r.slice(2))e.report({node:i,messageId:"routeLimit"})}}}});function Ee(e){let r=e?.value?.type==="JSXExpressionContainer"?e.value.expression:void 0;if(r?.type==="CallExpression"&&r.callee.type==="Identifier"&&r.callee.name==="itemHref")return!0;let i=u(e);return i!==void 0&&/^\/item(?:\/|$)/.test(i)}var $=p({name:"item-link-prefetch",meta:{type:"problem",docs:{description:"Prefetch item routes before shoppers tap."},schema:[],messages:{prefetch:'Add prefetch="viewport" so this item route is ready to tap.'}},defaultOptions:[],create(e){let r=new Set;return{ImportDeclaration(i){if(["react-router","react-router-dom"].includes(i.source.value))for(let t of i.specifiers)t.type==="ImportSpecifier"&&t.imported.type==="Identifier"&&["Link","NavLink"].includes(t.imported.name)&&r.add(t.local.name)},JSXOpeningElement(i){i.name.type==="JSXIdentifier"&&r.has(i.name.name)&&Ee(l(i,"to"))&&!l(i,"prefetch")&&e.report({node:i,messageId:"prefetch"})}}}});var xe=["**/*.server.*","**/server/**"],I=/otp|verify.?code|access.?token|refresh.?token|id.?token/i,B=new Set(["localStorage","sessionStorage"]),ve=new Set(["window","globalThis","self"]);function V(e){return e.type==="Identifier"?B.has(e.name):e.type==="MemberExpression"&&!e.computed&&e.object.type==="Identifier"&&ve.has(e.object.name)&&e.property.type==="Identifier"&&B.has(e.property.name)}function ke(e){return!e.computed&&e.property.type==="Identifier"?e.property.name:d(e.property)}function be(e){if(e==="jose"||e==="jsonwebtoken")return"server";if(e==="next-auth"||e.startsWith("next-auth/")||e.startsWith("@auth/")||e==="better-auth"||e.startsWith("better-auth/")||e==="lucia"||e.startsWith("@clerk/")||e==="firebase/auth"||e.startsWith("@supabase/auth-"))return"all"}var q=p({name:"no-custom-auth",meta:{type:"problem",docs:{description:"Sign customers in through the Zappie SDK."},schema:[{type:"object",properties:{serverFiles:{type:"array",items:{type:"string"},uniqueItems:!0}},additionalProperties:!1}],messages:{auth:"Use createCustomer().signIn() from @zappie/sdk/customer to sign customers in."}},defaultOptions:[{}],create(e,[r]){let i=D(e.filename,r.serverFiles??xe);return{ImportDeclaration(t){let n=be(t.source.value);(n==="all"||n==="server"&&!i)&&e.report({node:t.source,messageId:"auth"})},JSXOpeningElement(t){if(t.name.type!=="JSXIdentifier"||t.name.name.toLowerCase()!=="input")return;let n=u(l(t,"type")),a=u(l(t,"autocomplete"));(n?.toLowerCase()==="password"||a!==void 0&&["one-time-code","current-password","new-password"].includes(a.toLowerCase()))&&e.report({node:t,messageId:"auth"})},CallExpression(t){if(t.callee.type!=="MemberExpression"||!V(t.callee.object))return;let n=t.arguments[0],a=n?.type==="Identifier"?n.name:d(n??null);a&&I.test(a)&&e.report({node:t,messageId:"auth"})},AssignmentExpression(t){if(t.left.type==="MemberExpression"&&V(t.left.object)){let s=ke(t.left);s&&I.test(s)&&e.report({node:t,messageId:"auth"});return}let n=t.left.type==="MemberExpression"&&t.left.object.type==="Identifier"&&t.left.object.name==="document"&&t.left.property.type==="Identifier"&&t.left.property.name==="cookie",a=d(t.right);n&&a&&I.test(a)&&e.report({node:t,messageId:"auth"})}}}});function Ie(e){return/checkout\.zappie\.ai|\/checkout(?:\/|$)/i.test(e)?"checkout":/accounts\.zappie\.ai|\/customer\/graphql/i.test(e)?"customer":/\/storefront\/v1/i.test(e)?"storefront":"direct"}var Z=p({name:"no-direct-api",meta:{type:"problem",docs:{description:"Call Zappie through the SDK."},schema:[],messages:{direct:"Call the matching public @zappie/sdk client to reach this Zappie endpoint.",internal:"Use a public SDK entry. Only Zappie-owned packages can import @zappie/sdk/internal.",storefront:"Use createZappie(config).store or .menu to read storefront data.",checkout:"Use createCheckout().open() from @zappie/sdk/checkout.",customer:"Use createCustomer() from @zappie/sdk/customer."}},defaultOptions:[],create(e){let r=i=>{if(i.parent.type==="ImportDeclaration")return;let t=d(i);t&&R(t)&&e.report({node:i,messageId:Ie(t)})};return{Literal:r,TemplateLiteral:r,ImportDeclaration(i){X(i.source.value)&&e.report({node:i.source,messageId:"internal"})}}}});var Le=new Set(["react"]),we="React",W=p({name:"no-lazy-overlay",meta:{type:"problem",docs:{description:"Load overlays with deferred() from @zappie/foundations, not React.lazy or Suspense."},schema:[],messages:{lazy:"Load this component with deferred() from @zappie/foundations. React.lazy suspends, and React holds a suspended reveal back for about 300 ms, so the overlay opens late.",suspense:"Remove Suspense and load the component with deferred() from @zappie/foundations. It renders as soon as its code arrives and never suspends."}},defaultOptions:[],create(e){let r=new Set,i=new Set,t=new Set([we]),n=(a,s,o)=>a.type==="Identifier"&&t.has(a.name)&&s.type==="Identifier"&&s.name===o;return{ImportDeclaration(a){if(Le.has(a.source.value))for(let s of a.specifiers){if(s.type!=="ImportSpecifier"){t.add(s.local.name);continue}let o=s.imported.type==="Identifier"?s.imported.name:s.imported.value;o==="lazy"?r.add(s.local.name):o==="Suspense"&&i.add(s.local.name)}},CallExpression(a){let s=a.callee;(s.type==="Identifier"&&r.has(s.name)||s.type==="MemberExpression"&&!s.computed&&n(s.object,s.property,"lazy"))&&e.report({node:a,messageId:"lazy"})},JSXOpeningElement(a){let s=a.name;(s.type==="JSXIdentifier"&&i.has(s.name)||s.type==="JSXMemberExpression"&&s.object.type==="JSXIdentifier"&&t.has(s.object.name)&&s.property.name==="Suspense")&&e.report({node:a,messageId:"suspense"})}}}});import{ESLintUtils as Ce}from"@typescript-eslint/utils";var ze=/[₦€£]\s?\d|\$\s?\d|NGN\s?\d|\d+(?:\.\d{2})?\s?(?:naira|NGN)/i;function g(e){return e.trim()!=="$1"&&ze.test(e)}function f(e){if(e.type==="MemberExpression"){if(!e.computed&&e.property.type==="Identifier")return e.property.name;if(e.computed&&e.property.type==="Literal")return typeof e.property.value=="string"?e.property.value:void 0}}var S=e=>f(e)==="amountMinor";function Me(e){return e.type==="MemberExpression"&&S(e)?e.object:void 0}function y(e){if(!(e.type!=="BinaryExpression"||e.operator!=="/"||e.right.type!=="Literal"||e.right.value!==100))return Me(e.left)}function h(e){try{return Ce.getParserServices(e,!0)}catch{return}}function Ne(e){return e.getName().includes("MoneyBrand")?(e.getDeclarations()??[]).some(r=>{let i=r.getSourceFile().fileName.replaceAll("\\","/");return i.endsWith("/packages/sdk/src/money.ts")||/\/node_modules\/@zappie\/sdk\/.*\.d\.ts$/.test(i)}):!1}function T(e,r){if(!e?.program)return!1;let i=e.program.getTypeChecker(),t=e.esTreeNodeToTSNodeMap.get(r),n=i.getTypeAtLocation(t);return(n.isUnionOrIntersection()?n.types:[n]).some(s=>s.getProperties().some(o=>Ne(o)))}function E(e){return e?.type!=="ObjectExpression"?!1:e.properties.some(r=>r.type==="Property"&&!r.computed&&(r.key.type==="Identifier"&&r.key.name==="style"||r.key.type==="Literal"&&r.key.value==="style")&&r.value.type==="Literal"&&r.value.value==="currency")}var _=p({name:"no-money-literals",meta:{type:"problem",docs:{description:"Use SDK prices and totals in your storefront."},schema:[],messages:{arithmetic:"Use cart.subtotal, line.lineSubtotal, quote.subtotal, quote.lines totals or quote.total. Let the SDK calculate amounts.",literal:"Render SDK Money with format.money() or <Money> instead of a hard-coded amount.",scale:"Use format.money() to display amountMinor. Currencies use 0, 2, 3 or 4 minor-unit digits."}},defaultOptions:[],create(e){let r=h(e),i=t=>S(t)||T(r,t);return{Literal(t){typeof t.value=="string"&&t.parent.type!=="ImportDeclaration"&&g(t.value)&&e.report({node:t,messageId:"literal"})},JSXText(t){g(t.value)&&e.report({node:t,messageId:"literal"})},BinaryExpression(t){["+","-","*","/"].includes(t.operator)&&(y(t)&&t.parent.type==="MemberExpression"&&t.parent.object===t&&f(t.parent)==="toFixed"||(i(t.left)||i(t.right))&&e.report({node:t,messageId:"arithmetic"}))},TemplateLiteral(t){for(let n of t.quasis)g(n.value.cooked??n.value.raw)&&e.report({node:n,messageId:"literal"})},CallExpression(t){t.callee.type==="MemberExpression"&&f(t.callee)==="toFixed"&&t.arguments[0]?.type==="Literal"&&t.arguments[0].value===2&&y(t.callee.object)&&e.report({node:t,messageId:"scale"})}}}});var je=["react-ga4","react-facebook-pixel","@vercel/analytics","posthog-js"];function Ae(e){return je.includes(e)||e.startsWith("@segment/")}var H=p({name:"no-tracking-scripts",meta:{type:"problem",docs:{description:"Forward analytics through Zappie."},schema:[],messages:{tracking:"Configure analytics.forward in zappie.config.ts to forward analytics. Remove the tracking script."}},defaultOptions:[],create(e){let r=i=>{if(i.parent.type==="ImportDeclaration")return;let t=d(i);t&&J(t)&&e.report({node:i,messageId:"tracking"})};return{Literal:r,TemplateLiteral:r,ImportDeclaration(i){Ae(i.source.value)&&e.report({node:i.source,messageId:"tracking"})},CallExpression(i){let t=i.callee,n=t.type==="Identifier"&&["gtag","fbq"].includes(t.name),a=t.type==="MemberExpression"&&!t.computed&&t.object.type==="Identifier"&&t.object.name==="dataLayer"&&t.property.type==="Identifier"&&t.property.name==="push";(n||a)&&e.report({node:i,messageId:"tracking"})}}}});var Oe=["modal","sheet","drawer","dialog","item","cart","cart_sheet","cart_edit","cart_clear","search","catalogue_search","catalogue_categories","product_note","modal_id"];function L(e){if(e.type==="Identifier")return e.name;if(e.type==="MemberExpression"&&e.property.type==="Identifier")return e.property.name}function G(e,r){return e.type==="Literal"&&typeof e.value=="string"?[...r].some(i=>new RegExp(`[?&]${i}=`).test(e.value)):e.type==="TemplateLiteral"?e.quasis.some(i=>G({...i,type:"Literal",value:i.value.raw},r)):e.type==="ObjectExpression"?e.properties.some(i=>i.type==="Property"&&(i.key.type==="Identifier"&&r.has(i.key.name)||i.key.type==="Literal"&&typeof i.key.value=="string"&&r.has(i.key.value))):!1}var Q=p({name:"overlay-primitive",meta:{type:"problem",docs:{description:"Use Overlay to keep dialogs and sheets in the URL."},schema:[{type:"object",properties:{keys:{type:"array",items:{type:"string"}}},additionalProperties:!1}],messages:{overlay:"Use Overlay or useOverlay for dialogs and sheets in the URL. They handle Back, Forward and focus restoration."}},defaultOptions:[{}],create(e,[r]){let i=new Set([...Oe,...r.keys??[]]),t=new Set,n=a=>{let s=e.sourceCode.getText(a);return s.includes("useSearchParams(")||[...t].some(o=>new RegExp(`\\b${o}\\b`).test(s))};return{VariableDeclarator(a){a.init?.type==="CallExpression"&&L(a.init.callee)==="useSearchParams"?a.id.type==="Identifier"?t.add(a.id.name):a.id.type==="ArrayPattern"&&a.id.elements[0]?.type==="Identifier"&&t.add(a.id.elements[0].name):a.id.type==="Identifier"&&a.init&&n(a.init)&&t.add(a.id.name)},CallExpression(a){let s=L(a.callee);(s==="setSearchParams"||s==="navigate"||s==="pushState"||s==="replaceState"||a.callee.type==="CallExpression"&&L(a.callee.callee)==="useNavigate")&&a.arguments.some(c=>c.type!=="SpreadElement"&&G(c,i))&&e.report({node:a,messageId:"overlay"})},JSXOpeningElement(a){let s=a.name.type==="JSXIdentifier"&&a.name.name==="dialog",o=a.attributes.some(m=>m.type==="JSXAttribute"&&m.name.name==="role"&&m.value?.type==="Literal"&&m.value.value==="dialog"),c=a.attributes.find(m=>m.type==="JSXAttribute"&&m.name.name==="open");(s||o)&&c?.type==="JSXAttribute"&&c.value?.type==="JSXExpressionContainer"&&n(c.value.expression)&&e.report({node:a,messageId:"overlay"})}}}});function w(e){return e.body.find(r=>r.type==="ImportDeclaration"&&r.source.value==="@zappie/sdk")}function C(e){return e?.specifiers.find(r=>r.type==="ImportSpecifier"&&r.imported.type==="Identifier"&&r.imported.name==="format")}function Fe(e,r,i,t){let n=w(i);if(C(n)?.local.name===t)return;let a=t==="format"?"format":`format as ${t}`,s=n?.specifiers.filter(m=>m.type==="ImportSpecifier");if(n&&n.importKind!=="type"&&s?.length){let m=s.at(-1);if(m)return e.insertTextAfter(m,`, ${a}`)}let o=i.body[0],c=`import { ${a} } from "@zappie/sdk";
2
+ `;return o?e.insertTextBefore(o,c):e.insertTextAfter(i,c)}function z(e,r,i,t,n,a){let s=[e.replaceText(t,`${a}.money(${r.getText(n)})`)],o=Fe(e,r,i,a);return o&&s.push(o),s}function Y(e,r,i){if(C(w(r)))return"imported";let t=e.getScope(i);for(;t;){if(t.variables.some(n=>n.name==="format"))return"conflict";t=t.upper}return"available"}function ee(e){return C(w(e))?.local.name}function te(e,r){let i=new Set,t=e.getScope(r);for(;t;){for(let n of t.variables)i.add(n.name);t=t.upper}for(let n of["zappieFormat","zappieMoneyFormat","formatMoneyValue"])if(!i.has(n))return n;return"zappieSdkFormat"}function re(e){let r=e.type==="NewExpression"||e.type==="CallExpression"?e.callee:void 0;return r?.type==="MemberExpression"&&r.object.type==="Identifier"&&r.object.name==="Intl"&&f(r)==="NumberFormat"}function Re(e){if(e.callee.type==="MemberExpression"){if(f(e.callee)==="toLocaleString")return y(e.callee.object);if(f(e.callee)==="format")return y(e.arguments[0]??e)}}function Je(e){let r=e.parent,i=r.type==="MemberExpression"?r.parent:void 0;return!!(i?.type==="CallExpression"&&i.callee===r&&f(r)==="format"&&y(i.arguments[0]??i))}var ie=p({name:"use-format-money",meta:{type:"problem",docs:{description:"Format SDK Money with format.money() or <Money>."},fixable:"code",hasSuggestions:!0,schema:[],messages:{currency:"Use format.money(). Intl currency defaults can differ from the API minor-unit digits.",direct:"Render SDK Money with format.money(value) or <Money value={value}>.",minor:"Use format.money(value). Dividing by 100 gives the wrong amount for currencies with 0, 3 or 4 minor-unit digits.",useAlias:"Import format with an unused alias and format this Money value."}},defaultOptions:[],create(e){let r=e.sourceCode,i=h(e),t,n=(s,o,c)=>{let m={node:s,messageId:"minor"};if(Y(r,t,s)==="conflict"){let v=te(r,s);e.report({...m,suggest:[{messageId:"useAlias",fix:k=>z(k,r,t,o,c,v)}]});return}e.report({...m,fix:v=>{let k=ee(t)??"format";return z(v,r,t,o,c,k)}})},a=s=>{let o=Re(s);return o?(n(s,s,o),!0):!1};return{Program(s){t=s},NewExpression(s){re(s)&&E(s.arguments[1])&&!Je(s)&&e.report({node:s,messageId:"currency"})},CallExpression(s){if(!a(s)){if(re(s)&&E(s.arguments[1])){e.report({node:s,messageId:"currency"});return}s.callee.type==="MemberExpression"&&f(s.callee)==="toLocaleString"&&s.arguments.some(E)&&e.report({node:s,messageId:"currency"})}},TemplateLiteral(s){for(let o of s.expressions){let c=y(o);c?n(o,o,c):S(o)&&e.report({node:o,messageId:"minor"})}},JSXExpressionContainer(s){s.parent.type==="JSXAttribute"||s.expression.type==="JSXEmptyExpression"||(y(s.expression)||S(s.expression)?e.report({node:s.expression,messageId:"minor"}):T(i,s.expression)&&e.report({node:s.expression,messageId:"direct"}))}}}});var M={add:{name:"data-zappie-action",text:'data-zappie-action="add-to-cart"'},checkout:{name:"data-zappie-checkout",text:"data-zappie-checkout"},signin:{name:"data-zappie-signin",text:"data-zappie-signin"}};function Xe(e,r){let i=r.value?e.getText(r.value):"";if(/\b(?:cart(?:\.actions)?\.add)\s*\(/.test(i))return"add";if(/\b(?:checkout|createCheckout\([^)]*\))\.open\s*\(/.test(i))return"checkout";if(/\b(?:auth|createCustomer\([^)]*\))\.signIn\s*\(/.test(i))return"signin"}var se=p({name:"verify-markers",meta:{type:"suggestion",docs:{description:"Mark shopping actions so verify can find them."},fixable:"code",schema:[],messages:{marker:"Add {{marker}} so zappie verify can find this shopping action."}},defaultOptions:[],create(e){return{JSXOpeningElement(r){let i=r.attributes.find(n=>n.type==="JSXAttribute"&&n.name.name==="onClick");if(!i)return;let t=Xe(e.sourceCode,i);!t||r.attributes.some(n=>n.type==="JSXAttribute"&&n.name.name===M[t].name)||e.report({node:i,messageId:"marker",data:{marker:M[t].text},fix:n=>n.insertTextAfter(r.name,` ${M[t].text}`)})}}}});var ne={"no-direct-api":Z,"no-tracking-scripts":H,"checkout-via-sdk":K,"no-custom-auth":q,"no-money-literals":_,"use-format-money":ie,"overlay-primitive":Q,"cart-via-sdk":O,"verify-markers":se,"item-link-prefetch":$,"image-priority":U,"no-lazy-overlay":W},oe=Object.fromEntries(Object.keys(ne).map(e=>[`zappie/${e}`,e==="verify-markers"?"warn":"error"])),Ke={...oe,"zappie/verify-markers":"error"},A={meta:{name:"eslint-plugin-zappie",version:"0.0.0"},rules:ne,configs:{}},x={name:"zappie/rules",files:["**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}"],languageOptions:{parser:De},plugins:{zappie:A,"jsx-a11y":ae,"react-hooks":Pe},rules:{...ae.flatConfigs.recommended.rules,"react-hooks/rules-of-hooks":"error",...oe}},N={name:"zappie/search-dialogs",files:["**/search/**/*.{jsx,tsx}","**/*search*.{jsx,tsx}"],rules:{"jsx-a11y/no-autofocus":"off"}},j={name:"zappie/typed",files:["**/*.{ts,tsx,mts,cts}"],languageOptions:{parserOptions:{projectService:!0}}};A.configs={recommended:[x,j,N],strict:[{...x,rules:{...x.rules,...Ke}},j,N],"zappie-internal":[x,j,N,{name:"zappie/internal-sdk",files:["packages/sdk/src/**/*.{ts,tsx}"],rules:{"zappie/no-direct-api":"off"}},{name:"zappie/internal-auth-ui",files:["packages/foundations/src/otp/**/*.{ts,tsx}"],rules:{"zappie/no-custom-auth":"off"}},{name:"zappie/internal-money",files:["packages/sdk/src/money.ts","packages/sdk/src/internal/money-math.ts","packages/sdk/src/cart/branch.ts","packages/sdk/src/pricing/index.ts"],rules:{"zappie/no-money-literals":"off","zappie/use-format-money":"off"}},{name:"zappie/internal-overlay",files:["packages/foundations/src/overlay/**/*.{ts,tsx}"],rules:{"zappie/overlay-primitive":"off"}},{name:"zappie/internal-overlay-tests",files:["themes/base/src/cart/cart-review.test.tsx","themes/base/src/cart/core-cart.test.tsx","themes/base/src/cart/fulfilment.test.tsx","themes/base/src/cart/theme-class.test.tsx"],rules:{"zappie/overlay-primitive":"off"}}]};var Bt=A;export{O as cartViaSdk,K as checkoutViaSdk,Bt as default,U as imagePriority,$ as itemLinkPrefetch,q as noCustomAuth,Z as noDirectApi,W as noLazyOverlay,_ as noMoneyLiterals,H as noTrackingScripts,Q as overlayPrimitive,ie as useFormatMoney,se as verifyMarkers};
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/create-rule.ts","../src/rules/cart-via-sdk.ts","../src/matchers.ts","../src/rules/checkout-via-sdk.ts","../src/rules/image-priority.ts","../src/rules/item-link-prefetch.ts","../src/rules/no-custom-auth.ts","../src/rules/no-direct-api.ts","../src/money-rule-utils.ts","../src/rules/no-money-literals.ts","../src/rules/no-tracking-scripts.ts","../src/rules/overlay-primitive.ts","../src/format-fix.ts","../src/rules/use-format-money.ts","../src/rules/verify-markers.ts"],"sourcesContent":["import parser from \"@typescript-eslint/parser\";\nimport jsxA11y from \"eslint-plugin-jsx-a11y\";\nimport reactHooks from \"eslint-plugin-react-hooks\";\nimport { cartViaSdk } from \"./rules/cart-via-sdk\";\nimport { checkoutViaSdk } from \"./rules/checkout-via-sdk\";\nimport { imagePriority } from \"./rules/image-priority\";\nimport { itemLinkPrefetch } from \"./rules/item-link-prefetch\";\nimport { noCustomAuth } from \"./rules/no-custom-auth\";\nimport { noDirectApi } from \"./rules/no-direct-api\";\nimport { noMoneyLiterals } from \"./rules/no-money-literals\";\nimport { noTrackingScripts } from \"./rules/no-tracking-scripts\";\nimport { overlayPrimitive } from \"./rules/overlay-primitive\";\nimport { useFormatMoney } from \"./rules/use-format-money\";\nimport { verifyMarkers } from \"./rules/verify-markers\";\n\nconst rules = {\n \"no-direct-api\": noDirectApi,\n \"no-tracking-scripts\": noTrackingScripts,\n \"checkout-via-sdk\": checkoutViaSdk,\n \"no-custom-auth\": noCustomAuth,\n \"no-money-literals\": noMoneyLiterals,\n \"use-format-money\": useFormatMoney,\n \"overlay-primitive\": overlayPrimitive,\n \"cart-via-sdk\": cartViaSdk,\n \"verify-markers\": verifyMarkers,\n \"item-link-prefetch\": itemLinkPrefetch,\n \"image-priority\": imagePriority,\n};\n\nexport type ZappieFlatConfig = Record<string, unknown>;\n\nconst enabled = Object.fromEntries(\n Object.keys(rules).map((name) => [\n `zappie/${name}`,\n name === \"verify-markers\" ? \"warn\" : \"error\",\n ]),\n);\nconst strictRules = { ...enabled, \"zappie/verify-markers\": \"error\" };\n\nconst plugin = {\n meta: { name: \"eslint-plugin-zappie\", version: \"0.0.0\" },\n rules,\n configs: {} as Record<string, ZappieFlatConfig[]>,\n};\n\nconst base = {\n name: \"zappie/rules\",\n files: [\"**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}\"],\n languageOptions: { parser },\n plugins: { zappie: plugin, \"jsx-a11y\": jsxA11y, \"react-hooks\": reactHooks },\n rules: {\n ...jsxA11y.flatConfigs.recommended.rules,\n \"react-hooks/rules-of-hooks\": \"error\",\n ...enabled,\n },\n};\nconst searchDialogs = {\n name: \"zappie/search-dialogs\",\n files: [\"**/search/**/*.{jsx,tsx}\", \"**/*search*.{jsx,tsx}\"],\n rules: { \"jsx-a11y/no-autofocus\": \"off\" },\n};\n\n// JavaScript files (eslint.config.mjs among them) usually sit outside the\n// tsconfig, where the project service refuses to parse them. They get the\n// syntactic money checks only.\nconst typed = {\n name: \"zappie/typed\",\n files: [\"**/*.{ts,tsx,mts,cts}\"],\n languageOptions: { parserOptions: { projectService: true } },\n};\n\nplugin.configs = {\n recommended: [base, typed, searchDialogs],\n strict: [\n { ...base, rules: { ...base.rules, ...strictRules } },\n typed,\n searchDialogs,\n ],\n \"zappie-internal\": [\n base,\n typed,\n searchDialogs,\n {\n name: \"zappie/internal-sdk\",\n files: [\"packages/sdk/src/**/*.{ts,tsx}\"],\n rules: { \"zappie/no-direct-api\": \"off\" },\n },\n {\n name: \"zappie/internal-auth-ui\",\n files: [\"packages/foundations/src/otp/**/*.{ts,tsx}\"],\n rules: { \"zappie/no-custom-auth\": \"off\" },\n },\n {\n name: \"zappie/internal-money\",\n files: [\n \"packages/sdk/src/money.ts\",\n \"packages/sdk/src/internal/money-math.ts\",\n // Its own arithmetic: importing money-math here splits it out of the core bundle.\n \"packages/sdk/src/cart/branch.ts\",\n ],\n rules: {\n \"zappie/no-money-literals\": \"off\",\n \"zappie/use-format-money\": \"off\",\n },\n },\n {\n name: \"zappie/internal-overlay\",\n files: [\"packages/foundations/src/overlay/**/*.{ts,tsx}\"],\n rules: { \"zappie/overlay-primitive\": \"off\" },\n },\n {\n name: \"zappie/internal-overlay-tests\",\n files: [\n \"themes/base/src/cart/cart-review.test.tsx\",\n \"themes/base/src/cart/core-cart.test.tsx\",\n \"themes/base/src/cart/fulfilment.test.tsx\",\n \"themes/base/src/cart/theme-class.test.tsx\",\n ],\n rules: { \"zappie/overlay-primitive\": \"off\" },\n },\n ],\n};\n\nexport {\n cartViaSdk,\n checkoutViaSdk,\n imagePriority,\n itemLinkPrefetch,\n noCustomAuth,\n noDirectApi,\n noMoneyLiterals,\n noTrackingScripts,\n overlayPrimitive,\n useFormatMoney,\n verifyMarkers,\n};\nexport default plugin;\n","import { ESLintUtils } from \"@typescript-eslint/utils\";\n\nexport const createRule = ESLintUtils.RuleCreator(\n (name) => `https://docs.zappie.ai/lint/${name}`,\n);\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\n\nconst cartName = /^(cart|basket|bag)(Items|Lines|Count|Total)?$/i;\nconst storageKey = /cart|basket|bag/i;\n\nfunction calleeName(node: TSESTree.Node): string | undefined {\n if (node.type === \"Identifier\") {\n return node.name;\n }\n if (node.type === \"MemberExpression\" && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n return undefined;\n}\n\nfunction variableName(node: TSESTree.BindingName): string | undefined {\n if (node.type === \"Identifier\") {\n return node.name;\n }\n return node.type === \"ArrayPattern\" && node.elements[0]?.type === \"Identifier\"\n ? node.elements[0].name\n : undefined;\n}\n\nfunction cartInitial(node: TSESTree.CallExpression): boolean {\n const value =\n calleeName(node.callee) === \"useReducer\"\n ? node.arguments[1]\n : node.arguments[0];\n return Boolean(\n value &&\n value.type !== \"SpreadElement\" &&\n (value.type === \"ArrayExpression\" ||\n (value.type === \"Literal\" && typeof value.value === \"number\")),\n );\n}\n\nexport const cartViaSdk = createRule<[], \"cart\">({\n name: \"cart-via-sdk\",\n meta: {\n type: \"problem\",\n docs: { description: \"Keep your cart state in the SDK.\" },\n schema: [],\n messages: {\n cart: \"Use useCart(), useCartLine() or the SDK client cart. Keep cart state and storage in the SDK.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node) {\n const method = calleeName(node.callee);\n const key = node.arguments[0];\n if (\n [\"getItem\", \"setItem\", \"removeItem\", \"open\"].includes(method ?? \"\") &&\n key?.type === \"Literal\" &&\n typeof key.value === \"string\" &&\n storageKey.test(key.value)\n ) {\n context.report({ node, messageId: \"cart\" });\n }\n },\n VariableDeclarator(node) {\n if (node.init?.type !== \"CallExpression\") {\n return;\n }\n const id = variableName(node.id);\n const call = calleeName(node.init.callee);\n if (\n id &&\n cartName.test(id) &&\n [\"useState\", \"useReducer\", \"createContext\"].includes(call ?? \"\") &&\n cartInitial(node.init)\n ) {\n context.report({ node, messageId: \"cart\" });\n }\n if (\n id &&\n /cart/i.test(id) &&\n [\"create\", \"createStore\", \"atom\"].includes(call ?? \"\")\n ) {\n context.report({ node, messageId: \"cart\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { minimatch } from \"minimatch\";\n\nconst endpointPattern =\n /(?:api|accounts|checkout|edge)\\.zappie\\.ai|\\/storefront\\/v1|\\/customer\\/graphql/i;\nconst trackingPattern =\n /googletagmanager\\.com|google-analytics\\.com|connect\\.facebook\\.net|analytics\\.tiktok\\.com|static\\.hotjar\\.com|cdn\\.segment\\.com|plausible\\.io|clarity\\.ms/i;\n\nexport const paymentHostPattern =\n /checkout\\.paystack\\.com|flutterwave|(?:^|\\.)stripe\\.com/i;\n\nexport function staticText(node: TSESTree.Node | null): string | undefined {\n if (node?.type === \"Literal\" && typeof node.value === \"string\") {\n return node.value;\n }\n if (node?.type === \"TemplateLiteral\") {\n return node.quasis\n .map((part) => part.value.cooked ?? part.value.raw)\n .join(\"\");\n }\n return undefined;\n}\n\nexport function jsxAttribute(\n node: TSESTree.JSXOpeningElement,\n name: string,\n): TSESTree.JSXAttribute | undefined {\n return node.attributes.find(\n (attribute): attribute is TSESTree.JSXAttribute =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.type === \"JSXIdentifier\" &&\n attribute.name.name.toLowerCase() === name.toLowerCase(),\n );\n}\n\nexport function jsxValue(node?: TSESTree.JSXAttribute): string | undefined {\n if (!node?.value) {\n return undefined;\n }\n if (node.value.type === \"Literal\") {\n return typeof node.value.value === \"string\" ? node.value.value : undefined;\n }\n if (node.value.type === \"JSXExpressionContainer\") {\n return staticText(node.value.expression);\n }\n return undefined;\n}\n\nexport const isZappieEndpoint = (value: string) => endpointPattern.test(value);\nexport const isTrackingEndpoint = (value: string) =>\n trackingPattern.test(value);\nexport const isInternalSdkImport = (value: string) =>\n value === \"@zappie/sdk/internal\" || value.startsWith(\"@zappie/sdk/internal/\");\n\nexport function isServerFile(filename: string, patterns: string[]): boolean {\n const path = filename.replaceAll(\"\\\\\", \"/\");\n return patterns.some((pattern) =>\n minimatch(path, pattern, { matchBase: true }),\n );\n}\n","import { createRule } from \"../create-rule\";\nimport { jsxAttribute, jsxValue, paymentHostPattern } from \"../matchers\";\n\nconst autocomplete = new Set([\"cc-number\", \"cc-exp\", \"cc-csc\", \"cc-name\"]);\nconst paymentField = /card.?number|cvv|cvc|expiry/i;\n\nfunction providerImport(value: string): boolean {\n return (\n value.startsWith(\"@paystack/\") ||\n value === \"react-paystack\" ||\n value === \"flutterwave-react-v3\" ||\n value.startsWith(\"@stripe/\")\n );\n}\n\nexport const checkoutViaSdk = createRule<[], \"checkout\">({\n name: \"checkout-via-sdk\",\n meta: {\n type: \"problem\",\n docs: { description: \"Open checkout through the Zappie SDK.\" },\n schema: [],\n messages: {\n checkout:\n \"Use createCheckout().open() from @zappie/sdk/checkout. Let checkout collect payment details.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n ImportDeclaration(node) {\n if (providerImport(node.source.value)) {\n context.report({ node: node.source, messageId: \"checkout\" });\n }\n },\n JSXOpeningElement(node) {\n const tag =\n node.name.type === \"JSXIdentifier\"\n ? node.name.name.toLowerCase()\n : \"\";\n const auto = jsxValue(jsxAttribute(node, \"autocomplete\"));\n const name = jsxValue(jsxAttribute(node, \"name\"));\n const id = jsxValue(jsxAttribute(node, \"id\"));\n const target = jsxValue(\n jsxAttribute(node, tag === \"iframe\" ? \"src\" : \"href\"),\n );\n const cardInput =\n tag === \"input\" &&\n ((auto !== undefined && autocomplete.has(auto.toLowerCase())) ||\n (name !== undefined && paymentField.test(name)) ||\n (id !== undefined && paymentField.test(id)));\n const providerTarget =\n (tag === \"iframe\" || tag === \"a\") &&\n target !== undefined &&\n paymentHostPattern.test(target);\n if (cardInput || providerTarget) {\n context.report({ node, messageId: \"checkout\" });\n }\n },\n };\n },\n});\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { jsxAttribute } from \"../matchers\";\n\ntype Callback = TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;\n\nfunction mappedBy(node: TSESTree.Node) {\n let current: TSESTree.Node | undefined = node;\n while (current?.parent) {\n const parent: TSESTree.Node = current.parent;\n if (\n (parent.type === \"ArrowFunctionExpression\" ||\n parent.type === \"FunctionExpression\") &&\n parent.parent?.type === \"CallExpression\" &&\n parent.parent.callee.type === \"MemberExpression\" &&\n parent.parent.callee.property.type === \"Identifier\" &&\n parent.parent.callee.property.name === \"map\"\n ) {\n return parent;\n }\n current = parent;\n }\n}\n\nfunction firstOnly(source: Readonly<TSESLint.SourceCode>, callback: Callback) {\n const text = source.getText(callback.parent);\n if (/\\.slice\\(\\s*0\\s*,\\s*1\\s*\\)\\.map/.test(text)) {\n return true;\n }\n const index = callback.params[1];\n if (index?.type !== \"Identifier\") {\n return false;\n }\n const first = new RegExp(`${index.name}\\\\s*(?:={2,3}\\\\s*0|<\\\\s*1)`);\n return first.test(source.getText(callback));\n}\n\nexport const imagePriority = createRule<[], \"mapped\" | \"routeLimit\">({\n name: \"image-priority\",\n meta: {\n type: \"problem\",\n docs: { description: \"Reserve priority for the first phone screen.\" },\n schema: [],\n messages: {\n mapped: \"Remove priority from repeated images or limit it to the first.\",\n routeLimit: \"Keep no more than two priority images in a route.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const images: TSESTree.JSXOpeningElement[] = [];\n return {\n JSXOpeningElement(node) {\n const priority = jsxAttribute(node, \"priority\");\n const disabled =\n priority?.value?.type === \"JSXExpressionContainer\" &&\n priority.value.expression.type === \"Literal\" &&\n priority.value.expression.value === false;\n if (\n node.name.type !== \"JSXIdentifier\" ||\n node.name.name !== \"Image\" ||\n !priority ||\n disabled\n ) {\n return;\n }\n images.push(node);\n const callback = mappedBy(node);\n if (callback && !firstOnly(context.sourceCode, callback)) {\n context.report({ node: priority, messageId: \"mapped\" });\n }\n },\n \"Program:exit\"() {\n if (\n /(?:^|\\/)routes?\\/[^/]+\\.[jt]sx$/.test(\n context.filename.replaceAll(\"\\\\\", \"/\"),\n ) &&\n images.length > 2\n ) {\n for (const node of images.slice(2)) {\n context.report({ node, messageId: \"routeLimit\" });\n }\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { jsxAttribute, jsxValue } from \"../matchers\";\n\nfunction targetsItem(attribute?: TSESTree.JSXAttribute) {\n const expression =\n attribute?.value?.type === \"JSXExpressionContainer\"\n ? attribute.value.expression\n : undefined;\n if (\n expression?.type === \"CallExpression\" &&\n expression.callee.type === \"Identifier\" &&\n expression.callee.name === \"itemHref\"\n ) {\n return true;\n }\n const target = jsxValue(attribute);\n return target !== undefined && /^\\/item(?:\\/|$)/.test(target);\n}\n\nexport const itemLinkPrefetch = createRule<[], \"prefetch\">({\n name: \"item-link-prefetch\",\n meta: {\n type: \"problem\",\n docs: { description: \"Prefetch item routes before shoppers tap.\" },\n schema: [],\n messages: {\n prefetch: 'Add prefetch=\"viewport\" so this item route is ready to tap.',\n },\n },\n defaultOptions: [],\n create(context) {\n const links = new Set<string>();\n return {\n ImportDeclaration(node) {\n if (![\"react-router\", \"react-router-dom\"].includes(node.source.value)) {\n return;\n }\n for (const item of node.specifiers) {\n if (\n item.type === \"ImportSpecifier\" &&\n item.imported.type === \"Identifier\" &&\n [\"Link\", \"NavLink\"].includes(item.imported.name)\n ) {\n links.add(item.local.name);\n }\n }\n },\n JSXOpeningElement(node) {\n if (\n node.name.type === \"JSXIdentifier\" &&\n links.has(node.name.name) &&\n targetsItem(jsxAttribute(node, \"to\")) &&\n !jsxAttribute(node, \"prefetch\")\n ) {\n context.report({ node, messageId: \"prefetch\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { isServerFile, jsxAttribute, jsxValue, staticText } from \"../matchers\";\n\ntype Options = [{ serverFiles?: string[] }];\nconst defaults = [\"**/*.server.*\", \"**/server/**\"];\nconst authValue = /otp|verify.?code|access.?token|refresh.?token|id.?token/i;\nconst browserStorage = new Set([\"localStorage\", \"sessionStorage\"]);\nconst globals = new Set([\"window\", \"globalThis\", \"self\"]);\n\nfunction isStorage(node: TSESTree.Node): boolean {\n if (node.type === \"Identifier\") {\n return browserStorage.has(node.name);\n }\n return (\n node.type === \"MemberExpression\" &&\n !node.computed &&\n node.object.type === \"Identifier\" &&\n globals.has(node.object.name) &&\n node.property.type === \"Identifier\" &&\n browserStorage.has(node.property.name)\n );\n}\n\nfunction memberKey(node: TSESTree.MemberExpression): string | undefined {\n if (!node.computed && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n return staticText(node.property);\n}\n\nfunction authImport(value: string): \"server\" | \"all\" | undefined {\n if (value === \"jose\" || value === \"jsonwebtoken\") {\n return \"server\";\n }\n if (\n value === \"next-auth\" ||\n value.startsWith(\"next-auth/\") ||\n value.startsWith(\"@auth/\") ||\n value === \"better-auth\" ||\n value.startsWith(\"better-auth/\") ||\n value === \"lucia\" ||\n value.startsWith(\"@clerk/\") ||\n value === \"firebase/auth\" ||\n value.startsWith(\"@supabase/auth-\")\n ) {\n return \"all\";\n }\n return undefined;\n}\n\nexport const noCustomAuth = createRule<Options, \"auth\">({\n name: \"no-custom-auth\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Sign customers in through the Zappie SDK.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n serverFiles: {\n type: \"array\",\n items: { type: \"string\" },\n uniqueItems: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n auth: \"Use createCustomer().signIn() from @zappie/sdk/customer to sign customers in.\",\n },\n },\n defaultOptions: [{}],\n create(context, [options]) {\n const server = isServerFile(\n context.filename,\n options.serverFiles ?? defaults,\n );\n return {\n ImportDeclaration(node) {\n const kind = authImport(node.source.value);\n if (kind === \"all\" || (kind === \"server\" && !server)) {\n context.report({ node: node.source, messageId: \"auth\" });\n }\n },\n JSXOpeningElement(node) {\n if (\n node.name.type !== \"JSXIdentifier\" ||\n node.name.name.toLowerCase() !== \"input\"\n ) {\n return;\n }\n const type = jsxValue(jsxAttribute(node, \"type\"));\n const auto = jsxValue(jsxAttribute(node, \"autocomplete\"));\n if (\n type?.toLowerCase() === \"password\" ||\n (auto !== undefined &&\n [\"one-time-code\", \"current-password\", \"new-password\"].includes(\n auto.toLowerCase(),\n ))\n ) {\n context.report({ node, messageId: \"auth\" });\n }\n },\n CallExpression(node) {\n if (\n node.callee.type !== \"MemberExpression\" ||\n !isStorage(node.callee.object)\n ) {\n return;\n }\n const first = node.arguments[0];\n const value =\n first?.type === \"Identifier\" ? first.name : staticText(first ?? null);\n if (value && authValue.test(value)) {\n context.report({ node, messageId: \"auth\" });\n }\n },\n AssignmentExpression(node) {\n if (\n node.left.type === \"MemberExpression\" &&\n isStorage(node.left.object)\n ) {\n const key = memberKey(node.left);\n if (key && authValue.test(key)) {\n context.report({ node, messageId: \"auth\" });\n }\n return;\n }\n const cookie =\n node.left.type === \"MemberExpression\" &&\n node.left.object.type === \"Identifier\" &&\n node.left.object.name === \"document\" &&\n node.left.property.type === \"Identifier\" &&\n node.left.property.name === \"cookie\";\n const value = staticText(node.right);\n if (cookie && value && authValue.test(value)) {\n context.report({ node, messageId: \"auth\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { isInternalSdkImport, isZappieEndpoint, staticText } from \"../matchers\";\n\ntype Message = \"direct\" | \"internal\" | \"storefront\" | \"checkout\" | \"customer\";\n\nfunction message(value: string): Message {\n if (/checkout\\.zappie\\.ai|\\/checkout(?:\\/|$)/i.test(value)) {\n return \"checkout\";\n }\n if (/accounts\\.zappie\\.ai|\\/customer\\/graphql/i.test(value)) {\n return \"customer\";\n }\n if (/\\/storefront\\/v1/i.test(value)) {\n return \"storefront\";\n }\n return \"direct\";\n}\n\nexport const noDirectApi = createRule<[], Message>({\n name: \"no-direct-api\",\n meta: {\n type: \"problem\",\n docs: { description: \"Call Zappie through the SDK.\" },\n schema: [],\n messages: {\n direct:\n \"Call the matching public @zappie/sdk client to reach this Zappie endpoint.\",\n internal:\n \"Use a public SDK entry. Only Zappie-owned packages can import @zappie/sdk/internal.\",\n storefront:\n \"Use createZappie(config).store or .menu to read storefront data.\",\n checkout: \"Use createCheckout().open() from @zappie/sdk/checkout.\",\n customer: \"Use createCustomer() from @zappie/sdk/customer.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const inspect = (node: TSESTree.Literal | TSESTree.TemplateLiteral) => {\n if (node.parent.type === \"ImportDeclaration\") {\n return;\n }\n const value = staticText(node);\n if (value && isZappieEndpoint(value)) {\n context.report({ node, messageId: message(value) });\n }\n };\n return {\n Literal: inspect,\n TemplateLiteral: inspect,\n ImportDeclaration(node) {\n if (isInternalSdkImport(node.source.value)) {\n context.report({ node: node.source, messageId: \"internal\" });\n }\n },\n };\n },\n});\n","import {\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from \"@typescript-eslint/utils\";\nimport type ts from \"typescript\";\n\nconst moneyLiteralPattern =\n /[₦€£]\\s?\\d|\\$\\s?\\d|NGN\\s?\\d|\\d+(?:\\.\\d{2})?\\s?(?:naira|NGN)/i;\n\nexport function isMoneyLiteral(value: string): boolean {\n return value.trim() !== \"$1\" && moneyLiteralPattern.test(value);\n}\n\nexport function memberName(node: TSESTree.Node): string | undefined {\n if (node.type !== \"MemberExpression\") {\n return undefined;\n }\n if (!node.computed && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n if (node.computed && node.property.type === \"Literal\") {\n return typeof node.property.value === \"string\"\n ? node.property.value\n : undefined;\n }\n return undefined;\n}\n\nexport const isAmountMinor = (node: TSESTree.Node) =>\n memberName(node) === \"amountMinor\";\n\nexport function amountOwner(\n node: TSESTree.Node,\n): TSESTree.Expression | undefined {\n return node.type === \"MemberExpression\" && isAmountMinor(node)\n ? node.object\n : undefined;\n}\n\nexport function dividedMinorOwner(\n node: TSESTree.Node,\n): TSESTree.Expression | undefined {\n if (\n node.type !== \"BinaryExpression\" ||\n node.operator !== \"/\" ||\n node.right.type !== \"Literal\" ||\n node.right.value !== 100\n ) {\n return undefined;\n }\n return amountOwner(node.left);\n}\n\ntype Services = ReturnType<typeof ESLintUtils.getParserServices>;\n\nexport function parserServices<\n MessageIds extends string,\n Options extends readonly unknown[],\n>(context: TSESLint.RuleContext<MessageIds, Options>): Services | undefined {\n try {\n return ESLintUtils.getParserServices(context, true);\n } catch {\n return undefined;\n }\n}\n\nfunction declaredBySdkMoney(symbol: ts.Symbol): boolean {\n if (!symbol.getName().includes(\"MoneyBrand\")) {\n return false;\n }\n return (symbol.getDeclarations() ?? []).some((declaration) => {\n const path = declaration.getSourceFile().fileName.replaceAll(\"\\\\\", \"/\");\n return (\n path.endsWith(\"/packages/sdk/src/money.ts\") ||\n /\\/node_modules\\/@zappie\\/sdk\\/.*\\.d\\.ts$/.test(path)\n );\n });\n}\n\nexport function isSdkMoney(\n services: Services | undefined,\n node: TSESTree.Node,\n): boolean {\n if (!services?.program) {\n return false;\n }\n const checker = services.program.getTypeChecker();\n const tsNode = services.esTreeNodeToTSNodeMap.get(node);\n const type = checker.getTypeAtLocation(tsNode);\n const types = type.isUnionOrIntersection() ? type.types : [type];\n return types.some((part) =>\n part.getProperties().some((property) => declaredBySdkMoney(property)),\n );\n}\n\nexport function currencyStyle(node: TSESTree.Node | undefined): boolean {\n if (node?.type !== \"ObjectExpression\") {\n return false;\n }\n return node.properties.some(\n (property) =>\n property.type === \"Property\" &&\n !property.computed &&\n ((property.key.type === \"Identifier\" && property.key.name === \"style\") ||\n (property.key.type === \"Literal\" && property.key.value === \"style\")) &&\n property.value.type === \"Literal\" &&\n property.value.value === \"currency\",\n );\n}\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport {\n dividedMinorOwner,\n isAmountMinor,\n isMoneyLiteral,\n isSdkMoney,\n memberName,\n parserServices,\n} from \"../money-rule-utils\";\n\nexport const noMoneyLiterals = createRule<\n [],\n \"arithmetic\" | \"literal\" | \"scale\"\n>({\n name: \"no-money-literals\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Use SDK prices and totals in your storefront.\",\n },\n schema: [],\n messages: {\n arithmetic:\n \"Use cart.subtotal, line.lineSubtotal, quote.subtotal, quote.lines totals or quote.total. Let the SDK calculate amounts.\",\n literal:\n \"Render SDK Money with format.money() or <Money> instead of a hard-coded amount.\",\n scale:\n \"Use format.money() to display amountMinor. Currencies use 0, 2, 3 or 4 minor-unit digits.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const services = parserServices(context);\n const arithmetic = (node: TSESTree.Node) =>\n isAmountMinor(node) || isSdkMoney(services, node);\n return {\n Literal(node) {\n if (\n typeof node.value === \"string\" &&\n node.parent.type !== \"ImportDeclaration\" &&\n isMoneyLiteral(node.value)\n ) {\n context.report({ node, messageId: \"literal\" });\n }\n },\n JSXText(node) {\n if (isMoneyLiteral(node.value)) {\n context.report({ node, messageId: \"literal\" });\n }\n },\n BinaryExpression(node) {\n if (![\"+\", \"-\", \"*\", \"/\"].includes(node.operator)) {\n return;\n }\n if (\n dividedMinorOwner(node) &&\n node.parent.type === \"MemberExpression\" &&\n node.parent.object === node &&\n memberName(node.parent) === \"toFixed\"\n ) {\n return;\n }\n if (arithmetic(node.left) || arithmetic(node.right)) {\n context.report({ node, messageId: \"arithmetic\" });\n }\n },\n TemplateLiteral(node) {\n for (const quasi of node.quasis) {\n if (isMoneyLiteral(quasi.value.cooked ?? quasi.value.raw)) {\n context.report({ node: quasi, messageId: \"literal\" });\n }\n }\n },\n CallExpression(node) {\n if (\n node.callee.type === \"MemberExpression\" &&\n memberName(node.callee) === \"toFixed\" &&\n node.arguments[0]?.type === \"Literal\" &&\n node.arguments[0].value === 2 &&\n dividedMinorOwner(node.callee.object)\n ) {\n context.report({ node, messageId: \"scale\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { isTrackingEndpoint, staticText } from \"../matchers\";\n\nconst imports = [\n \"react-ga4\",\n \"react-facebook-pixel\",\n \"@vercel/analytics\",\n \"posthog-js\",\n];\n\nfunction imported(value: string): boolean {\n return imports.includes(value) || value.startsWith(\"@segment/\");\n}\n\nexport const noTrackingScripts = createRule<[], \"tracking\">({\n name: \"no-tracking-scripts\",\n meta: {\n type: \"problem\",\n docs: { description: \"Forward analytics through Zappie.\" },\n schema: [],\n messages: {\n tracking:\n \"Configure analytics.forward in zappie.config.ts to forward analytics. Remove the tracking script.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const inspect = (node: TSESTree.Literal | TSESTree.TemplateLiteral) => {\n if (node.parent.type === \"ImportDeclaration\") {\n return;\n }\n const value = staticText(node);\n if (value && isTrackingEndpoint(value)) {\n context.report({ node, messageId: \"tracking\" });\n }\n };\n return {\n Literal: inspect,\n TemplateLiteral: inspect,\n ImportDeclaration(node) {\n if (imported(node.source.value)) {\n context.report({ node: node.source, messageId: \"tracking\" });\n }\n },\n CallExpression(node) {\n const callee = node.callee;\n const direct =\n callee.type === \"Identifier\" && [\"gtag\", \"fbq\"].includes(callee.name);\n const dataLayer =\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n callee.object.type === \"Identifier\" &&\n callee.object.name === \"dataLayer\" &&\n callee.property.type === \"Identifier\" &&\n callee.property.name === \"push\";\n if (direct || dataLayer) {\n context.report({ node, messageId: \"tracking\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\n\nconst defaults = [\n \"modal\",\n \"sheet\",\n \"drawer\",\n \"dialog\",\n \"item\",\n \"cart\",\n \"cart_sheet\",\n \"cart_edit\",\n \"cart_clear\",\n \"search\",\n \"catalogue_search\",\n \"catalogue_categories\",\n \"product_note\",\n \"modal_id\",\n];\n\nfunction name(node: TSESTree.Node): string | undefined {\n if (node.type === \"Identifier\") {\n return node.name;\n }\n if (node.type === \"MemberExpression\" && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n return undefined;\n}\n\nfunction carriesKey(node: TSESTree.Node, keys: Set<string>): boolean {\n if (node.type === \"Literal\" && typeof node.value === \"string\") {\n return [...keys].some((key) => new RegExp(`[?&]${key}=`).test(node.value));\n }\n if (node.type === \"TemplateLiteral\") {\n return node.quasis.some((part) =>\n carriesKey(\n { ...part, type: \"Literal\", value: part.value.raw } as never,\n keys,\n ),\n );\n }\n if (node.type === \"ObjectExpression\") {\n return node.properties.some(\n (property) =>\n property.type === \"Property\" &&\n ((property.key.type === \"Identifier\" && keys.has(property.key.name)) ||\n (property.key.type === \"Literal\" &&\n typeof property.key.value === \"string\" &&\n keys.has(property.key.value))),\n );\n }\n return false;\n}\n\nexport const overlayPrimitive = createRule<[{ keys?: string[] }], \"overlay\">({\n name: \"overlay-primitive\",\n meta: {\n type: \"problem\",\n docs: { description: \"Use Overlay to keep dialogs and sheets in the URL.\" },\n schema: [\n {\n type: \"object\",\n properties: { keys: { type: \"array\", items: { type: \"string\" } } },\n additionalProperties: false,\n },\n ],\n messages: {\n overlay:\n \"Use Overlay or useOverlay for dialogs and sheets in the URL. They handle Back, Forward and focus restoration.\",\n },\n },\n defaultOptions: [{}],\n create(context, [options]) {\n const keys = new Set([...defaults, ...(options.keys ?? [])]);\n const searchValues = new Set<string>();\n const derivedFromSearch = (node: TSESTree.Node) => {\n const text = context.sourceCode.getText(node);\n return (\n text.includes(\"useSearchParams(\") ||\n [...searchValues].some((value) =>\n new RegExp(`\\\\b${value}\\\\b`).test(text),\n )\n );\n };\n return {\n VariableDeclarator(node) {\n if (\n node.init?.type === \"CallExpression\" &&\n name(node.init.callee) === \"useSearchParams\"\n ) {\n if (node.id.type === \"Identifier\") {\n searchValues.add(node.id.name);\n } else if (\n node.id.type === \"ArrayPattern\" &&\n node.id.elements[0]?.type === \"Identifier\"\n ) {\n searchValues.add(node.id.elements[0].name);\n }\n } else if (\n node.id.type === \"Identifier\" &&\n node.init &&\n derivedFromSearch(node.init)\n ) {\n searchValues.add(node.id.name);\n }\n },\n CallExpression(node) {\n const callee = name(node.callee);\n const navigation =\n callee === \"setSearchParams\" ||\n callee === \"navigate\" ||\n callee === \"pushState\" ||\n callee === \"replaceState\" ||\n (node.callee.type === \"CallExpression\" &&\n name(node.callee.callee) === \"useNavigate\");\n if (\n navigation &&\n node.arguments.some(\n (argument) =>\n argument.type !== \"SpreadElement\" && carriesKey(argument, keys),\n )\n ) {\n context.report({ node, messageId: \"overlay\" });\n }\n },\n JSXOpeningElement(node) {\n const dialog =\n node.name.type === \"JSXIdentifier\" && node.name.name === \"dialog\";\n const role = node.attributes.some(\n (attribute) =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.name === \"role\" &&\n attribute.value?.type === \"Literal\" &&\n attribute.value.value === \"dialog\",\n );\n const open = node.attributes.find(\n (attribute) =>\n attribute.type === \"JSXAttribute\" && attribute.name.name === \"open\",\n );\n if (\n (dialog || role) &&\n open?.type === \"JSXAttribute\" &&\n open.value?.type === \"JSXExpressionContainer\" &&\n derivedFromSearch(open.value.expression)\n ) {\n context.report({ node, messageId: \"overlay\" });\n }\n },\n };\n },\n});\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\";\n\ntype Fixer = TSESLint.RuleFixer;\ntype Fix = TSESLint.RuleFix;\ntype Source = Readonly<TSESLint.SourceCode>;\n\nfunction sdkImport(\n program: TSESTree.Program,\n): TSESTree.ImportDeclaration | undefined {\n return program.body.find(\n (node): node is TSESTree.ImportDeclaration =>\n node.type === \"ImportDeclaration\" && node.source.value === \"@zappie/sdk\",\n );\n}\n\nfunction importedFormat(\n declaration: TSESTree.ImportDeclaration | undefined,\n): TSESTree.ImportSpecifier | undefined {\n return declaration?.specifiers.find(\n (specifier): specifier is TSESTree.ImportSpecifier =>\n specifier.type === \"ImportSpecifier\" &&\n specifier.imported.type === \"Identifier\" &&\n specifier.imported.name === \"format\",\n );\n}\n\nfunction importFix(\n fixer: Fixer,\n source: Source,\n program: TSESTree.Program,\n local: string,\n): Fix | undefined {\n const declaration = sdkImport(program);\n if (importedFormat(declaration)?.local.name === local) {\n return undefined;\n }\n const importedName = local === \"format\" ? \"format\" : `format as ${local}`;\n const named = declaration?.specifiers.filter(\n (specifier) => specifier.type === \"ImportSpecifier\",\n );\n if (declaration && declaration.importKind !== \"type\" && named?.length) {\n const last = named.at(-1);\n if (last) {\n return fixer.insertTextAfter(last, `, ${importedName}`);\n }\n }\n const first = program.body[0];\n const text = `import { ${importedName} } from \"@zappie/sdk\";\\n`;\n return first\n ? fixer.insertTextBefore(first, text)\n : fixer.insertTextAfter(program, text);\n}\n\nexport function formatFixes(\n fixer: Fixer,\n source: Source,\n program: TSESTree.Program,\n replace: TSESTree.Node,\n owner: TSESTree.Expression,\n local: string,\n): Fix[] {\n const fixes = [\n fixer.replaceText(replace, `${local}.money(${source.getText(owner)})`),\n ];\n const addition = importFix(fixer, source, program, local);\n if (addition) {\n fixes.push(addition);\n }\n return fixes;\n}\n\nexport function formatBinding(\n source: Source,\n program: TSESTree.Program,\n node: TSESTree.Node,\n): \"available\" | \"conflict\" | \"imported\" {\n if (importedFormat(sdkImport(program))) {\n return \"imported\";\n }\n let scope: TSESLint.Scope.Scope | null = source.getScope(node);\n while (scope) {\n if (scope.variables.some((variable) => variable.name === \"format\")) {\n return \"conflict\";\n }\n scope = scope.upper;\n }\n return \"available\";\n}\n\nexport function importedFormatLocal(\n program: TSESTree.Program,\n): string | undefined {\n return importedFormat(sdkImport(program))?.local.name;\n}\n\nexport function availableAlias(source: Source, node: TSESTree.Node): string {\n const names = new Set<string>();\n let scope: TSESLint.Scope.Scope | null = source.getScope(node);\n while (scope) {\n for (const variable of scope.variables) {\n names.add(variable.name);\n }\n scope = scope.upper;\n }\n for (const name of [\n \"zappieFormat\",\n \"zappieMoneyFormat\",\n \"formatMoneyValue\",\n ]) {\n if (!names.has(name)) {\n return name;\n }\n }\n return \"zappieSdkFormat\";\n}\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport {\n availableAlias,\n formatBinding,\n formatFixes,\n importedFormatLocal,\n} from \"../format-fix\";\nimport {\n currencyStyle,\n dividedMinorOwner,\n isAmountMinor,\n isSdkMoney,\n memberName,\n parserServices,\n} from \"../money-rule-utils\";\n\ntype Message = \"currency\" | \"direct\" | \"minor\" | \"useAlias\";\n\nfunction intlNumberFormat(node: TSESTree.Node): boolean {\n const callee =\n node.type === \"NewExpression\" || node.type === \"CallExpression\"\n ? node.callee\n : undefined;\n return Boolean(\n callee?.type === \"MemberExpression\" &&\n callee.object.type === \"Identifier\" &&\n callee.object.name === \"Intl\" &&\n memberName(callee) === \"NumberFormat\",\n );\n}\n\nfunction formattedMinor(node: TSESTree.CallExpression) {\n if (node.callee.type !== \"MemberExpression\") {\n return undefined;\n }\n if (memberName(node.callee) === \"toLocaleString\") {\n return dividedMinorOwner(node.callee.object);\n }\n if (memberName(node.callee) === \"format\") {\n return dividedMinorOwner(node.arguments[0] ?? node);\n }\n return undefined;\n}\n\nfunction formatsDividedMinor(node: TSESTree.NewExpression): boolean {\n const member = node.parent;\n const call = member.type === \"MemberExpression\" ? member.parent : undefined;\n return Boolean(\n call?.type === \"CallExpression\" &&\n call.callee === member &&\n memberName(member) === \"format\" &&\n dividedMinorOwner(call.arguments[0] ?? call),\n );\n}\n\nexport const useFormatMoney = createRule<[], Message>({\n name: \"use-format-money\",\n meta: {\n type: \"problem\",\n docs: { description: \"Format SDK Money with format.money() or <Money>.\" },\n fixable: \"code\",\n hasSuggestions: true,\n schema: [],\n messages: {\n currency:\n \"Use format.money(). Intl currency defaults can differ from the API minor-unit digits.\",\n direct:\n \"Render SDK Money with format.money(value) or <Money value={value}>.\",\n minor:\n \"Use format.money(value). Dividing by 100 gives the wrong amount for currencies with 0, 3 or 4 minor-unit digits.\",\n useAlias:\n \"Import format with an unused alias and format this Money value.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const source = context.sourceCode;\n const services = parserServices(context);\n let program: TSESTree.Program;\n const reportFix = (\n node: TSESTree.Node,\n replace: TSESTree.Node,\n owner: TSESTree.Expression,\n ) => {\n const data = { node, messageId: \"minor\" as const };\n const binding = formatBinding(source, program, node);\n if (binding === \"conflict\") {\n const alias = availableAlias(source, node);\n context.report({\n ...data,\n suggest: [\n {\n messageId: \"useAlias\",\n fix: (fixer) =>\n formatFixes(fixer, source, program, replace, owner, alias),\n },\n ],\n });\n return;\n }\n context.report({\n ...data,\n fix: (fixer) => {\n const local = importedFormatLocal(program) ?? \"format\";\n return formatFixes(fixer, source, program, replace, owner, local);\n },\n });\n };\n const inspectFormatter = (node: TSESTree.CallExpression) => {\n const owner = formattedMinor(node);\n if (owner) {\n reportFix(node, node, owner);\n return true;\n }\n return false;\n };\n return {\n Program(node) {\n program = node;\n },\n NewExpression(node) {\n if (\n intlNumberFormat(node) &&\n currencyStyle(node.arguments[1]) &&\n !formatsDividedMinor(node)\n ) {\n context.report({ node, messageId: \"currency\" });\n }\n },\n CallExpression(node) {\n if (inspectFormatter(node)) {\n return;\n }\n if (intlNumberFormat(node) && currencyStyle(node.arguments[1])) {\n context.report({ node, messageId: \"currency\" });\n return;\n }\n if (\n node.callee.type === \"MemberExpression\" &&\n memberName(node.callee) === \"toLocaleString\" &&\n node.arguments.some(currencyStyle)\n ) {\n context.report({ node, messageId: \"currency\" });\n }\n },\n TemplateLiteral(node) {\n for (const expression of node.expressions) {\n const owner = dividedMinorOwner(expression);\n if (owner) {\n reportFix(expression, expression, owner);\n } else if (isAmountMinor(expression)) {\n context.report({ node: expression, messageId: \"minor\" });\n }\n }\n },\n JSXExpressionContainer(node) {\n if (\n node.parent.type === \"JSXAttribute\" ||\n node.expression.type === \"JSXEmptyExpression\"\n ) {\n return;\n }\n if (\n dividedMinorOwner(node.expression) ||\n isAmountMinor(node.expression)\n ) {\n context.report({ node: node.expression, messageId: \"minor\" });\n } else if (isSdkMoney(services, node.expression)) {\n context.report({ node: node.expression, messageId: \"direct\" });\n }\n },\n };\n },\n});\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\n\ntype Kind = \"add\" | \"checkout\" | \"signin\";\nconst marker: Record<Kind, { name: string; text: string }> = {\n add: { name: \"data-zappie-action\", text: 'data-zappie-action=\"add-to-cart\"' },\n checkout: { name: \"data-zappie-checkout\", text: \"data-zappie-checkout\" },\n signin: { name: \"data-zappie-signin\", text: \"data-zappie-signin\" },\n};\n\nfunction handlerKind(\n source: Readonly<TSESLint.SourceCode>,\n node: TSESTree.JSXAttribute,\n): Kind | undefined {\n const text = node.value ? source.getText(node.value) : \"\";\n if (/\\b(?:cart(?:\\.actions)?\\.add)\\s*\\(/.test(text)) {\n return \"add\";\n }\n if (/\\b(?:checkout|createCheckout\\([^)]*\\))\\.open\\s*\\(/.test(text)) {\n return \"checkout\";\n }\n if (/\\b(?:auth|createCustomer\\([^)]*\\))\\.signIn\\s*\\(/.test(text)) {\n return \"signin\";\n }\n return undefined;\n}\n\nexport const verifyMarkers = createRule<[], \"marker\">({\n name: \"verify-markers\",\n meta: {\n type: \"suggestion\",\n docs: { description: \"Mark shopping actions so verify can find them.\" },\n fixable: \"code\",\n schema: [],\n messages: {\n marker: \"Add {{marker}} so zappie verify can find this shopping action.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n JSXOpeningElement(node) {\n const click = node.attributes.find(\n (attribute): attribute is TSESTree.JSXAttribute =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.name === \"onClick\",\n );\n if (!click) {\n return;\n }\n const kind = handlerKind(context.sourceCode, click);\n if (\n !kind ||\n node.attributes.some(\n (attribute) =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.name === marker[kind].name,\n )\n ) {\n return;\n }\n context.report({\n node: click,\n messageId: \"marker\",\n data: { marker: marker[kind].text },\n fix: (fixer) =>\n fixer.insertTextAfter(node.name, ` ${marker[kind].text}`),\n });\n },\n };\n },\n});\n"],"mappings":"AAAA,OAAOA,OAAY,4BACnB,OAAOC,OAAa,yBACpB,OAAOC,OAAgB,4BCFvB,OAAS,eAAAC,OAAmB,2BAErB,IAAMC,EAAaD,GAAY,YACnCE,GAAS,+BAA+BA,CAAI,EAC/C,ECDA,IAAMC,GAAW,iDACXC,GAAa,mBAEnB,SAASC,EAAWC,EAAyC,CAC3D,GAAIA,EAAK,OAAS,aAChB,OAAOA,EAAK,KAEd,GAAIA,EAAK,OAAS,oBAAsBA,EAAK,SAAS,OAAS,aAC7D,OAAOA,EAAK,SAAS,IAGzB,CAEA,SAASC,GAAaD,EAAgD,CACpE,OAAIA,EAAK,OAAS,aACTA,EAAK,KAEPA,EAAK,OAAS,gBAAkBA,EAAK,SAAS,CAAC,GAAG,OAAS,aAC9DA,EAAK,SAAS,CAAC,EAAE,KACjB,MACN,CAEA,SAASE,GAAYF,EAAwC,CAC3D,IAAMG,EACJJ,EAAWC,EAAK,MAAM,IAAM,aACxBA,EAAK,UAAU,CAAC,EAChBA,EAAK,UAAU,CAAC,EACtB,MAAO,GACLG,GACAA,EAAM,OAAS,kBACdA,EAAM,OAAS,mBACbA,EAAM,OAAS,WAAa,OAAOA,EAAM,OAAU,UAE1D,CAEO,IAAMC,EAAaC,EAAuB,CAC/C,KAAM,eACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,kCAAmC,EACxD,OAAQ,CAAC,EACT,SAAU,CACR,KAAM,8FACR,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,MAAO,CACL,eAAeN,EAAM,CACnB,IAAMO,EAASR,EAAWC,EAAK,MAAM,EAC/BQ,EAAMR,EAAK,UAAU,CAAC,EAE1B,CAAC,UAAW,UAAW,aAAc,MAAM,EAAE,SAASO,GAAU,EAAE,GAClEC,GAAK,OAAS,WACd,OAAOA,EAAI,OAAU,UACrBV,GAAW,KAAKU,EAAI,KAAK,GAEzBF,EAAQ,OAAO,CAAE,KAAAN,EAAM,UAAW,MAAO,CAAC,CAE9C,EACA,mBAAmBA,EAAM,CACvB,GAAIA,EAAK,MAAM,OAAS,iBACtB,OAEF,IAAMS,EAAKR,GAAaD,EAAK,EAAE,EACzBU,EAAOX,EAAWC,EAAK,KAAK,MAAM,EAEtCS,GACAZ,GAAS,KAAKY,CAAE,GAChB,CAAC,WAAY,aAAc,eAAe,EAAE,SAASC,GAAQ,EAAE,GAC/DR,GAAYF,EAAK,IAAI,GAErBM,EAAQ,OAAO,CAAE,KAAAN,EAAM,UAAW,MAAO,CAAC,EAG1CS,GACA,QAAQ,KAAKA,CAAE,GACf,CAAC,SAAU,cAAe,MAAM,EAAE,SAASC,GAAQ,EAAE,GAErDJ,EAAQ,OAAO,CAAE,KAAAN,EAAM,UAAW,MAAO,CAAC,CAE9C,CACF,CACF,CACF,CAAC,ECtFD,OAAS,aAAAW,OAAiB,YAE1B,IAAMC,GACJ,mFACIC,GACJ,6JAEWC,EACX,2DAEK,SAASC,EAAWC,EAAgD,CACzE,GAAIA,GAAM,OAAS,WAAa,OAAOA,EAAK,OAAU,SACpD,OAAOA,EAAK,MAEd,GAAIA,GAAM,OAAS,kBACjB,OAAOA,EAAK,OACT,IAAKC,GAASA,EAAK,MAAM,QAAUA,EAAK,MAAM,GAAG,EACjD,KAAK,EAAE,CAGd,CAEO,SAASC,EACdF,EACAG,EACmC,CACnC,OAAOH,EAAK,WAAW,KACpBI,GACCA,EAAU,OAAS,gBACnBA,EAAU,KAAK,OAAS,iBACxBA,EAAU,KAAK,KAAK,YAAY,IAAMD,EAAK,YAAY,CAC3D,CACF,CAEO,SAASE,EAASL,EAAkD,CACzE,GAAKA,GAAM,MAGX,IAAIA,EAAK,MAAM,OAAS,UACtB,OAAO,OAAOA,EAAK,MAAM,OAAU,SAAWA,EAAK,MAAM,MAAQ,OAEnE,GAAIA,EAAK,MAAM,OAAS,yBACtB,OAAOD,EAAWC,EAAK,MAAM,UAAU,EAG3C,CAEO,IAAMM,EAAoBC,GAAkBX,GAAgB,KAAKW,CAAK,EAChEC,EAAsBD,GACjCV,GAAgB,KAAKU,CAAK,EACfE,EAAuBF,GAClCA,IAAU,wBAA0BA,EAAM,WAAW,uBAAuB,EAEvE,SAASG,EAAaC,EAAkBC,EAA6B,CAC1E,IAAMC,EAAOF,EAAS,WAAW,KAAM,GAAG,EAC1C,OAAOC,EAAS,KAAME,GACpBnB,GAAUkB,EAAMC,EAAS,CAAE,UAAW,EAAK,CAAC,CAC9C,CACF,CCxDA,IAAMC,GAAe,IAAI,IAAI,CAAC,YAAa,SAAU,SAAU,SAAS,CAAC,EACnEC,EAAe,+BAErB,SAASC,GAAeC,EAAwB,CAC9C,OACEA,EAAM,WAAW,YAAY,GAC7BA,IAAU,kBACVA,IAAU,wBACVA,EAAM,WAAW,UAAU,CAE/B,CAEO,IAAMC,EAAiBC,EAA2B,CACvD,KAAM,mBACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,uCAAwC,EAC7D,OAAQ,CAAC,EACT,SAAU,CACR,SACE,8FACJ,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,MAAO,CACL,kBAAkBC,EAAM,CAClBL,GAAeK,EAAK,OAAO,KAAK,GAClCD,EAAQ,OAAO,CAAE,KAAMC,EAAK,OAAQ,UAAW,UAAW,CAAC,CAE/D,EACA,kBAAkBA,EAAM,CACtB,IAAMC,EACJD,EAAK,KAAK,OAAS,gBACfA,EAAK,KAAK,KAAK,YAAY,EAC3B,GACAE,EAAOC,EAASC,EAAaJ,EAAM,cAAc,CAAC,EAClDK,EAAOF,EAASC,EAAaJ,EAAM,MAAM,CAAC,EAC1CM,EAAKH,EAASC,EAAaJ,EAAM,IAAI,CAAC,EACtCO,EAASJ,EACbC,EAAaJ,EAAMC,IAAQ,SAAW,MAAQ,MAAM,CACtD,EACMO,EACJP,IAAQ,UACNC,IAAS,QAAaT,GAAa,IAAIS,EAAK,YAAY,CAAC,GACxDG,IAAS,QAAaX,EAAa,KAAKW,CAAI,GAC5CC,IAAO,QAAaZ,EAAa,KAAKY,CAAE,GACvCG,GACHR,IAAQ,UAAYA,IAAQ,MAC7BM,IAAW,QACXG,EAAmB,KAAKH,CAAM,GAC5BC,GAAaC,IACfV,EAAQ,OAAO,CAAE,KAAAC,EAAM,UAAW,UAAW,CAAC,CAElD,CACF,CACF,CACF,CAAC,ECtDD,SAASW,GAASC,EAAqB,CACrC,IAAIC,EAAqCD,EACzC,KAAOC,GAAS,QAAQ,CACtB,IAAMC,EAAwBD,EAAQ,OACtC,IACGC,EAAO,OAAS,2BACfA,EAAO,OAAS,uBAClBA,EAAO,QAAQ,OAAS,kBACxBA,EAAO,OAAO,OAAO,OAAS,oBAC9BA,EAAO,OAAO,OAAO,SAAS,OAAS,cACvCA,EAAO,OAAO,OAAO,SAAS,OAAS,MAEvC,OAAOA,EAETD,EAAUC,CACZ,CACF,CAEA,SAASC,GAAUC,EAAuCC,EAAoB,CAC5E,IAAMC,EAAOF,EAAO,QAAQC,EAAS,MAAM,EAC3C,GAAI,kCAAkC,KAAKC,CAAI,EAC7C,MAAO,GAET,IAAMC,EAAQF,EAAS,OAAO,CAAC,EAC/B,OAAIE,GAAO,OAAS,aACX,GAEK,IAAI,OAAO,GAAGA,EAAM,IAAI,4BAA4B,EACrD,KAAKH,EAAO,QAAQC,CAAQ,CAAC,CAC5C,CAEO,IAAMG,EAAgBC,EAAwC,CACnE,KAAM,iBACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,8CAA+C,EACpE,OAAQ,CAAC,EACT,SAAU,CACR,OAAQ,iEACR,WAAY,mDACd,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,IAAMC,EAAuC,CAAC,EAC9C,MAAO,CACL,kBAAkBX,EAAM,CACtB,IAAMY,EAAWC,EAAab,EAAM,UAAU,EACxCc,EACJF,GAAU,OAAO,OAAS,0BAC1BA,EAAS,MAAM,WAAW,OAAS,WACnCA,EAAS,MAAM,WAAW,QAAU,GACtC,GACEZ,EAAK,KAAK,OAAS,iBACnBA,EAAK,KAAK,OAAS,SACnB,CAACY,GACDE,EAEA,OAEFH,EAAO,KAAKX,CAAI,EAChB,IAAMK,EAAWN,GAASC,CAAI,EAC1BK,GAAY,CAACF,GAAUO,EAAQ,WAAYL,CAAQ,GACrDK,EAAQ,OAAO,CAAE,KAAME,EAAU,UAAW,QAAS,CAAC,CAE1D,EACA,gBAAiB,CACf,GACE,kCAAkC,KAChCF,EAAQ,SAAS,WAAW,KAAM,GAAG,CACvC,GACAC,EAAO,OAAS,EAEhB,QAAWX,KAAQW,EAAO,MAAM,CAAC,EAC/BD,EAAQ,OAAO,CAAE,KAAAV,EAAM,UAAW,YAAa,CAAC,CAGtD,CACF,CACF,CACF,CAAC,EClFD,SAASe,GAAYC,EAAmC,CACtD,IAAMC,EACJD,GAAW,OAAO,OAAS,yBACvBA,EAAU,MAAM,WAChB,OACN,GACEC,GAAY,OAAS,kBACrBA,EAAW,OAAO,OAAS,cAC3BA,EAAW,OAAO,OAAS,WAE3B,MAAO,GAET,IAAMC,EAASC,EAASH,CAAS,EACjC,OAAOE,IAAW,QAAa,kBAAkB,KAAKA,CAAM,CAC9D,CAEO,IAAME,EAAmBC,EAA2B,CACzD,KAAM,qBACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,2CAA4C,EACjE,OAAQ,CAAC,EACT,SAAU,CACR,SAAU,6DACZ,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,IAAMC,EAAQ,IAAI,IAClB,MAAO,CACL,kBAAkBC,EAAM,CACtB,GAAK,CAAC,eAAgB,kBAAkB,EAAE,SAASA,EAAK,OAAO,KAAK,EAGpE,QAAWC,KAAQD,EAAK,WAEpBC,EAAK,OAAS,mBACdA,EAAK,SAAS,OAAS,cACvB,CAAC,OAAQ,SAAS,EAAE,SAASA,EAAK,SAAS,IAAI,GAE/CF,EAAM,IAAIE,EAAK,MAAM,IAAI,CAG/B,EACA,kBAAkBD,EAAM,CAEpBA,EAAK,KAAK,OAAS,iBACnBD,EAAM,IAAIC,EAAK,KAAK,IAAI,GACxBT,GAAYW,EAAaF,EAAM,IAAI,CAAC,GACpC,CAACE,EAAaF,EAAM,UAAU,GAE9BF,EAAQ,OAAO,CAAE,KAAAE,EAAM,UAAW,UAAW,CAAC,CAElD,CACF,CACF,CACF,CAAC,ECvDD,IAAMG,GAAW,CAAC,gBAAiB,cAAc,EAC3CC,EAAY,2DACZC,EAAiB,IAAI,IAAI,CAAC,eAAgB,gBAAgB,CAAC,EAC3DC,GAAU,IAAI,IAAI,CAAC,SAAU,aAAc,MAAM,CAAC,EAExD,SAASC,EAAUC,EAA8B,CAC/C,OAAIA,EAAK,OAAS,aACTH,EAAe,IAAIG,EAAK,IAAI,EAGnCA,EAAK,OAAS,oBACd,CAACA,EAAK,UACNA,EAAK,OAAO,OAAS,cACrBF,GAAQ,IAAIE,EAAK,OAAO,IAAI,GAC5BA,EAAK,SAAS,OAAS,cACvBH,EAAe,IAAIG,EAAK,SAAS,IAAI,CAEzC,CAEA,SAASC,GAAUD,EAAqD,CACtE,MAAI,CAACA,EAAK,UAAYA,EAAK,SAAS,OAAS,aACpCA,EAAK,SAAS,KAEhBE,EAAWF,EAAK,QAAQ,CACjC,CAEA,SAASG,GAAWC,EAA6C,CAC/D,GAAIA,IAAU,QAAUA,IAAU,eAChC,MAAO,SAET,GACEA,IAAU,aACVA,EAAM,WAAW,YAAY,GAC7BA,EAAM,WAAW,QAAQ,GACzBA,IAAU,eACVA,EAAM,WAAW,cAAc,GAC/BA,IAAU,SACVA,EAAM,WAAW,SAAS,GAC1BA,IAAU,iBACVA,EAAM,WAAW,iBAAiB,EAElC,MAAO,KAGX,CAEO,IAAMC,EAAeC,EAA4B,CACtD,KAAM,iBACN,KAAM,CACJ,KAAM,UACN,KAAM,CACJ,YAAa,2CACf,EACA,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CACV,YAAa,CACX,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,EACxB,YAAa,EACf,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,KAAM,+EACR,CACF,EACA,eAAgB,CAAC,CAAC,CAAC,EACnB,OAAOC,EAAS,CAACC,CAAO,EAAG,CACzB,IAAMC,EAASC,EACbH,EAAQ,SACRC,EAAQ,aAAeb,EACzB,EACA,MAAO,CACL,kBAAkBK,EAAM,CACtB,IAAMW,EAAOR,GAAWH,EAAK,OAAO,KAAK,GACrCW,IAAS,OAAUA,IAAS,UAAY,CAACF,IAC3CF,EAAQ,OAAO,CAAE,KAAMP,EAAK,OAAQ,UAAW,MAAO,CAAC,CAE3D,EACA,kBAAkBA,EAAM,CACtB,GACEA,EAAK,KAAK,OAAS,iBACnBA,EAAK,KAAK,KAAK,YAAY,IAAM,QAEjC,OAEF,IAAMY,EAAOC,EAASC,EAAad,EAAM,MAAM,CAAC,EAC1Ce,EAAOF,EAASC,EAAad,EAAM,cAAc,CAAC,GAEtDY,GAAM,YAAY,IAAM,YACvBG,IAAS,QACR,CAAC,gBAAiB,mBAAoB,cAAc,EAAE,SACpDA,EAAK,YAAY,CACnB,IAEFR,EAAQ,OAAO,CAAE,KAAAP,EAAM,UAAW,MAAO,CAAC,CAE9C,EACA,eAAeA,EAAM,CACnB,GACEA,EAAK,OAAO,OAAS,oBACrB,CAACD,EAAUC,EAAK,OAAO,MAAM,EAE7B,OAEF,IAAMgB,EAAQhB,EAAK,UAAU,CAAC,EACxBI,EACJY,GAAO,OAAS,aAAeA,EAAM,KAAOd,EAAWc,GAAS,IAAI,EAClEZ,GAASR,EAAU,KAAKQ,CAAK,GAC/BG,EAAQ,OAAO,CAAE,KAAAP,EAAM,UAAW,MAAO,CAAC,CAE9C,EACA,qBAAqBA,EAAM,CACzB,GACEA,EAAK,KAAK,OAAS,oBACnBD,EAAUC,EAAK,KAAK,MAAM,EAC1B,CACA,IAAMiB,EAAMhB,GAAUD,EAAK,IAAI,EAC3BiB,GAAOrB,EAAU,KAAKqB,CAAG,GAC3BV,EAAQ,OAAO,CAAE,KAAAP,EAAM,UAAW,MAAO,CAAC,EAE5C,MACF,CACA,IAAMkB,EACJlB,EAAK,KAAK,OAAS,oBACnBA,EAAK,KAAK,OAAO,OAAS,cAC1BA,EAAK,KAAK,OAAO,OAAS,YAC1BA,EAAK,KAAK,SAAS,OAAS,cAC5BA,EAAK,KAAK,SAAS,OAAS,SACxBI,EAAQF,EAAWF,EAAK,KAAK,EAC/BkB,GAAUd,GAASR,EAAU,KAAKQ,CAAK,GACzCG,EAAQ,OAAO,CAAE,KAAAP,EAAM,UAAW,MAAO,CAAC,CAE9C,CACF,CACF,CACF,CAAC,EC3ID,SAASmB,GAAQC,EAAwB,CACvC,MAAI,2CAA2C,KAAKA,CAAK,EAChD,WAEL,4CAA4C,KAAKA,CAAK,EACjD,WAEL,oBAAoB,KAAKA,CAAK,EACzB,aAEF,QACT,CAEO,IAAMC,EAAcC,EAAwB,CACjD,KAAM,gBACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,8BAA+B,EACpD,OAAQ,CAAC,EACT,SAAU,CACR,OACE,6EACF,SACE,sFACF,WACE,mEACF,SAAU,yDACV,SAAU,iDACZ,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,IAAMC,EAAWC,GAAsD,CACrE,GAAIA,EAAK,OAAO,OAAS,oBACvB,OAEF,IAAML,EAAQM,EAAWD,CAAI,EACzBL,GAASO,EAAiBP,CAAK,GACjCG,EAAQ,OAAO,CAAE,KAAAE,EAAM,UAAWN,GAAQC,CAAK,CAAE,CAAC,CAEtD,EACA,MAAO,CACL,QAASI,EACT,gBAAiBA,EACjB,kBAAkBC,EAAM,CAClBG,EAAoBH,EAAK,OAAO,KAAK,GACvCF,EAAQ,OAAO,CAAE,KAAME,EAAK,OAAQ,UAAW,UAAW,CAAC,CAE/D,CACF,CACF,CACF,CAAC,ECzDD,OACE,eAAAI,OAGK,2BAGP,IAAMC,GACJ,+DAEK,SAASC,EAAeC,EAAwB,CACrD,OAAOA,EAAM,KAAK,IAAM,MAAQF,GAAoB,KAAKE,CAAK,CAChE,CAEO,SAASC,EAAWC,EAAyC,CAClE,GAAIA,EAAK,OAAS,mBAGlB,IAAI,CAACA,EAAK,UAAYA,EAAK,SAAS,OAAS,aAC3C,OAAOA,EAAK,SAAS,KAEvB,GAAIA,EAAK,UAAYA,EAAK,SAAS,OAAS,UAC1C,OAAO,OAAOA,EAAK,SAAS,OAAU,SAClCA,EAAK,SAAS,MACd,OAGR,CAEO,IAAMC,EAAiBD,GAC5BD,EAAWC,CAAI,IAAM,cAEhB,SAASE,GACdF,EACiC,CACjC,OAAOA,EAAK,OAAS,oBAAsBC,EAAcD,CAAI,EACzDA,EAAK,OACL,MACN,CAEO,SAASG,EACdH,EACiC,CACjC,GACE,EAAAA,EAAK,OAAS,oBACdA,EAAK,WAAa,KAClBA,EAAK,MAAM,OAAS,WACpBA,EAAK,MAAM,QAAU,KAIvB,OAAOE,GAAYF,EAAK,IAAI,CAC9B,CAIO,SAASI,EAGdC,EAA0E,CAC1E,GAAI,CACF,OAAOV,GAAY,kBAAkBU,EAAS,EAAI,CACpD,MAAQ,CACN,MACF,CACF,CAEA,SAASC,GAAmBC,EAA4B,CACtD,OAAKA,EAAO,QAAQ,EAAE,SAAS,YAAY,GAGnCA,EAAO,gBAAgB,GAAK,CAAC,GAAG,KAAMC,GAAgB,CAC5D,IAAMC,EAAOD,EAAY,cAAc,EAAE,SAAS,WAAW,KAAM,GAAG,EACtE,OACEC,EAAK,SAAS,4BAA4B,GAC1C,2CAA2C,KAAKA,CAAI,CAExD,CAAC,EARQ,EASX,CAEO,SAASC,EACdC,EACAX,EACS,CACT,GAAI,CAACW,GAAU,QACb,MAAO,GAET,IAAMC,EAAUD,EAAS,QAAQ,eAAe,EAC1CE,EAASF,EAAS,sBAAsB,IAAIX,CAAI,EAChDc,EAAOF,EAAQ,kBAAkBC,CAAM,EAE7C,OADcC,EAAK,sBAAsB,EAAIA,EAAK,MAAQ,CAACA,CAAI,GAClD,KAAMC,GACjBA,EAAK,cAAc,EAAE,KAAMC,GAAaV,GAAmBU,CAAQ,CAAC,CACtE,CACF,CAEO,SAASC,EAAcjB,EAA0C,CACtE,OAAIA,GAAM,OAAS,mBACV,GAEFA,EAAK,WAAW,KACpBgB,GACCA,EAAS,OAAS,YAClB,CAACA,EAAS,WACRA,EAAS,IAAI,OAAS,cAAgBA,EAAS,IAAI,OAAS,SAC3DA,EAAS,IAAI,OAAS,WAAaA,EAAS,IAAI,QAAU,UAC7DA,EAAS,MAAM,OAAS,WACxBA,EAAS,MAAM,QAAU,UAC7B,CACF,CClGO,IAAME,EAAkBC,EAG7B,CACA,KAAM,oBACN,KAAM,CACJ,KAAM,UACN,KAAM,CACJ,YAAa,+CACf,EACA,OAAQ,CAAC,EACT,SAAU,CACR,WACE,0HACF,QACE,kFACF,MACE,2FACJ,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,IAAMC,EAAWC,EAAeF,CAAO,EACjCG,EAAcC,GAClBC,EAAcD,CAAI,GAAKE,EAAWL,EAAUG,CAAI,EAClD,MAAO,CACL,QAAQA,EAAM,CAEV,OAAOA,EAAK,OAAU,UACtBA,EAAK,OAAO,OAAS,qBACrBG,EAAeH,EAAK,KAAK,GAEzBJ,EAAQ,OAAO,CAAE,KAAAI,EAAM,UAAW,SAAU,CAAC,CAEjD,EACA,QAAQA,EAAM,CACRG,EAAeH,EAAK,KAAK,GAC3BJ,EAAQ,OAAO,CAAE,KAAAI,EAAM,UAAW,SAAU,CAAC,CAEjD,EACA,iBAAiBA,EAAM,CAChB,CAAC,IAAK,IAAK,IAAK,GAAG,EAAE,SAASA,EAAK,QAAQ,IAI9CI,EAAkBJ,CAAI,GACtBA,EAAK,OAAO,OAAS,oBACrBA,EAAK,OAAO,SAAWA,GACvBK,EAAWL,EAAK,MAAM,IAAM,YAI1BD,EAAWC,EAAK,IAAI,GAAKD,EAAWC,EAAK,KAAK,IAChDJ,EAAQ,OAAO,CAAE,KAAAI,EAAM,UAAW,YAAa,CAAC,EAEpD,EACA,gBAAgBA,EAAM,CACpB,QAAWM,KAASN,EAAK,OACnBG,EAAeG,EAAM,MAAM,QAAUA,EAAM,MAAM,GAAG,GACtDV,EAAQ,OAAO,CAAE,KAAMU,EAAO,UAAW,SAAU,CAAC,CAG1D,EACA,eAAeN,EAAM,CAEjBA,EAAK,OAAO,OAAS,oBACrBK,EAAWL,EAAK,MAAM,IAAM,WAC5BA,EAAK,UAAU,CAAC,GAAG,OAAS,WAC5BA,EAAK,UAAU,CAAC,EAAE,QAAU,GAC5BI,EAAkBJ,EAAK,OAAO,MAAM,GAEpCJ,EAAQ,OAAO,CAAE,KAAAI,EAAM,UAAW,OAAQ,CAAC,CAE/C,CACF,CACF,CACF,CAAC,ECnFD,IAAMO,GAAU,CACd,YACA,uBACA,oBACA,YACF,EAEA,SAASC,GAASC,EAAwB,CACxC,OAAOF,GAAQ,SAASE,CAAK,GAAKA,EAAM,WAAW,WAAW,CAChE,CAEO,IAAMC,EAAoBC,EAA2B,CAC1D,KAAM,sBACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,mCAAoC,EACzD,OAAQ,CAAC,EACT,SAAU,CACR,SACE,mGACJ,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,IAAMC,EAAWC,GAAsD,CACrE,GAAIA,EAAK,OAAO,OAAS,oBACvB,OAEF,IAAML,EAAQM,EAAWD,CAAI,EACzBL,GAASO,EAAmBP,CAAK,GACnCG,EAAQ,OAAO,CAAE,KAAAE,EAAM,UAAW,UAAW,CAAC,CAElD,EACA,MAAO,CACL,QAASD,EACT,gBAAiBA,EACjB,kBAAkBC,EAAM,CAClBN,GAASM,EAAK,OAAO,KAAK,GAC5BF,EAAQ,OAAO,CAAE,KAAME,EAAK,OAAQ,UAAW,UAAW,CAAC,CAE/D,EACA,eAAeA,EAAM,CACnB,IAAMG,EAASH,EAAK,OACdI,EACJD,EAAO,OAAS,cAAgB,CAAC,OAAQ,KAAK,EAAE,SAASA,EAAO,IAAI,EAChEE,EACJF,EAAO,OAAS,oBAChB,CAACA,EAAO,UACRA,EAAO,OAAO,OAAS,cACvBA,EAAO,OAAO,OAAS,aACvBA,EAAO,SAAS,OAAS,cACzBA,EAAO,SAAS,OAAS,QACvBC,GAAUC,IACZP,EAAQ,OAAO,CAAE,KAAAE,EAAM,UAAW,UAAW,CAAC,CAElD,CACF,CACF,CACF,CAAC,EC3DD,IAAMM,GAAW,CACf,QACA,QACA,SACA,SACA,OACA,OACA,aACA,YACA,aACA,SACA,mBACA,uBACA,eACA,UACF,EAEA,SAASC,EAAKC,EAAyC,CACrD,GAAIA,EAAK,OAAS,aAChB,OAAOA,EAAK,KAEd,GAAIA,EAAK,OAAS,oBAAsBA,EAAK,SAAS,OAAS,aAC7D,OAAOA,EAAK,SAAS,IAGzB,CAEA,SAASC,EAAWD,EAAqBE,EAA4B,CACnE,OAAIF,EAAK,OAAS,WAAa,OAAOA,EAAK,OAAU,SAC5C,CAAC,GAAGE,CAAI,EAAE,KAAMC,GAAQ,IAAI,OAAO,OAAOA,CAAG,GAAG,EAAE,KAAKH,EAAK,KAAK,CAAC,EAEvEA,EAAK,OAAS,kBACTA,EAAK,OAAO,KAAMI,GACvBH,EACE,CAAE,GAAGG,EAAM,KAAM,UAAW,MAAOA,EAAK,MAAM,GAAI,EAClDF,CACF,CACF,EAEEF,EAAK,OAAS,mBACTA,EAAK,WAAW,KACpBK,GACCA,EAAS,OAAS,aAChBA,EAAS,IAAI,OAAS,cAAgBH,EAAK,IAAIG,EAAS,IAAI,IAAI,GAC/DA,EAAS,IAAI,OAAS,WACrB,OAAOA,EAAS,IAAI,OAAU,UAC9BH,EAAK,IAAIG,EAAS,IAAI,KAAK,EACnC,EAEK,EACT,CAEO,IAAMC,EAAmBC,EAA6C,CAC3E,KAAM,oBACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,oDAAqD,EAC1E,OAAQ,CACN,CACE,KAAM,SACN,WAAY,CAAE,KAAM,CAAE,KAAM,QAAS,MAAO,CAAE,KAAM,QAAS,CAAE,CAAE,EACjE,qBAAsB,EACxB,CACF,EACA,SAAU,CACR,QACE,+GACJ,CACF,EACA,eAAgB,CAAC,CAAC,CAAC,EACnB,OAAOC,EAAS,CAACC,CAAO,EAAG,CACzB,IAAMP,EAAO,IAAI,IAAI,CAAC,GAAGJ,GAAU,GAAIW,EAAQ,MAAQ,CAAC,CAAE,CAAC,EACrDC,EAAe,IAAI,IACnBC,EAAqBX,GAAwB,CACjD,IAAMY,EAAOJ,EAAQ,WAAW,QAAQR,CAAI,EAC5C,OACEY,EAAK,SAAS,kBAAkB,GAChC,CAAC,GAAGF,CAAY,EAAE,KAAMG,GACtB,IAAI,OAAO,MAAMA,CAAK,KAAK,EAAE,KAAKD,CAAI,CACxC,CAEJ,EACA,MAAO,CACL,mBAAmBZ,EAAM,CAErBA,EAAK,MAAM,OAAS,kBACpBD,EAAKC,EAAK,KAAK,MAAM,IAAM,kBAEvBA,EAAK,GAAG,OAAS,aACnBU,EAAa,IAAIV,EAAK,GAAG,IAAI,EAE7BA,EAAK,GAAG,OAAS,gBACjBA,EAAK,GAAG,SAAS,CAAC,GAAG,OAAS,cAE9BU,EAAa,IAAIV,EAAK,GAAG,SAAS,CAAC,EAAE,IAAI,EAG3CA,EAAK,GAAG,OAAS,cACjBA,EAAK,MACLW,EAAkBX,EAAK,IAAI,GAE3BU,EAAa,IAAIV,EAAK,GAAG,IAAI,CAEjC,EACA,eAAeA,EAAM,CACnB,IAAMc,EAASf,EAAKC,EAAK,MAAM,GAE7Bc,IAAW,mBACXA,IAAW,YACXA,IAAW,aACXA,IAAW,gBACVd,EAAK,OAAO,OAAS,kBACpBD,EAAKC,EAAK,OAAO,MAAM,IAAM,gBAG/BA,EAAK,UAAU,KACZe,GACCA,EAAS,OAAS,iBAAmBd,EAAWc,EAAUb,CAAI,CAClE,GAEAM,EAAQ,OAAO,CAAE,KAAAR,EAAM,UAAW,SAAU,CAAC,CAEjD,EACA,kBAAkBA,EAAM,CACtB,IAAMgB,EACJhB,EAAK,KAAK,OAAS,iBAAmBA,EAAK,KAAK,OAAS,SACrDiB,EAAOjB,EAAK,WAAW,KAC1BkB,GACCA,EAAU,OAAS,gBACnBA,EAAU,KAAK,OAAS,QACxBA,EAAU,OAAO,OAAS,WAC1BA,EAAU,MAAM,QAAU,QAC9B,EACMC,EAAOnB,EAAK,WAAW,KAC1BkB,GACCA,EAAU,OAAS,gBAAkBA,EAAU,KAAK,OAAS,MACjE,GAEGF,GAAUC,IACXE,GAAM,OAAS,gBACfA,EAAK,OAAO,OAAS,0BACrBR,EAAkBQ,EAAK,MAAM,UAAU,GAEvCX,EAAQ,OAAO,CAAE,KAAAR,EAAM,UAAW,SAAU,CAAC,CAEjD,CACF,CACF,CACF,CAAC,ECjJD,SAASoB,EACPC,EACwC,CACxC,OAAOA,EAAQ,KAAK,KACjBC,GACCA,EAAK,OAAS,qBAAuBA,EAAK,OAAO,QAAU,aAC/D,CACF,CAEA,SAASC,EACPC,EACsC,CACtC,OAAOA,GAAa,WAAW,KAC5BC,GACCA,EAAU,OAAS,mBACnBA,EAAU,SAAS,OAAS,cAC5BA,EAAU,SAAS,OAAS,QAChC,CACF,CAEA,SAASC,GACPC,EACAC,EACAP,EACAQ,EACiB,CACjB,IAAML,EAAcJ,EAAUC,CAAO,EACrC,GAAIE,EAAeC,CAAW,GAAG,MAAM,OAASK,EAC9C,OAEF,IAAMC,EAAeD,IAAU,SAAW,SAAW,aAAaA,CAAK,GACjEE,EAAQP,GAAa,WAAW,OACnCC,GAAcA,EAAU,OAAS,iBACpC,EACA,GAAID,GAAeA,EAAY,aAAe,QAAUO,GAAO,OAAQ,CACrE,IAAMC,EAAOD,EAAM,GAAG,EAAE,EACxB,GAAIC,EACF,OAAOL,EAAM,gBAAgBK,EAAM,KAAKF,CAAY,EAAE,CAE1D,CACA,IAAMG,EAAQZ,EAAQ,KAAK,CAAC,EACtBa,EAAO,YAAYJ,CAAY;AAAA,EACrC,OAAOG,EACHN,EAAM,iBAAiBM,EAAOC,CAAI,EAClCP,EAAM,gBAAgBN,EAASa,CAAI,CACzC,CAEO,SAASC,EACdR,EACAC,EACAP,EACAe,EACAC,EACAR,EACO,CACP,IAAMS,EAAQ,CACZX,EAAM,YAAYS,EAAS,GAAGP,CAAK,UAAUD,EAAO,QAAQS,CAAK,CAAC,GAAG,CACvE,EACME,EAAWb,GAAUC,EAAOC,EAAQP,EAASQ,CAAK,EACxD,OAAIU,GACFD,EAAM,KAAKC,CAAQ,EAEdD,CACT,CAEO,SAASE,EACdZ,EACAP,EACAC,EACuC,CACvC,GAAIC,EAAeH,EAAUC,CAAO,CAAC,EACnC,MAAO,WAET,IAAIoB,EAAqCb,EAAO,SAASN,CAAI,EAC7D,KAAOmB,GAAO,CACZ,GAAIA,EAAM,UAAU,KAAMC,GAAaA,EAAS,OAAS,QAAQ,EAC/D,MAAO,WAETD,EAAQA,EAAM,KAChB,CACA,MAAO,WACT,CAEO,SAASE,EACdtB,EACoB,CACpB,OAAOE,EAAeH,EAAUC,CAAO,CAAC,GAAG,MAAM,IACnD,CAEO,SAASuB,GAAehB,EAAgBN,EAA6B,CAC1E,IAAMuB,EAAQ,IAAI,IACdJ,EAAqCb,EAAO,SAASN,CAAI,EAC7D,KAAOmB,GAAO,CACZ,QAAWC,KAAYD,EAAM,UAC3BI,EAAM,IAAIH,EAAS,IAAI,EAEzBD,EAAQA,EAAM,KAChB,CACA,QAAWK,IAAQ,CACjB,eACA,oBACA,kBACF,EACE,GAAI,CAACD,EAAM,IAAIC,CAAI,EACjB,OAAOA,EAGX,MAAO,iBACT,CC/FA,SAASC,GAAiBC,EAA8B,CACtD,IAAMC,EACJD,EAAK,OAAS,iBAAmBA,EAAK,OAAS,iBAC3CA,EAAK,OACL,OACN,OACEC,GAAQ,OAAS,oBACjBA,EAAO,OAAO,OAAS,cACvBA,EAAO,OAAO,OAAS,QACvBC,EAAWD,CAAM,IAAM,cAE3B,CAEA,SAASE,GAAeH,EAA+B,CACrD,GAAIA,EAAK,OAAO,OAAS,mBAGzB,IAAIE,EAAWF,EAAK,MAAM,IAAM,iBAC9B,OAAOI,EAAkBJ,EAAK,OAAO,MAAM,EAE7C,GAAIE,EAAWF,EAAK,MAAM,IAAM,SAC9B,OAAOI,EAAkBJ,EAAK,UAAU,CAAC,GAAKA,CAAI,EAGtD,CAEA,SAASK,GAAoBL,EAAuC,CAClE,IAAMM,EAASN,EAAK,OACdO,EAAOD,EAAO,OAAS,mBAAqBA,EAAO,OAAS,OAClE,MAAO,GACLC,GAAM,OAAS,kBACfA,EAAK,SAAWD,GAChBJ,EAAWI,CAAM,IAAM,UACvBF,EAAkBG,EAAK,UAAU,CAAC,GAAKA,CAAI,EAE/C,CAEO,IAAMC,GAAiBC,EAAwB,CACpD,KAAM,mBACN,KAAM,CACJ,KAAM,UACN,KAAM,CAAE,YAAa,kDAAmD,EACxE,QAAS,OACT,eAAgB,GAChB,OAAQ,CAAC,EACT,SAAU,CACR,SACE,wFACF,OACE,sEACF,MACE,mHACF,SACE,iEACJ,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,IAAMC,EAASD,EAAQ,WACjBE,EAAWC,EAAeH,CAAO,EACnCI,EACEC,EAAY,CAChBf,EACAgB,EACAC,IACG,CACH,IAAMC,EAAO,CAAE,KAAAlB,EAAM,UAAW,OAAiB,EAEjD,GADgBmB,EAAcR,EAAQG,EAASd,CAAI,IACnC,WAAY,CAC1B,IAAMoB,EAAQC,GAAeV,EAAQX,CAAI,EACzCU,EAAQ,OAAO,CACb,GAAGQ,EACH,QAAS,CACP,CACE,UAAW,WACX,IAAMI,GACJC,EAAYD,EAAOX,EAAQG,EAASE,EAASC,EAAOG,CAAK,CAC7D,CACF,CACF,CAAC,EACD,MACF,CACAV,EAAQ,OAAO,CACb,GAAGQ,EACH,IAAMI,GAAU,CACd,IAAME,EAAQC,EAAoBX,CAAO,GAAK,SAC9C,OAAOS,EAAYD,EAAOX,EAAQG,EAASE,EAASC,EAAOO,CAAK,CAClE,CACF,CAAC,CACH,EACME,EAAoB1B,GAAkC,CAC1D,IAAMiB,EAAQd,GAAeH,CAAI,EACjC,OAAIiB,GACFF,EAAUf,EAAMA,EAAMiB,CAAK,EACpB,IAEF,EACT,EACA,MAAO,CACL,QAAQjB,EAAM,CACZc,EAAUd,CACZ,EACA,cAAcA,EAAM,CAEhBD,GAAiBC,CAAI,GACrB2B,EAAc3B,EAAK,UAAU,CAAC,CAAC,GAC/B,CAACK,GAAoBL,CAAI,GAEzBU,EAAQ,OAAO,CAAE,KAAAV,EAAM,UAAW,UAAW,CAAC,CAElD,EACA,eAAeA,EAAM,CACnB,GAAI,CAAA0B,EAAiB1B,CAAI,EAGzB,IAAID,GAAiBC,CAAI,GAAK2B,EAAc3B,EAAK,UAAU,CAAC,CAAC,EAAG,CAC9DU,EAAQ,OAAO,CAAE,KAAAV,EAAM,UAAW,UAAW,CAAC,EAC9C,MACF,CAEEA,EAAK,OAAO,OAAS,oBACrBE,EAAWF,EAAK,MAAM,IAAM,kBAC5BA,EAAK,UAAU,KAAK2B,CAAa,GAEjCjB,EAAQ,OAAO,CAAE,KAAAV,EAAM,UAAW,UAAW,CAAC,EAElD,EACA,gBAAgBA,EAAM,CACpB,QAAW4B,KAAc5B,EAAK,YAAa,CACzC,IAAMiB,EAAQb,EAAkBwB,CAAU,EACtCX,EACFF,EAAUa,EAAYA,EAAYX,CAAK,EAC9BY,EAAcD,CAAU,GACjClB,EAAQ,OAAO,CAAE,KAAMkB,EAAY,UAAW,OAAQ,CAAC,CAE3D,CACF,EACA,uBAAuB5B,EAAM,CAEzBA,EAAK,OAAO,OAAS,gBACrBA,EAAK,WAAW,OAAS,uBAKzBI,EAAkBJ,EAAK,UAAU,GACjC6B,EAAc7B,EAAK,UAAU,EAE7BU,EAAQ,OAAO,CAAE,KAAMV,EAAK,WAAY,UAAW,OAAQ,CAAC,EACnD8B,EAAWlB,EAAUZ,EAAK,UAAU,GAC7CU,EAAQ,OAAO,CAAE,KAAMV,EAAK,WAAY,UAAW,QAAS,CAAC,EAEjE,CACF,CACF,CACF,CAAC,EC1KD,IAAM+B,EAAuD,CAC3D,IAAK,CAAE,KAAM,qBAAsB,KAAM,kCAAmC,EAC5E,SAAU,CAAE,KAAM,uBAAwB,KAAM,sBAAuB,EACvE,OAAQ,CAAE,KAAM,qBAAsB,KAAM,oBAAqB,CACnE,EAEA,SAASC,GACPC,EACAC,EACkB,CAClB,IAAMC,EAAOD,EAAK,MAAQD,EAAO,QAAQC,EAAK,KAAK,EAAI,GACvD,GAAI,qCAAqC,KAAKC,CAAI,EAChD,MAAO,MAET,GAAI,oDAAoD,KAAKA,CAAI,EAC/D,MAAO,WAET,GAAI,kDAAkD,KAAKA,CAAI,EAC7D,MAAO,QAGX,CAEO,IAAMC,GAAgBC,EAAyB,CACpD,KAAM,iBACN,KAAM,CACJ,KAAM,aACN,KAAM,CAAE,YAAa,gDAAiD,EACtE,QAAS,OACT,OAAQ,CAAC,EACT,SAAU,CACR,OAAQ,gEACV,CACF,EACA,eAAgB,CAAC,EACjB,OAAOC,EAAS,CACd,MAAO,CACL,kBAAkBJ,EAAM,CACtB,IAAMK,EAAQL,EAAK,WAAW,KAC3BM,GACCA,EAAU,OAAS,gBACnBA,EAAU,KAAK,OAAS,SAC5B,EACA,GAAI,CAACD,EACH,OAEF,IAAME,EAAOT,GAAYM,EAAQ,WAAYC,CAAK,EAEhD,CAACE,GACDP,EAAK,WAAW,KACbM,GACCA,EAAU,OAAS,gBACnBA,EAAU,KAAK,OAAST,EAAOU,CAAI,EAAE,IACzC,GAIFH,EAAQ,OAAO,CACb,KAAMC,EACN,UAAW,SACX,KAAM,CAAE,OAAQR,EAAOU,CAAI,EAAE,IAAK,EAClC,IAAMC,GACJA,EAAM,gBAAgBR,EAAK,KAAM,IAAIH,EAAOU,CAAI,EAAE,IAAI,EAAE,CAC5D,CAAC,CACH,CACF,CACF,CACF,CAAC,EfxDD,IAAME,GAAQ,CACZ,gBAAiBC,EACjB,sBAAuBC,EACvB,mBAAoBC,EACpB,iBAAkBC,EAClB,oBAAqBC,EACrB,mBAAoBC,GACpB,oBAAqBC,EACrB,eAAgBC,EAChB,iBAAkBC,GAClB,qBAAsBC,EACtB,iBAAkBC,CACpB,EAIMC,GAAU,OAAO,YACrB,OAAO,KAAKZ,EAAK,EAAE,IAAKa,GAAS,CAC/B,UAAUA,CAAI,GACdA,IAAS,iBAAmB,OAAS,OACvC,CAAC,CACH,EACMC,GAAc,CAAE,GAAGF,GAAS,wBAAyB,OAAQ,EAE7DG,EAAS,CACb,KAAM,CAAE,KAAM,uBAAwB,QAAS,OAAQ,EACvD,MAAAf,GACA,QAAS,CAAC,CACZ,EAEMgB,EAAO,CACX,KAAM,eACN,MAAO,CAAC,sCAAsC,EAC9C,gBAAiB,CAAE,OAAAC,EAAO,EAC1B,QAAS,CAAE,OAAQF,EAAQ,WAAYG,GAAS,cAAeC,EAAW,EAC1E,MAAO,CACL,GAAGD,GAAQ,YAAY,YAAY,MACnC,6BAA8B,QAC9B,GAAGN,EACL,CACF,EACMQ,EAAgB,CACpB,KAAM,wBACN,MAAO,CAAC,2BAA4B,uBAAuB,EAC3D,MAAO,CAAE,wBAAyB,KAAM,CAC1C,EAKMC,EAAQ,CACZ,KAAM,eACN,MAAO,CAAC,uBAAuB,EAC/B,gBAAiB,CAAE,cAAe,CAAE,eAAgB,EAAK,CAAE,CAC7D,EAEAN,EAAO,QAAU,CACf,YAAa,CAACC,EAAMK,EAAOD,CAAa,EACxC,OAAQ,CACN,CAAE,GAAGJ,EAAM,MAAO,CAAE,GAAGA,EAAK,MAAO,GAAGF,EAAY,CAAE,EACpDO,EACAD,CACF,EACA,kBAAmB,CACjBJ,EACAK,EACAD,EACA,CACE,KAAM,sBACN,MAAO,CAAC,gCAAgC,EACxC,MAAO,CAAE,uBAAwB,KAAM,CACzC,EACA,CACE,KAAM,0BACN,MAAO,CAAC,4CAA4C,EACpD,MAAO,CAAE,wBAAyB,KAAM,CAC1C,EACA,CACE,KAAM,wBACN,MAAO,CACL,4BACA,0CAEA,iCACF,EACA,MAAO,CACL,2BAA4B,MAC5B,0BAA2B,KAC7B,CACF,EACA,CACE,KAAM,0BACN,MAAO,CAAC,gDAAgD,EACxD,MAAO,CAAE,2BAA4B,KAAM,CAC7C,EACA,CACE,KAAM,gCACN,MAAO,CACL,4CACA,0CACA,2CACA,2CACF,EACA,MAAO,CAAE,2BAA4B,KAAM,CAC7C,CACF,CACF,EAeA,IAAOE,GAAQC","names":["parser","jsxA11y","reactHooks","ESLintUtils","createRule","name","cartName","storageKey","calleeName","node","variableName","cartInitial","value","cartViaSdk","createRule","context","method","key","id","call","minimatch","endpointPattern","trackingPattern","paymentHostPattern","staticText","node","part","jsxAttribute","name","attribute","jsxValue","isZappieEndpoint","value","isTrackingEndpoint","isInternalSdkImport","isServerFile","filename","patterns","path","pattern","autocomplete","paymentField","providerImport","value","checkoutViaSdk","createRule","context","node","tag","auto","jsxValue","jsxAttribute","name","id","target","cardInput","providerTarget","paymentHostPattern","mappedBy","node","current","parent","firstOnly","source","callback","text","index","imagePriority","createRule","context","images","priority","jsxAttribute","disabled","targetsItem","attribute","expression","target","jsxValue","itemLinkPrefetch","createRule","context","links","node","item","jsxAttribute","defaults","authValue","browserStorage","globals","isStorage","node","memberKey","staticText","authImport","value","noCustomAuth","createRule","context","options","server","isServerFile","kind","type","jsxValue","jsxAttribute","auto","first","key","cookie","message","value","noDirectApi","createRule","context","inspect","node","staticText","isZappieEndpoint","isInternalSdkImport","ESLintUtils","moneyLiteralPattern","isMoneyLiteral","value","memberName","node","isAmountMinor","amountOwner","dividedMinorOwner","parserServices","context","declaredBySdkMoney","symbol","declaration","path","isSdkMoney","services","checker","tsNode","type","part","property","currencyStyle","noMoneyLiterals","createRule","context","services","parserServices","arithmetic","node","isAmountMinor","isSdkMoney","isMoneyLiteral","dividedMinorOwner","memberName","quasi","imports","imported","value","noTrackingScripts","createRule","context","inspect","node","staticText","isTrackingEndpoint","callee","direct","dataLayer","defaults","name","node","carriesKey","keys","key","part","property","overlayPrimitive","createRule","context","options","searchValues","derivedFromSearch","text","value","callee","argument","dialog","role","attribute","open","sdkImport","program","node","importedFormat","declaration","specifier","importFix","fixer","source","local","importedName","named","last","first","text","formatFixes","replace","owner","fixes","addition","formatBinding","scope","variable","importedFormatLocal","availableAlias","names","name","intlNumberFormat","node","callee","memberName","formattedMinor","dividedMinorOwner","formatsDividedMinor","member","call","useFormatMoney","createRule","context","source","services","parserServices","program","reportFix","replace","owner","data","formatBinding","alias","availableAlias","fixer","formatFixes","local","importedFormatLocal","inspectFormatter","currencyStyle","expression","isAmountMinor","isSdkMoney","marker","handlerKind","source","node","text","verifyMarkers","createRule","context","click","attribute","kind","fixer","rules","noDirectApi","noTrackingScripts","checkoutViaSdk","noCustomAuth","noMoneyLiterals","useFormatMoney","overlayPrimitive","cartViaSdk","verifyMarkers","itemLinkPrefetch","imagePriority","enabled","name","strictRules","plugin","base","parser","jsxA11y","reactHooks","searchDialogs","typed","src_default","plugin"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/create-rule.ts","../src/rules/cart-via-sdk.ts","../src/matchers.ts","../src/rules/checkout-via-sdk.ts","../src/rules/image-priority.ts","../src/rules/item-link-prefetch.ts","../src/rules/no-custom-auth.ts","../src/rules/no-direct-api.ts","../src/rules/no-lazy-overlay.ts","../src/money-rule-utils.ts","../src/rules/no-money-literals.ts","../src/rules/no-tracking-scripts.ts","../src/rules/overlay-primitive.ts","../src/format-fix.ts","../src/rules/use-format-money.ts","../src/rules/verify-markers.ts"],"sourcesContent":["import parser from \"@typescript-eslint/parser\";\nimport jsxA11y from \"eslint-plugin-jsx-a11y\";\nimport reactHooks from \"eslint-plugin-react-hooks\";\nimport { cartViaSdk } from \"./rules/cart-via-sdk\";\nimport { checkoutViaSdk } from \"./rules/checkout-via-sdk\";\nimport { imagePriority } from \"./rules/image-priority\";\nimport { itemLinkPrefetch } from \"./rules/item-link-prefetch\";\nimport { noCustomAuth } from \"./rules/no-custom-auth\";\nimport { noDirectApi } from \"./rules/no-direct-api\";\nimport { noLazyOverlay } from \"./rules/no-lazy-overlay\";\nimport { noMoneyLiterals } from \"./rules/no-money-literals\";\nimport { noTrackingScripts } from \"./rules/no-tracking-scripts\";\nimport { overlayPrimitive } from \"./rules/overlay-primitive\";\nimport { useFormatMoney } from \"./rules/use-format-money\";\nimport { verifyMarkers } from \"./rules/verify-markers\";\n\nconst rules = {\n \"no-direct-api\": noDirectApi,\n \"no-tracking-scripts\": noTrackingScripts,\n \"checkout-via-sdk\": checkoutViaSdk,\n \"no-custom-auth\": noCustomAuth,\n \"no-money-literals\": noMoneyLiterals,\n \"use-format-money\": useFormatMoney,\n \"overlay-primitive\": overlayPrimitive,\n \"cart-via-sdk\": cartViaSdk,\n \"verify-markers\": verifyMarkers,\n \"item-link-prefetch\": itemLinkPrefetch,\n \"image-priority\": imagePriority,\n \"no-lazy-overlay\": noLazyOverlay,\n};\n\nexport type ZappieFlatConfig = Record<string, unknown>;\n\nconst enabled = Object.fromEntries(\n Object.keys(rules).map((name) => [\n `zappie/${name}`,\n name === \"verify-markers\" ? \"warn\" : \"error\",\n ]),\n);\nconst strictRules = { ...enabled, \"zappie/verify-markers\": \"error\" };\n\nconst plugin = {\n meta: { name: \"eslint-plugin-zappie\", version: \"0.0.0\" },\n rules,\n configs: {} as Record<string, ZappieFlatConfig[]>,\n};\n\nconst base = {\n name: \"zappie/rules\",\n files: [\"**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}\"],\n languageOptions: { parser },\n plugins: { zappie: plugin, \"jsx-a11y\": jsxA11y, \"react-hooks\": reactHooks },\n rules: {\n ...jsxA11y.flatConfigs.recommended.rules,\n \"react-hooks/rules-of-hooks\": \"error\",\n ...enabled,\n },\n};\nconst searchDialogs = {\n name: \"zappie/search-dialogs\",\n files: [\"**/search/**/*.{jsx,tsx}\", \"**/*search*.{jsx,tsx}\"],\n rules: { \"jsx-a11y/no-autofocus\": \"off\" },\n};\n\n// JavaScript files (eslint.config.mjs among them) usually sit outside the\n// tsconfig, where the project service refuses to parse them. They get the\n// syntactic money checks only.\nconst typed = {\n name: \"zappie/typed\",\n files: [\"**/*.{ts,tsx,mts,cts}\"],\n languageOptions: { parserOptions: { projectService: true } },\n};\n\nplugin.configs = {\n recommended: [base, typed, searchDialogs],\n strict: [\n { ...base, rules: { ...base.rules, ...strictRules } },\n typed,\n searchDialogs,\n ],\n \"zappie-internal\": [\n base,\n typed,\n searchDialogs,\n {\n name: \"zappie/internal-sdk\",\n files: [\"packages/sdk/src/**/*.{ts,tsx}\"],\n rules: { \"zappie/no-direct-api\": \"off\" },\n },\n {\n name: \"zappie/internal-auth-ui\",\n files: [\"packages/foundations/src/otp/**/*.{ts,tsx}\"],\n rules: { \"zappie/no-custom-auth\": \"off\" },\n },\n {\n name: \"zappie/internal-money\",\n files: [\n \"packages/sdk/src/money.ts\",\n \"packages/sdk/src/internal/money-math.ts\",\n // Its own arithmetic: importing money-math here splits it out of the core bundle.\n \"packages/sdk/src/cart/branch.ts\",\n // The starting-price rule compares and adds variant and choice prices.\n \"packages/sdk/src/pricing/index.ts\",\n ],\n rules: {\n \"zappie/no-money-literals\": \"off\",\n \"zappie/use-format-money\": \"off\",\n },\n },\n {\n name: \"zappie/internal-overlay\",\n files: [\"packages/foundations/src/overlay/**/*.{ts,tsx}\"],\n rules: { \"zappie/overlay-primitive\": \"off\" },\n },\n {\n name: \"zappie/internal-overlay-tests\",\n files: [\n \"themes/base/src/cart/cart-review.test.tsx\",\n \"themes/base/src/cart/core-cart.test.tsx\",\n \"themes/base/src/cart/fulfilment.test.tsx\",\n \"themes/base/src/cart/theme-class.test.tsx\",\n ],\n rules: { \"zappie/overlay-primitive\": \"off\" },\n },\n ],\n};\n\nexport {\n cartViaSdk,\n checkoutViaSdk,\n imagePriority,\n itemLinkPrefetch,\n noCustomAuth,\n noDirectApi,\n noLazyOverlay,\n noMoneyLiterals,\n noTrackingScripts,\n overlayPrimitive,\n useFormatMoney,\n verifyMarkers,\n};\nexport default plugin;\n","import { ESLintUtils } from \"@typescript-eslint/utils\";\n\nexport const createRule = ESLintUtils.RuleCreator(\n (name) => `https://docs.zappie.ai/lint/${name}`,\n);\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\n\nconst cartName = /^(cart|basket|bag)(Items|Lines|Count|Total)?$/i;\nconst storageKey = /cart|basket|bag/i;\n\nfunction calleeName(node: TSESTree.Node): string | undefined {\n if (node.type === \"Identifier\") {\n return node.name;\n }\n if (node.type === \"MemberExpression\" && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n return undefined;\n}\n\nfunction variableName(node: TSESTree.BindingName): string | undefined {\n if (node.type === \"Identifier\") {\n return node.name;\n }\n return node.type === \"ArrayPattern\" && node.elements[0]?.type === \"Identifier\"\n ? node.elements[0].name\n : undefined;\n}\n\nfunction cartInitial(node: TSESTree.CallExpression): boolean {\n const value =\n calleeName(node.callee) === \"useReducer\"\n ? node.arguments[1]\n : node.arguments[0];\n return Boolean(\n value &&\n value.type !== \"SpreadElement\" &&\n (value.type === \"ArrayExpression\" ||\n (value.type === \"Literal\" && typeof value.value === \"number\")),\n );\n}\n\nexport const cartViaSdk = createRule<[], \"cart\">({\n name: \"cart-via-sdk\",\n meta: {\n type: \"problem\",\n docs: { description: \"Keep your cart state in the SDK.\" },\n schema: [],\n messages: {\n cart: \"Use useCart(), useCartLine() or the SDK client cart. Keep cart state and storage in the SDK.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node) {\n const method = calleeName(node.callee);\n const key = node.arguments[0];\n if (\n [\"getItem\", \"setItem\", \"removeItem\", \"open\"].includes(method ?? \"\") &&\n key?.type === \"Literal\" &&\n typeof key.value === \"string\" &&\n storageKey.test(key.value)\n ) {\n context.report({ node, messageId: \"cart\" });\n }\n },\n VariableDeclarator(node) {\n if (node.init?.type !== \"CallExpression\") {\n return;\n }\n const id = variableName(node.id);\n const call = calleeName(node.init.callee);\n if (\n id &&\n cartName.test(id) &&\n [\"useState\", \"useReducer\", \"createContext\"].includes(call ?? \"\") &&\n cartInitial(node.init)\n ) {\n context.report({ node, messageId: \"cart\" });\n }\n if (\n id &&\n /cart/i.test(id) &&\n [\"create\", \"createStore\", \"atom\"].includes(call ?? \"\")\n ) {\n context.report({ node, messageId: \"cart\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { minimatch } from \"minimatch\";\n\nconst endpointPattern =\n /(?:api|accounts|checkout|edge)\\.zappie\\.ai|\\/storefront\\/v1|\\/customer\\/graphql/i;\nconst trackingPattern =\n /googletagmanager\\.com|google-analytics\\.com|connect\\.facebook\\.net|analytics\\.tiktok\\.com|static\\.hotjar\\.com|cdn\\.segment\\.com|plausible\\.io|clarity\\.ms/i;\n\nexport const paymentHostPattern =\n /checkout\\.paystack\\.com|flutterwave|(?:^|\\.)stripe\\.com/i;\n\nexport function staticText(node: TSESTree.Node | null): string | undefined {\n if (node?.type === \"Literal\" && typeof node.value === \"string\") {\n return node.value;\n }\n if (node?.type === \"TemplateLiteral\") {\n return node.quasis\n .map((part) => part.value.cooked ?? part.value.raw)\n .join(\"\");\n }\n return undefined;\n}\n\nexport function jsxAttribute(\n node: TSESTree.JSXOpeningElement,\n name: string,\n): TSESTree.JSXAttribute | undefined {\n return node.attributes.find(\n (attribute): attribute is TSESTree.JSXAttribute =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.type === \"JSXIdentifier\" &&\n attribute.name.name.toLowerCase() === name.toLowerCase(),\n );\n}\n\nexport function jsxValue(node?: TSESTree.JSXAttribute): string | undefined {\n if (!node?.value) {\n return undefined;\n }\n if (node.value.type === \"Literal\") {\n return typeof node.value.value === \"string\" ? node.value.value : undefined;\n }\n if (node.value.type === \"JSXExpressionContainer\") {\n return staticText(node.value.expression);\n }\n return undefined;\n}\n\nexport const isZappieEndpoint = (value: string) => endpointPattern.test(value);\nexport const isTrackingEndpoint = (value: string) =>\n trackingPattern.test(value);\nexport const isInternalSdkImport = (value: string) =>\n value === \"@zappie/sdk/internal\" || value.startsWith(\"@zappie/sdk/internal/\");\n\nexport function isServerFile(filename: string, patterns: string[]): boolean {\n const path = filename.replaceAll(\"\\\\\", \"/\");\n return patterns.some((pattern) =>\n minimatch(path, pattern, { matchBase: true }),\n );\n}\n","import { createRule } from \"../create-rule\";\nimport { jsxAttribute, jsxValue, paymentHostPattern } from \"../matchers\";\n\nconst autocomplete = new Set([\"cc-number\", \"cc-exp\", \"cc-csc\", \"cc-name\"]);\nconst paymentField = /card.?number|cvv|cvc|expiry/i;\n\nfunction providerImport(value: string): boolean {\n return (\n value.startsWith(\"@paystack/\") ||\n value === \"react-paystack\" ||\n value === \"flutterwave-react-v3\" ||\n value.startsWith(\"@stripe/\")\n );\n}\n\nexport const checkoutViaSdk = createRule<[], \"checkout\">({\n name: \"checkout-via-sdk\",\n meta: {\n type: \"problem\",\n docs: { description: \"Open checkout through the Zappie SDK.\" },\n schema: [],\n messages: {\n checkout:\n \"Use createCheckout().open() from @zappie/sdk/checkout. Let checkout collect payment details.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n ImportDeclaration(node) {\n if (providerImport(node.source.value)) {\n context.report({ node: node.source, messageId: \"checkout\" });\n }\n },\n JSXOpeningElement(node) {\n const tag =\n node.name.type === \"JSXIdentifier\"\n ? node.name.name.toLowerCase()\n : \"\";\n const auto = jsxValue(jsxAttribute(node, \"autocomplete\"));\n const name = jsxValue(jsxAttribute(node, \"name\"));\n const id = jsxValue(jsxAttribute(node, \"id\"));\n const target = jsxValue(\n jsxAttribute(node, tag === \"iframe\" ? \"src\" : \"href\"),\n );\n const cardInput =\n tag === \"input\" &&\n ((auto !== undefined && autocomplete.has(auto.toLowerCase())) ||\n (name !== undefined && paymentField.test(name)) ||\n (id !== undefined && paymentField.test(id)));\n const providerTarget =\n (tag === \"iframe\" || tag === \"a\") &&\n target !== undefined &&\n paymentHostPattern.test(target);\n if (cardInput || providerTarget) {\n context.report({ node, messageId: \"checkout\" });\n }\n },\n };\n },\n});\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { jsxAttribute } from \"../matchers\";\n\ntype Callback = TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;\n\nfunction mappedBy(node: TSESTree.Node) {\n let current: TSESTree.Node | undefined = node;\n while (current?.parent) {\n const parent: TSESTree.Node = current.parent;\n if (\n (parent.type === \"ArrowFunctionExpression\" ||\n parent.type === \"FunctionExpression\") &&\n parent.parent?.type === \"CallExpression\" &&\n parent.parent.callee.type === \"MemberExpression\" &&\n parent.parent.callee.property.type === \"Identifier\" &&\n parent.parent.callee.property.name === \"map\"\n ) {\n return parent;\n }\n current = parent;\n }\n}\n\nfunction firstOnly(source: Readonly<TSESLint.SourceCode>, callback: Callback) {\n const text = source.getText(callback.parent);\n if (/\\.slice\\(\\s*0\\s*,\\s*1\\s*\\)\\.map/.test(text)) {\n return true;\n }\n const index = callback.params[1];\n if (index?.type !== \"Identifier\") {\n return false;\n }\n const first = new RegExp(`${index.name}\\\\s*(?:={2,3}\\\\s*0|<\\\\s*1)`);\n return first.test(source.getText(callback));\n}\n\nexport const imagePriority = createRule<[], \"mapped\" | \"routeLimit\">({\n name: \"image-priority\",\n meta: {\n type: \"problem\",\n docs: { description: \"Reserve priority for the first phone screen.\" },\n schema: [],\n messages: {\n mapped: \"Remove priority from repeated images or limit it to the first.\",\n routeLimit: \"Keep no more than two priority images in a route.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const images: TSESTree.JSXOpeningElement[] = [];\n return {\n JSXOpeningElement(node) {\n const priority = jsxAttribute(node, \"priority\");\n const disabled =\n priority?.value?.type === \"JSXExpressionContainer\" &&\n priority.value.expression.type === \"Literal\" &&\n priority.value.expression.value === false;\n if (\n node.name.type !== \"JSXIdentifier\" ||\n node.name.name !== \"Image\" ||\n !priority ||\n disabled\n ) {\n return;\n }\n images.push(node);\n const callback = mappedBy(node);\n if (callback && !firstOnly(context.sourceCode, callback)) {\n context.report({ node: priority, messageId: \"mapped\" });\n }\n },\n \"Program:exit\"() {\n if (\n /(?:^|\\/)routes?\\/[^/]+\\.[jt]sx$/.test(\n context.filename.replaceAll(\"\\\\\", \"/\"),\n ) &&\n images.length > 2\n ) {\n for (const node of images.slice(2)) {\n context.report({ node, messageId: \"routeLimit\" });\n }\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { jsxAttribute, jsxValue } from \"../matchers\";\n\nfunction targetsItem(attribute?: TSESTree.JSXAttribute) {\n const expression =\n attribute?.value?.type === \"JSXExpressionContainer\"\n ? attribute.value.expression\n : undefined;\n if (\n expression?.type === \"CallExpression\" &&\n expression.callee.type === \"Identifier\" &&\n expression.callee.name === \"itemHref\"\n ) {\n return true;\n }\n const target = jsxValue(attribute);\n return target !== undefined && /^\\/item(?:\\/|$)/.test(target);\n}\n\nexport const itemLinkPrefetch = createRule<[], \"prefetch\">({\n name: \"item-link-prefetch\",\n meta: {\n type: \"problem\",\n docs: { description: \"Prefetch item routes before shoppers tap.\" },\n schema: [],\n messages: {\n prefetch: 'Add prefetch=\"viewport\" so this item route is ready to tap.',\n },\n },\n defaultOptions: [],\n create(context) {\n const links = new Set<string>();\n return {\n ImportDeclaration(node) {\n if (![\"react-router\", \"react-router-dom\"].includes(node.source.value)) {\n return;\n }\n for (const item of node.specifiers) {\n if (\n item.type === \"ImportSpecifier\" &&\n item.imported.type === \"Identifier\" &&\n [\"Link\", \"NavLink\"].includes(item.imported.name)\n ) {\n links.add(item.local.name);\n }\n }\n },\n JSXOpeningElement(node) {\n if (\n node.name.type === \"JSXIdentifier\" &&\n links.has(node.name.name) &&\n targetsItem(jsxAttribute(node, \"to\")) &&\n !jsxAttribute(node, \"prefetch\")\n ) {\n context.report({ node, messageId: \"prefetch\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { isServerFile, jsxAttribute, jsxValue, staticText } from \"../matchers\";\n\ntype Options = [{ serverFiles?: string[] }];\nconst defaults = [\"**/*.server.*\", \"**/server/**\"];\nconst authValue = /otp|verify.?code|access.?token|refresh.?token|id.?token/i;\nconst browserStorage = new Set([\"localStorage\", \"sessionStorage\"]);\nconst globals = new Set([\"window\", \"globalThis\", \"self\"]);\n\nfunction isStorage(node: TSESTree.Node): boolean {\n if (node.type === \"Identifier\") {\n return browserStorage.has(node.name);\n }\n return (\n node.type === \"MemberExpression\" &&\n !node.computed &&\n node.object.type === \"Identifier\" &&\n globals.has(node.object.name) &&\n node.property.type === \"Identifier\" &&\n browserStorage.has(node.property.name)\n );\n}\n\nfunction memberKey(node: TSESTree.MemberExpression): string | undefined {\n if (!node.computed && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n return staticText(node.property);\n}\n\nfunction authImport(value: string): \"server\" | \"all\" | undefined {\n if (value === \"jose\" || value === \"jsonwebtoken\") {\n return \"server\";\n }\n if (\n value === \"next-auth\" ||\n value.startsWith(\"next-auth/\") ||\n value.startsWith(\"@auth/\") ||\n value === \"better-auth\" ||\n value.startsWith(\"better-auth/\") ||\n value === \"lucia\" ||\n value.startsWith(\"@clerk/\") ||\n value === \"firebase/auth\" ||\n value.startsWith(\"@supabase/auth-\")\n ) {\n return \"all\";\n }\n return undefined;\n}\n\nexport const noCustomAuth = createRule<Options, \"auth\">({\n name: \"no-custom-auth\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Sign customers in through the Zappie SDK.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n serverFiles: {\n type: \"array\",\n items: { type: \"string\" },\n uniqueItems: true,\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n auth: \"Use createCustomer().signIn() from @zappie/sdk/customer to sign customers in.\",\n },\n },\n defaultOptions: [{}],\n create(context, [options]) {\n const server = isServerFile(\n context.filename,\n options.serverFiles ?? defaults,\n );\n return {\n ImportDeclaration(node) {\n const kind = authImport(node.source.value);\n if (kind === \"all\" || (kind === \"server\" && !server)) {\n context.report({ node: node.source, messageId: \"auth\" });\n }\n },\n JSXOpeningElement(node) {\n if (\n node.name.type !== \"JSXIdentifier\" ||\n node.name.name.toLowerCase() !== \"input\"\n ) {\n return;\n }\n const type = jsxValue(jsxAttribute(node, \"type\"));\n const auto = jsxValue(jsxAttribute(node, \"autocomplete\"));\n if (\n type?.toLowerCase() === \"password\" ||\n (auto !== undefined &&\n [\"one-time-code\", \"current-password\", \"new-password\"].includes(\n auto.toLowerCase(),\n ))\n ) {\n context.report({ node, messageId: \"auth\" });\n }\n },\n CallExpression(node) {\n if (\n node.callee.type !== \"MemberExpression\" ||\n !isStorage(node.callee.object)\n ) {\n return;\n }\n const first = node.arguments[0];\n const value =\n first?.type === \"Identifier\" ? first.name : staticText(first ?? null);\n if (value && authValue.test(value)) {\n context.report({ node, messageId: \"auth\" });\n }\n },\n AssignmentExpression(node) {\n if (\n node.left.type === \"MemberExpression\" &&\n isStorage(node.left.object)\n ) {\n const key = memberKey(node.left);\n if (key && authValue.test(key)) {\n context.report({ node, messageId: \"auth\" });\n }\n return;\n }\n const cookie =\n node.left.type === \"MemberExpression\" &&\n node.left.object.type === \"Identifier\" &&\n node.left.object.name === \"document\" &&\n node.left.property.type === \"Identifier\" &&\n node.left.property.name === \"cookie\";\n const value = staticText(node.right);\n if (cookie && value && authValue.test(value)) {\n context.report({ node, messageId: \"auth\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { isInternalSdkImport, isZappieEndpoint, staticText } from \"../matchers\";\n\ntype Message = \"direct\" | \"internal\" | \"storefront\" | \"checkout\" | \"customer\";\n\nfunction message(value: string): Message {\n if (/checkout\\.zappie\\.ai|\\/checkout(?:\\/|$)/i.test(value)) {\n return \"checkout\";\n }\n if (/accounts\\.zappie\\.ai|\\/customer\\/graphql/i.test(value)) {\n return \"customer\";\n }\n if (/\\/storefront\\/v1/i.test(value)) {\n return \"storefront\";\n }\n return \"direct\";\n}\n\nexport const noDirectApi = createRule<[], Message>({\n name: \"no-direct-api\",\n meta: {\n type: \"problem\",\n docs: { description: \"Call Zappie through the SDK.\" },\n schema: [],\n messages: {\n direct:\n \"Call the matching public @zappie/sdk client to reach this Zappie endpoint.\",\n internal:\n \"Use a public SDK entry. Only Zappie-owned packages can import @zappie/sdk/internal.\",\n storefront:\n \"Use createZappie(config).store or .menu to read storefront data.\",\n checkout: \"Use createCheckout().open() from @zappie/sdk/checkout.\",\n customer: \"Use createCustomer() from @zappie/sdk/customer.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const inspect = (node: TSESTree.Literal | TSESTree.TemplateLiteral) => {\n if (node.parent.type === \"ImportDeclaration\") {\n return;\n }\n const value = staticText(node);\n if (value && isZappieEndpoint(value)) {\n context.report({ node, messageId: message(value) });\n }\n };\n return {\n Literal: inspect,\n TemplateLiteral: inspect,\n ImportDeclaration(node) {\n if (isInternalSdkImport(node.source.value)) {\n context.report({ node: node.source, messageId: \"internal\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\n\nconst react = new Set([\"react\"]);\n\n// React's own name for its default and namespace import, even without an import (a global build).\nconst conventionalNamespace = \"React\";\n\nexport const noLazyOverlay = createRule<[], \"lazy\" | \"suspense\">({\n name: \"no-lazy-overlay\",\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Load overlays with deferred() from @zappie/foundations, not React.lazy or Suspense.\",\n },\n schema: [],\n messages: {\n lazy: \"Load this component with deferred() from @zappie/foundations. React.lazy suspends, and React holds a suspended reveal back for about 300 ms, so the overlay opens late.\",\n suspense:\n \"Remove Suspense and load the component with deferred() from @zappie/foundations. It renders as soon as its code arrives and never suspends.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const lazyNames = new Set<string>();\n const suspenseNames = new Set<string>();\n const namespaces = new Set<string>([conventionalNamespace]);\n\n const fromNamespace = (\n object: TSESTree.Node,\n property: TSESTree.Node,\n wanted: string,\n ) =>\n object.type === \"Identifier\" &&\n namespaces.has(object.name) &&\n property.type === \"Identifier\" &&\n property.name === wanted;\n\n return {\n ImportDeclaration(node) {\n if (!react.has(node.source.value)) {\n return;\n }\n for (const specifier of node.specifiers) {\n if (specifier.type !== \"ImportSpecifier\") {\n namespaces.add(specifier.local.name);\n continue;\n }\n const imported =\n specifier.imported.type === \"Identifier\"\n ? specifier.imported.name\n : specifier.imported.value;\n if (imported === \"lazy\") {\n lazyNames.add(specifier.local.name);\n } else if (imported === \"Suspense\") {\n suspenseNames.add(specifier.local.name);\n }\n }\n },\n CallExpression(node) {\n const callee = node.callee;\n if (\n (callee.type === \"Identifier\" && lazyNames.has(callee.name)) ||\n (callee.type === \"MemberExpression\" &&\n !callee.computed &&\n fromNamespace(callee.object, callee.property, \"lazy\"))\n ) {\n context.report({ node, messageId: \"lazy\" });\n }\n },\n JSXOpeningElement(node) {\n const name = node.name;\n if (\n (name.type === \"JSXIdentifier\" && suspenseNames.has(name.name)) ||\n (name.type === \"JSXMemberExpression\" &&\n name.object.type === \"JSXIdentifier\" &&\n namespaces.has(name.object.name) &&\n name.property.name === \"Suspense\")\n ) {\n context.report({ node, messageId: \"suspense\" });\n }\n },\n };\n },\n});\n","import {\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from \"@typescript-eslint/utils\";\nimport type ts from \"typescript\";\n\nconst moneyLiteralPattern =\n /[₦€£]\\s?\\d|\\$\\s?\\d|NGN\\s?\\d|\\d+(?:\\.\\d{2})?\\s?(?:naira|NGN)/i;\n\nexport function isMoneyLiteral(value: string): boolean {\n return value.trim() !== \"$1\" && moneyLiteralPattern.test(value);\n}\n\nexport function memberName(node: TSESTree.Node): string | undefined {\n if (node.type !== \"MemberExpression\") {\n return undefined;\n }\n if (!node.computed && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n if (node.computed && node.property.type === \"Literal\") {\n return typeof node.property.value === \"string\"\n ? node.property.value\n : undefined;\n }\n return undefined;\n}\n\nexport const isAmountMinor = (node: TSESTree.Node) =>\n memberName(node) === \"amountMinor\";\n\nexport function amountOwner(\n node: TSESTree.Node,\n): TSESTree.Expression | undefined {\n return node.type === \"MemberExpression\" && isAmountMinor(node)\n ? node.object\n : undefined;\n}\n\nexport function dividedMinorOwner(\n node: TSESTree.Node,\n): TSESTree.Expression | undefined {\n if (\n node.type !== \"BinaryExpression\" ||\n node.operator !== \"/\" ||\n node.right.type !== \"Literal\" ||\n node.right.value !== 100\n ) {\n return undefined;\n }\n return amountOwner(node.left);\n}\n\ntype Services = ReturnType<typeof ESLintUtils.getParserServices>;\n\nexport function parserServices<\n MessageIds extends string,\n Options extends readonly unknown[],\n>(context: TSESLint.RuleContext<MessageIds, Options>): Services | undefined {\n try {\n return ESLintUtils.getParserServices(context, true);\n } catch {\n return undefined;\n }\n}\n\nfunction declaredBySdkMoney(symbol: ts.Symbol): boolean {\n if (!symbol.getName().includes(\"MoneyBrand\")) {\n return false;\n }\n return (symbol.getDeclarations() ?? []).some((declaration) => {\n const path = declaration.getSourceFile().fileName.replaceAll(\"\\\\\", \"/\");\n return (\n path.endsWith(\"/packages/sdk/src/money.ts\") ||\n /\\/node_modules\\/@zappie\\/sdk\\/.*\\.d\\.ts$/.test(path)\n );\n });\n}\n\nexport function isSdkMoney(\n services: Services | undefined,\n node: TSESTree.Node,\n): boolean {\n if (!services?.program) {\n return false;\n }\n const checker = services.program.getTypeChecker();\n const tsNode = services.esTreeNodeToTSNodeMap.get(node);\n const type = checker.getTypeAtLocation(tsNode);\n const types = type.isUnionOrIntersection() ? type.types : [type];\n return types.some((part) =>\n part.getProperties().some((property) => declaredBySdkMoney(property)),\n );\n}\n\nexport function currencyStyle(node: TSESTree.Node | undefined): boolean {\n if (node?.type !== \"ObjectExpression\") {\n return false;\n }\n return node.properties.some(\n (property) =>\n property.type === \"Property\" &&\n !property.computed &&\n ((property.key.type === \"Identifier\" && property.key.name === \"style\") ||\n (property.key.type === \"Literal\" && property.key.value === \"style\")) &&\n property.value.type === \"Literal\" &&\n property.value.value === \"currency\",\n );\n}\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport {\n dividedMinorOwner,\n isAmountMinor,\n isMoneyLiteral,\n isSdkMoney,\n memberName,\n parserServices,\n} from \"../money-rule-utils\";\n\nexport const noMoneyLiterals = createRule<\n [],\n \"arithmetic\" | \"literal\" | \"scale\"\n>({\n name: \"no-money-literals\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Use SDK prices and totals in your storefront.\",\n },\n schema: [],\n messages: {\n arithmetic:\n \"Use cart.subtotal, line.lineSubtotal, quote.subtotal, quote.lines totals or quote.total. Let the SDK calculate amounts.\",\n literal:\n \"Render SDK Money with format.money() or <Money> instead of a hard-coded amount.\",\n scale:\n \"Use format.money() to display amountMinor. Currencies use 0, 2, 3 or 4 minor-unit digits.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const services = parserServices(context);\n const arithmetic = (node: TSESTree.Node) =>\n isAmountMinor(node) || isSdkMoney(services, node);\n return {\n Literal(node) {\n if (\n typeof node.value === \"string\" &&\n node.parent.type !== \"ImportDeclaration\" &&\n isMoneyLiteral(node.value)\n ) {\n context.report({ node, messageId: \"literal\" });\n }\n },\n JSXText(node) {\n if (isMoneyLiteral(node.value)) {\n context.report({ node, messageId: \"literal\" });\n }\n },\n BinaryExpression(node) {\n if (![\"+\", \"-\", \"*\", \"/\"].includes(node.operator)) {\n return;\n }\n if (\n dividedMinorOwner(node) &&\n node.parent.type === \"MemberExpression\" &&\n node.parent.object === node &&\n memberName(node.parent) === \"toFixed\"\n ) {\n return;\n }\n if (arithmetic(node.left) || arithmetic(node.right)) {\n context.report({ node, messageId: \"arithmetic\" });\n }\n },\n TemplateLiteral(node) {\n for (const quasi of node.quasis) {\n if (isMoneyLiteral(quasi.value.cooked ?? quasi.value.raw)) {\n context.report({ node: quasi, messageId: \"literal\" });\n }\n }\n },\n CallExpression(node) {\n if (\n node.callee.type === \"MemberExpression\" &&\n memberName(node.callee) === \"toFixed\" &&\n node.arguments[0]?.type === \"Literal\" &&\n node.arguments[0].value === 2 &&\n dividedMinorOwner(node.callee.object)\n ) {\n context.report({ node, messageId: \"scale\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport { isTrackingEndpoint, staticText } from \"../matchers\";\n\nconst imports = [\n \"react-ga4\",\n \"react-facebook-pixel\",\n \"@vercel/analytics\",\n \"posthog-js\",\n];\n\nfunction imported(value: string): boolean {\n return imports.includes(value) || value.startsWith(\"@segment/\");\n}\n\nexport const noTrackingScripts = createRule<[], \"tracking\">({\n name: \"no-tracking-scripts\",\n meta: {\n type: \"problem\",\n docs: { description: \"Forward analytics through Zappie.\" },\n schema: [],\n messages: {\n tracking:\n \"Configure analytics.forward in zappie.config.ts to forward analytics. Remove the tracking script.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const inspect = (node: TSESTree.Literal | TSESTree.TemplateLiteral) => {\n if (node.parent.type === \"ImportDeclaration\") {\n return;\n }\n const value = staticText(node);\n if (value && isTrackingEndpoint(value)) {\n context.report({ node, messageId: \"tracking\" });\n }\n };\n return {\n Literal: inspect,\n TemplateLiteral: inspect,\n ImportDeclaration(node) {\n if (imported(node.source.value)) {\n context.report({ node: node.source, messageId: \"tracking\" });\n }\n },\n CallExpression(node) {\n const callee = node.callee;\n const direct =\n callee.type === \"Identifier\" && [\"gtag\", \"fbq\"].includes(callee.name);\n const dataLayer =\n callee.type === \"MemberExpression\" &&\n !callee.computed &&\n callee.object.type === \"Identifier\" &&\n callee.object.name === \"dataLayer\" &&\n callee.property.type === \"Identifier\" &&\n callee.property.name === \"push\";\n if (direct || dataLayer) {\n context.report({ node, messageId: \"tracking\" });\n }\n },\n };\n },\n});\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\n\nconst defaults = [\n \"modal\",\n \"sheet\",\n \"drawer\",\n \"dialog\",\n \"item\",\n \"cart\",\n \"cart_sheet\",\n \"cart_edit\",\n \"cart_clear\",\n \"search\",\n \"catalogue_search\",\n \"catalogue_categories\",\n \"product_note\",\n \"modal_id\",\n];\n\nfunction name(node: TSESTree.Node): string | undefined {\n if (node.type === \"Identifier\") {\n return node.name;\n }\n if (node.type === \"MemberExpression\" && node.property.type === \"Identifier\") {\n return node.property.name;\n }\n return undefined;\n}\n\nfunction carriesKey(node: TSESTree.Node, keys: Set<string>): boolean {\n if (node.type === \"Literal\" && typeof node.value === \"string\") {\n return [...keys].some((key) => new RegExp(`[?&]${key}=`).test(node.value));\n }\n if (node.type === \"TemplateLiteral\") {\n return node.quasis.some((part) =>\n carriesKey(\n { ...part, type: \"Literal\", value: part.value.raw } as never,\n keys,\n ),\n );\n }\n if (node.type === \"ObjectExpression\") {\n return node.properties.some(\n (property) =>\n property.type === \"Property\" &&\n ((property.key.type === \"Identifier\" && keys.has(property.key.name)) ||\n (property.key.type === \"Literal\" &&\n typeof property.key.value === \"string\" &&\n keys.has(property.key.value))),\n );\n }\n return false;\n}\n\nexport const overlayPrimitive = createRule<[{ keys?: string[] }], \"overlay\">({\n name: \"overlay-primitive\",\n meta: {\n type: \"problem\",\n docs: { description: \"Use Overlay to keep dialogs and sheets in the URL.\" },\n schema: [\n {\n type: \"object\",\n properties: { keys: { type: \"array\", items: { type: \"string\" } } },\n additionalProperties: false,\n },\n ],\n messages: {\n overlay:\n \"Use Overlay or useOverlay for dialogs and sheets in the URL. They handle Back, Forward and focus restoration.\",\n },\n },\n defaultOptions: [{}],\n create(context, [options]) {\n const keys = new Set([...defaults, ...(options.keys ?? [])]);\n const searchValues = new Set<string>();\n const derivedFromSearch = (node: TSESTree.Node) => {\n const text = context.sourceCode.getText(node);\n return (\n text.includes(\"useSearchParams(\") ||\n [...searchValues].some((value) =>\n new RegExp(`\\\\b${value}\\\\b`).test(text),\n )\n );\n };\n return {\n VariableDeclarator(node) {\n if (\n node.init?.type === \"CallExpression\" &&\n name(node.init.callee) === \"useSearchParams\"\n ) {\n if (node.id.type === \"Identifier\") {\n searchValues.add(node.id.name);\n } else if (\n node.id.type === \"ArrayPattern\" &&\n node.id.elements[0]?.type === \"Identifier\"\n ) {\n searchValues.add(node.id.elements[0].name);\n }\n } else if (\n node.id.type === \"Identifier\" &&\n node.init &&\n derivedFromSearch(node.init)\n ) {\n searchValues.add(node.id.name);\n }\n },\n CallExpression(node) {\n const callee = name(node.callee);\n const navigation =\n callee === \"setSearchParams\" ||\n callee === \"navigate\" ||\n callee === \"pushState\" ||\n callee === \"replaceState\" ||\n (node.callee.type === \"CallExpression\" &&\n name(node.callee.callee) === \"useNavigate\");\n if (\n navigation &&\n node.arguments.some(\n (argument) =>\n argument.type !== \"SpreadElement\" && carriesKey(argument, keys),\n )\n ) {\n context.report({ node, messageId: \"overlay\" });\n }\n },\n JSXOpeningElement(node) {\n const dialog =\n node.name.type === \"JSXIdentifier\" && node.name.name === \"dialog\";\n const role = node.attributes.some(\n (attribute) =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.name === \"role\" &&\n attribute.value?.type === \"Literal\" &&\n attribute.value.value === \"dialog\",\n );\n const open = node.attributes.find(\n (attribute) =>\n attribute.type === \"JSXAttribute\" && attribute.name.name === \"open\",\n );\n if (\n (dialog || role) &&\n open?.type === \"JSXAttribute\" &&\n open.value?.type === \"JSXExpressionContainer\" &&\n derivedFromSearch(open.value.expression)\n ) {\n context.report({ node, messageId: \"overlay\" });\n }\n },\n };\n },\n});\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\";\n\ntype Fixer = TSESLint.RuleFixer;\ntype Fix = TSESLint.RuleFix;\ntype Source = Readonly<TSESLint.SourceCode>;\n\nfunction sdkImport(\n program: TSESTree.Program,\n): TSESTree.ImportDeclaration | undefined {\n return program.body.find(\n (node): node is TSESTree.ImportDeclaration =>\n node.type === \"ImportDeclaration\" && node.source.value === \"@zappie/sdk\",\n );\n}\n\nfunction importedFormat(\n declaration: TSESTree.ImportDeclaration | undefined,\n): TSESTree.ImportSpecifier | undefined {\n return declaration?.specifiers.find(\n (specifier): specifier is TSESTree.ImportSpecifier =>\n specifier.type === \"ImportSpecifier\" &&\n specifier.imported.type === \"Identifier\" &&\n specifier.imported.name === \"format\",\n );\n}\n\nfunction importFix(\n fixer: Fixer,\n source: Source,\n program: TSESTree.Program,\n local: string,\n): Fix | undefined {\n const declaration = sdkImport(program);\n if (importedFormat(declaration)?.local.name === local) {\n return undefined;\n }\n const importedName = local === \"format\" ? \"format\" : `format as ${local}`;\n const named = declaration?.specifiers.filter(\n (specifier) => specifier.type === \"ImportSpecifier\",\n );\n if (declaration && declaration.importKind !== \"type\" && named?.length) {\n const last = named.at(-1);\n if (last) {\n return fixer.insertTextAfter(last, `, ${importedName}`);\n }\n }\n const first = program.body[0];\n const text = `import { ${importedName} } from \"@zappie/sdk\";\\n`;\n return first\n ? fixer.insertTextBefore(first, text)\n : fixer.insertTextAfter(program, text);\n}\n\nexport function formatFixes(\n fixer: Fixer,\n source: Source,\n program: TSESTree.Program,\n replace: TSESTree.Node,\n owner: TSESTree.Expression,\n local: string,\n): Fix[] {\n const fixes = [\n fixer.replaceText(replace, `${local}.money(${source.getText(owner)})`),\n ];\n const addition = importFix(fixer, source, program, local);\n if (addition) {\n fixes.push(addition);\n }\n return fixes;\n}\n\nexport function formatBinding(\n source: Source,\n program: TSESTree.Program,\n node: TSESTree.Node,\n): \"available\" | \"conflict\" | \"imported\" {\n if (importedFormat(sdkImport(program))) {\n return \"imported\";\n }\n let scope: TSESLint.Scope.Scope | null = source.getScope(node);\n while (scope) {\n if (scope.variables.some((variable) => variable.name === \"format\")) {\n return \"conflict\";\n }\n scope = scope.upper;\n }\n return \"available\";\n}\n\nexport function importedFormatLocal(\n program: TSESTree.Program,\n): string | undefined {\n return importedFormat(sdkImport(program))?.local.name;\n}\n\nexport function availableAlias(source: Source, node: TSESTree.Node): string {\n const names = new Set<string>();\n let scope: TSESLint.Scope.Scope | null = source.getScope(node);\n while (scope) {\n for (const variable of scope.variables) {\n names.add(variable.name);\n }\n scope = scope.upper;\n }\n for (const name of [\n \"zappieFormat\",\n \"zappieMoneyFormat\",\n \"formatMoneyValue\",\n ]) {\n if (!names.has(name)) {\n return name;\n }\n }\n return \"zappieSdkFormat\";\n}\n","import type { TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\nimport {\n availableAlias,\n formatBinding,\n formatFixes,\n importedFormatLocal,\n} from \"../format-fix\";\nimport {\n currencyStyle,\n dividedMinorOwner,\n isAmountMinor,\n isSdkMoney,\n memberName,\n parserServices,\n} from \"../money-rule-utils\";\n\ntype Message = \"currency\" | \"direct\" | \"minor\" | \"useAlias\";\n\nfunction intlNumberFormat(node: TSESTree.Node): boolean {\n const callee =\n node.type === \"NewExpression\" || node.type === \"CallExpression\"\n ? node.callee\n : undefined;\n return Boolean(\n callee?.type === \"MemberExpression\" &&\n callee.object.type === \"Identifier\" &&\n callee.object.name === \"Intl\" &&\n memberName(callee) === \"NumberFormat\",\n );\n}\n\nfunction formattedMinor(node: TSESTree.CallExpression) {\n if (node.callee.type !== \"MemberExpression\") {\n return undefined;\n }\n if (memberName(node.callee) === \"toLocaleString\") {\n return dividedMinorOwner(node.callee.object);\n }\n if (memberName(node.callee) === \"format\") {\n return dividedMinorOwner(node.arguments[0] ?? node);\n }\n return undefined;\n}\n\nfunction formatsDividedMinor(node: TSESTree.NewExpression): boolean {\n const member = node.parent;\n const call = member.type === \"MemberExpression\" ? member.parent : undefined;\n return Boolean(\n call?.type === \"CallExpression\" &&\n call.callee === member &&\n memberName(member) === \"format\" &&\n dividedMinorOwner(call.arguments[0] ?? call),\n );\n}\n\nexport const useFormatMoney = createRule<[], Message>({\n name: \"use-format-money\",\n meta: {\n type: \"problem\",\n docs: { description: \"Format SDK Money with format.money() or <Money>.\" },\n fixable: \"code\",\n hasSuggestions: true,\n schema: [],\n messages: {\n currency:\n \"Use format.money(). Intl currency defaults can differ from the API minor-unit digits.\",\n direct:\n \"Render SDK Money with format.money(value) or <Money value={value}>.\",\n minor:\n \"Use format.money(value). Dividing by 100 gives the wrong amount for currencies with 0, 3 or 4 minor-unit digits.\",\n useAlias:\n \"Import format with an unused alias and format this Money value.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const source = context.sourceCode;\n const services = parserServices(context);\n let program: TSESTree.Program;\n const reportFix = (\n node: TSESTree.Node,\n replace: TSESTree.Node,\n owner: TSESTree.Expression,\n ) => {\n const data = { node, messageId: \"minor\" as const };\n const binding = formatBinding(source, program, node);\n if (binding === \"conflict\") {\n const alias = availableAlias(source, node);\n context.report({\n ...data,\n suggest: [\n {\n messageId: \"useAlias\",\n fix: (fixer) =>\n formatFixes(fixer, source, program, replace, owner, alias),\n },\n ],\n });\n return;\n }\n context.report({\n ...data,\n fix: (fixer) => {\n const local = importedFormatLocal(program) ?? \"format\";\n return formatFixes(fixer, source, program, replace, owner, local);\n },\n });\n };\n const inspectFormatter = (node: TSESTree.CallExpression) => {\n const owner = formattedMinor(node);\n if (owner) {\n reportFix(node, node, owner);\n return true;\n }\n return false;\n };\n return {\n Program(node) {\n program = node;\n },\n NewExpression(node) {\n if (\n intlNumberFormat(node) &&\n currencyStyle(node.arguments[1]) &&\n !formatsDividedMinor(node)\n ) {\n context.report({ node, messageId: \"currency\" });\n }\n },\n CallExpression(node) {\n if (inspectFormatter(node)) {\n return;\n }\n if (intlNumberFormat(node) && currencyStyle(node.arguments[1])) {\n context.report({ node, messageId: \"currency\" });\n return;\n }\n if (\n node.callee.type === \"MemberExpression\" &&\n memberName(node.callee) === \"toLocaleString\" &&\n node.arguments.some(currencyStyle)\n ) {\n context.report({ node, messageId: \"currency\" });\n }\n },\n TemplateLiteral(node) {\n for (const expression of node.expressions) {\n const owner = dividedMinorOwner(expression);\n if (owner) {\n reportFix(expression, expression, owner);\n } else if (isAmountMinor(expression)) {\n context.report({ node: expression, messageId: \"minor\" });\n }\n }\n },\n JSXExpressionContainer(node) {\n if (\n node.parent.type === \"JSXAttribute\" ||\n node.expression.type === \"JSXEmptyExpression\"\n ) {\n return;\n }\n if (\n dividedMinorOwner(node.expression) ||\n isAmountMinor(node.expression)\n ) {\n context.report({ node: node.expression, messageId: \"minor\" });\n } else if (isSdkMoney(services, node.expression)) {\n context.report({ node: node.expression, messageId: \"direct\" });\n }\n },\n };\n },\n});\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport { createRule } from \"../create-rule\";\n\ntype Kind = \"add\" | \"checkout\" | \"signin\";\nconst marker: Record<Kind, { name: string; text: string }> = {\n add: { name: \"data-zappie-action\", text: 'data-zappie-action=\"add-to-cart\"' },\n checkout: { name: \"data-zappie-checkout\", text: \"data-zappie-checkout\" },\n signin: { name: \"data-zappie-signin\", text: \"data-zappie-signin\" },\n};\n\nfunction handlerKind(\n source: Readonly<TSESLint.SourceCode>,\n node: TSESTree.JSXAttribute,\n): Kind | undefined {\n const text = node.value ? source.getText(node.value) : \"\";\n if (/\\b(?:cart(?:\\.actions)?\\.add)\\s*\\(/.test(text)) {\n return \"add\";\n }\n if (/\\b(?:checkout|createCheckout\\([^)]*\\))\\.open\\s*\\(/.test(text)) {\n return \"checkout\";\n }\n if (/\\b(?:auth|createCustomer\\([^)]*\\))\\.signIn\\s*\\(/.test(text)) {\n return \"signin\";\n }\n return undefined;\n}\n\nexport const verifyMarkers = createRule<[], \"marker\">({\n name: \"verify-markers\",\n meta: {\n type: \"suggestion\",\n docs: { description: \"Mark shopping actions so verify can find them.\" },\n fixable: \"code\",\n schema: [],\n messages: {\n marker: \"Add {{marker}} so zappie verify can find this shopping action.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n JSXOpeningElement(node) {\n const click = node.attributes.find(\n (attribute): attribute is TSESTree.JSXAttribute =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.name === \"onClick\",\n );\n if (!click) {\n return;\n }\n const kind = handlerKind(context.sourceCode, click);\n if (\n !kind ||\n node.attributes.some(\n (attribute) =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.name === marker[kind].name,\n )\n ) {\n return;\n }\n context.report({\n node: click,\n messageId: \"marker\",\n data: { marker: marker[kind].text },\n fix: (fixer) =>\n fixer.insertTextAfter(node.name, ` ${marker[kind].text}`),\n });\n },\n };\n },\n});\n"],"mappings":"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","names":["parser","jsxA11y","reactHooks","ESLintUtils","createRule","name","cartName","storageKey","calleeName","node","variableName","cartInitial","value","cartViaSdk","createRule","context","method","key","id","call","minimatch","endpointPattern","trackingPattern","paymentHostPattern","staticText","node","part","jsxAttribute","name","attribute","jsxValue","isZappieEndpoint","value","isTrackingEndpoint","isInternalSdkImport","isServerFile","filename","patterns","path","pattern","autocomplete","paymentField","providerImport","value","checkoutViaSdk","createRule","context","node","tag","auto","jsxValue","jsxAttribute","name","id","target","cardInput","providerTarget","paymentHostPattern","mappedBy","node","current","parent","firstOnly","source","callback","text","index","imagePriority","createRule","context","images","priority","jsxAttribute","disabled","targetsItem","attribute","expression","target","jsxValue","itemLinkPrefetch","createRule","context","links","node","item","jsxAttribute","defaults","authValue","browserStorage","globals","isStorage","node","memberKey","staticText","authImport","value","noCustomAuth","createRule","context","options","server","isServerFile","kind","type","jsxValue","jsxAttribute","auto","first","key","cookie","message","value","noDirectApi","createRule","context","inspect","node","staticText","isZappieEndpoint","isInternalSdkImport","react","conventionalNamespace","noLazyOverlay","createRule","context","lazyNames","suspenseNames","namespaces","fromNamespace","object","property","wanted","node","specifier","imported","callee","name","ESLintUtils","moneyLiteralPattern","isMoneyLiteral","value","memberName","node","isAmountMinor","amountOwner","dividedMinorOwner","parserServices","context","declaredBySdkMoney","symbol","declaration","path","isSdkMoney","services","checker","tsNode","type","part","property","currencyStyle","noMoneyLiterals","createRule","context","services","parserServices","arithmetic","node","isAmountMinor","isSdkMoney","isMoneyLiteral","dividedMinorOwner","memberName","quasi","imports","imported","value","noTrackingScripts","createRule","context","inspect","node","staticText","isTrackingEndpoint","callee","direct","dataLayer","defaults","name","node","carriesKey","keys","key","part","property","overlayPrimitive","createRule","context","options","searchValues","derivedFromSearch","text","value","callee","argument","dialog","role","attribute","open","sdkImport","program","node","importedFormat","declaration","specifier","importFix","fixer","source","local","importedName","named","last","first","text","formatFixes","replace","owner","fixes","addition","formatBinding","scope","variable","importedFormatLocal","availableAlias","names","name","intlNumberFormat","node","callee","memberName","formattedMinor","dividedMinorOwner","formatsDividedMinor","member","call","useFormatMoney","createRule","context","source","services","parserServices","program","reportFix","replace","owner","data","formatBinding","alias","availableAlias","fixer","formatFixes","local","importedFormatLocal","inspectFormatter","currencyStyle","expression","isAmountMinor","isSdkMoney","marker","handlerKind","source","node","text","verifyMarkers","createRule","context","click","attribute","kind","fixer","rules","noDirectApi","noTrackingScripts","checkoutViaSdk","noCustomAuth","noMoneyLiterals","useFormatMoney","overlayPrimitive","cartViaSdk","verifyMarkers","itemLinkPrefetch","imagePriority","noLazyOverlay","enabled","name","strictRules","plugin","base","parser","jsxA11y","reactHooks","searchDialogs","typed","src_default","plugin"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-zappie",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Flat ESLint rules for Zappie storefront projects.",
5
5
  "license": "MIT",
6
6
  "repository": {