UNPKG

@shopify/shop-minis-react

Version:

React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)

130 lines (116 loc) 3.7 kB
/** * ESLint rule to prefer SDK hooks over native browser APIs * @fileoverview Enforce using Shop Minis SDK hooks instead of native browser APIs */ module.exports = { meta: { type: 'suggestion', docs: { description: 'Prefer Shop Minis SDK hooks over native browser APIs for better compatibility and functionality', category: 'Best Practices', recommended: true, }, messages: { preferAsyncStorage: 'Use useAsyncStorage (or useSecureStorage for sensitive data) from @shopify/shop-minis-react instead of localStorage. The SDK hook provides async storage that works reliably in the Shop mini-app environment.', }, schema: [ { type: 'object', properties: { apis: { type: 'object', description: 'Map of native APIs to SDK hooks', additionalProperties: { type: 'string', }, }, }, additionalProperties: false, }, ], }, create(context) { // Default API mappings const defaultApis = { localStorage: 'useAsyncStorage', sessionStorage: 'useAsyncStorage', } // Get user configuration or use defaults const options = context.options[0] || {} const apiMap = { ...defaultApis, ...(options.apis || {}), } return { MemberExpression(node) { // Check for direct access: localStorage.getItem() if ( node.object.type === 'Identifier' && Object.hasOwn(apiMap, node.object.name) ) { const apiName = node.object.name const sdkHook = apiMap[apiName] context.report({ node: node.object, messageId: 'preferAsyncStorage', data: { nativeApi: apiName, sdkHook, }, }) return } // Check for global access: window.localStorage or globalThis.localStorage if ( node.object.type === 'MemberExpression' && node.object.object.type === 'Identifier' && (node.object.object.name === 'window' || node.object.object.name === 'globalThis') && node.object.property.type === 'Identifier' && Object.hasOwn(apiMap, node.object.property.name) ) { const apiName = node.object.property.name const sdkHook = apiMap[apiName] context.report({ node: node.object, messageId: 'preferAsyncStorage', data: { nativeApi: apiName, sdkHook, }, }) } }, // Also catch direct references to localStorage/sessionStorage Identifier(node) { // Only flag if it's being used, not just referenced in imports const parent = node.parent // Skip if it's part of an import statement if ( parent.type === 'ImportSpecifier' || parent.type === 'ImportDefaultSpecifier' ) { return } // Skip if it's already part of a MemberExpression (handled above) if (parent.type === 'MemberExpression' && parent.object === node) { return } // Check if this is a direct reference to localStorage/sessionStorage if (!Object.hasOwn(apiMap, node.name)) { return } // Skip if it's the variable name being declared (e.g., const localStorage = ...) if (parent.type === 'VariableDeclarator' && parent.id === node) { return } context.report({ node, messageId: 'preferAsyncStorage', }) }, } }, }