UNPKG

@shopify/shop-minis-react

Version:

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

149 lines (132 loc) 4.13 kB
/** * ESLint rule to require fallbacks for environment variables * @fileoverview Disallow using import.meta.env without a fallback value * * In Shop Minis, environment variables work in development but are not available * in production. This rule ensures developers always provide fallback values. */ module.exports = { meta: { type: 'problem', docs: { description: 'Require fallback values when using import.meta.env variables', category: 'Best Practices', recommended: true, }, messages: { noFallback: 'Environment variable "{{ name }}" must have a fallback value. Environment variables are only available in development. Use || or ?? to provide a production fallback.', }, schema: [], }, create(context) { return { MemberExpression(node) { // Check if this is import.meta.env.SOMETHING if (!isImportMetaEnv(node)) { return } // Get the env variable name for the error message const envName = node.property.name || node.property.value || 'unknown' // Check if it has a fallback (|| or ??) if (hasFallback(node)) { return } context.report({ node, messageId: 'noFallback', data: { name: `import.meta.env.${envName}`, }, }) }, } }, } /** * Check if node is import.meta.env.SOMETHING */ function isImportMetaEnv(node) { // Must be a member expression with a property if (node.type !== 'MemberExpression' || !node.property) { return false } // The object should be import.meta.env const obj = node.object if ( obj.type === 'MemberExpression' && obj.object.type === 'MetaProperty' && obj.object.meta.name === 'import' && obj.object.property.name === 'meta' && obj.property.name === 'env' ) { return true } return false } /** * Check if the node has a fallback via || or ?? operator */ function hasFallback(node) { let current = node.parent // Walk up the tree to find if we're in a logical expression with fallback while (current) { // Direct fallback: import.meta.env.FOO || 'default' or import.meta.env.FOO ?? 'default' // Also handles chains: import.meta.env.A || import.meta.env.B || 'default' if ( current.type === 'LogicalExpression' && (current.operator === '||' || current.operator === '??') ) { // Check if this logical expression chain eventually has a non-env fallback if (logicalChainHasFallback(current)) { return true } } // Conditional expression: import.meta.env.FOO ? import.meta.env.FOO : 'default' if (current.type === 'ConditionalExpression') { // If used in the test part, it has a fallback (the alternate) if (node === current.test) { return true } // If used in the consequent and test is an env var check, it's valid if (node === current.consequent && isImportMetaEnv(current.test)) { return true } } current = current.parent } return false } /** * Check if a logical expression chain has a fallback at the end * For: A || B || C, we walk up to find the topmost || or ?? and check its right side */ function logicalChainHasFallback(node) { // Walk up to the topmost logical expression in the chain let topmost = node while ( topmost.parent && topmost.parent.type === 'LogicalExpression' && (topmost.parent.operator === '||' || topmost.parent.operator === '??') && topmost.parent.left === topmost ) { topmost = topmost.parent } // The rightmost value in the chain is the fallback // Check that it's not also an import.meta.env (that would mean no real fallback) const rightmost = getRightmostValue(topmost) return !isImportMetaEnv(rightmost) } /** * Get the rightmost value in a logical expression chain */ function getRightmostValue(node) { if ( node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??') ) { return getRightmostValue(node.right) } return node }