Skip to content

Instantly share code, notes, and snippets.

@dimfeld
Created September 7, 2025 22:39
Show Gist options
  • Save dimfeld/04d96e8315186f42939ee534b0be05ec to your computer and use it in GitHub Desktop.
Save dimfeld/04d96e8315186f42939ee534b0be05ec to your computer and use it in GitHub Desktop.
Eslint rule to catch Svelte `$derived(() => expression)`
/**
* ESLint rule to detect $derived(() => expression) patterns and suggest using $derived(expression) instead
*/
export default {
meta: {
type: 'suggestion',
docs: {
description:
'prefer using $derived(expression) over $derived(() => expression) for simple expressions',
category: 'Stylistic Issues',
recommended: true,
},
fixable: 'code',
schema: [],
messages: {
preferDirectDerived:
'Use $derived(expression) instead of $derived(() => expression) for simple expressions.',
preferDerivedBy:
'Use $derived.by(() => { ... }) instead of $derived(() => { ... }) for complex logic.',
},
},
create(context) {
/**
* Check if a node is a $derived call expression
*/
function isDerivedCall(node) {
return node.callee.type === 'Identifier' && node.callee.name === '$derived';
}
/**
* Check if the argument is an arrow function with a simple return expression
*/
function isSimpleArrowFunction(node) {
if (node.type !== 'ArrowFunctionExpression') {
return false;
}
// Only handle functions with no parameters
if (node.params.length > 0) {
return false;
}
// Check if it's a simple expression (not a block statement)
if (node.body.type === 'BlockStatement') {
// For block statements, check if it's a single return statement
if (node.body.body.length === 1) {
const stmt = node.body.body[0];
return stmt.type === 'ReturnStatement' && stmt.argument != null;
}
return false;
}
// It's an expression body, which is what we want to simplify
return true;
}
/**
* Check if the argument is a regular function with a simple return
*/
function isSimpleFunctionExpression(node) {
if (node.type !== 'FunctionExpression') {
return false;
}
// Only handle functions with no parameters
if (node.params.length > 0) {
return false;
}
// Must have a block body with a single return statement
if (node.body.type === 'BlockStatement' && node.body.body.length === 1) {
const stmt = node.body.body[0];
return stmt.type === 'ReturnStatement' && stmt.argument != null;
}
return false;
}
/**
* Check if the argument is a complex function (has parameters or complex body)
*/
function isComplexFunction(node) {
if (node.type !== 'ArrowFunctionExpression' && node.type !== 'FunctionExpression') {
return false;
}
// Has parameters
if (node.params.length > 0) {
return true;
}
// Arrow function with block statement (not simple expression)
if (node.type === 'ArrowFunctionExpression' && node.body.type === 'BlockStatement') {
// If it's not a simple single return, it's complex
if (node.body.body.length !== 1) {
return true;
}
const stmt = node.body.body[0];
if (stmt.type !== 'ReturnStatement' || stmt.argument == null) {
return true;
}
return false; // It's actually simple
}
// Function expression with complex body
if (node.type === 'FunctionExpression' && node.body.type === 'BlockStatement') {
// If it's not a simple single return, it's complex
if (node.body.body.length !== 1) {
return true;
}
const stmt = node.body.body[0];
if (stmt.type !== 'ReturnStatement' || stmt.argument == null) {
return true;
}
return false; // It's actually simple
}
return false;
}
/**
* Extract the expression from a simple function
*/
function extractExpression(node) {
if (node.type === 'ArrowFunctionExpression') {
if (node.body.type === 'BlockStatement') {
const stmt = node.body.body[0];
return stmt.argument;
}
return node.body;
}
if (node.type === 'FunctionExpression' && node.body.type === 'BlockStatement') {
const stmt = node.body.body[0];
return stmt.argument;
}
return null;
}
return {
CallExpression(node) {
// Check if this is a $derived call
if (!isDerivedCall(node)) {
return;
}
// Must have exactly one argument
if (node.arguments.length !== 1) {
return;
}
const arg = node.arguments[0];
// Check if the argument is a simple arrow function or function expression
if (isSimpleArrowFunction(arg) || isSimpleFunctionExpression(arg)) {
const expression = extractExpression(arg);
if (!expression) {
return;
}
context.report({
node: arg,
messageId: 'preferDirectDerived',
fix(fixer) {
const sourceCode = context.getSourceCode();
const expressionText = sourceCode.getText(expression);
// Replace the entire function argument with just the expression
return fixer.replaceText(arg, expressionText);
},
});
} else if (isComplexFunction(arg)) {
// Flag complex functions and suggest using $derived.by
context.report({
node: arg,
messageId: 'preferDerivedBy',
fix(fixer) {
const sourceCode = context.getSourceCode();
const functionText = sourceCode.getText(arg);
// Replace $derived with $derived.by
return fixer.replaceText(node.callee, '$derived.by');
},
});
}
},
};
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment