UNPKG

eslint-plugin-svelte

Version:
108 lines (107 loc) 3.99 kB
import { createRule } from '../utils/index.js'; import { getScope } from '../utils/ast-utils.js'; import { VERSION as SVELTE_VERSION } from 'svelte/compiler'; import semver from 'semver'; // Writable derived were introduced in Svelte 5.25.0 const shouldRun = semver.satisfies(SVELTE_VERSION, '>=5.25.0'); function isEffectOrEffectPre(node) { if (node.callee.type === 'Identifier') { return node.callee.name === '$effect'; } if (node.callee.type === 'MemberExpression') { return (node.callee.object.type === 'Identifier' && node.callee.object.name === '$effect' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'pre'); } return false; } function isValidFunctionArgument(argument) { if ((argument.type !== 'FunctionExpression' && argument.type !== 'ArrowFunctionExpression') || argument.params.length !== 0) { return false; } if (argument.body.type !== 'BlockStatement') { return false; } return argument.body.body.length === 1; } function isValidAssignment(statement) { if (statement.type !== 'ExpressionStatement') return false; const { expression } = statement; return (expression.type === 'AssignmentExpression' && expression.operator === '=' && expression.left.type === 'Identifier'); } function isStateVariable(init) { return (init?.type === 'CallExpression' && init.callee.type === 'Identifier' && init.callee.name === '$state'); } export default createRule('prefer-writable-derived', { meta: { docs: { description: 'Prefer using writable $derived instead of $state and $effect', category: 'Best Practices', recommended: true }, schema: [], messages: { unexpected: 'Prefer using writable $derived instead of $state and $effect', suggestRewrite: 'Rewrite $state and $effect to $derived' }, type: 'suggestion', conditions: [ { svelteVersions: ['5'], runes: [true, 'undetermined'] } ], hasSuggestions: true }, create(context) { if (!shouldRun) { return {}; } return { CallExpression: (node) => { if (!isEffectOrEffectPre(node) || node.arguments.length !== 1) { return; } const argument = node.arguments[0]; if (!isValidFunctionArgument(argument)) { return; } const statement = argument.body.body[0]; if (!isValidAssignment(statement)) { return; } const { left, right } = statement.expression; const scope = getScope(context, statement); const reference = scope.references.find((ref) => ref.identifier.type === 'Identifier' && ref.identifier.name === left.name); const def = reference?.resolved?.defs?.[0]; if (!def || def.type !== 'Variable' || def.node.type !== 'VariableDeclarator') { return; } const { init } = def.node; if (!isStateVariable(init)) { return; } context.report({ node: def.node, messageId: 'unexpected', suggest: [ { messageId: 'suggestRewrite', fix: (fixer) => { const rightCode = context.sourceCode.getText(right); return [fixer.replaceText(init, `$derived(${rightCode})`), fixer.remove(node)]; } } ] }); } }; } });