ESLint Plugin
The official ESLint plugin for Flexium helps you write better code by enforcing best practices and catching common mistakes.
Installation
npm install eslint-plugin-flexium --save-devBasic Usage
Add flexium to the plugins section of your .eslintrc configuration file and extend the recommended config:
{
"plugins": ["flexium"],
"extends": ["plugin:flexium/recommended"]
}Configurations
The plugin provides three preset configurations:
| Configuration | Description | Use Case |
|---|---|---|
recommended | Balanced rules for most projects | General development |
strict | Stricter rules for production code | Production applications |
all | Enable all rules as errors | Maximum code quality enforcement |
Using Configurations
{
"extends": ["plugin:flexium/recommended"]
}{
"extends": ["plugin:flexium/strict"]
}{
"extends": ["plugin:flexium/all"]
}Custom Configuration
Configure rules individually to match your project's needs:
{
"plugins": ["flexium"],
"rules": {
"flexium/no-signal-outside-reactive": "warn",
"flexium/effect-cleanup": "warn",
"flexium/no-side-effect-in-computed": "error",
"flexium/prefer-sync": "off"
}
}Rules
flexium/no-state-comparison
Prevent direct comparison of state() proxy values which always fail.
Why? State values returned by state() are Proxy objects. Direct comparison with === or boolean coercion always fails because:
stateValue === 5is alwaysfalse(Proxy !== primitive)if (stateValue)is alwaystrue(Proxy objects are always truthy)if (!stateValue)is alwaysfalse
Bad
const [count, setCount] = state(0);
const [isVisible, setVisible] = state(false);
// ❌ Direct comparison always fails
if (count === 5) {
doSomething(); // Never runs!
}
// ❌ Boolean coercion is unreliable
if (!isVisible) {
hide(); // Never runs! Proxy is always truthy
}
// ❌ Direct use in ternary
const message = count ? 'Has value' : 'Empty'; // Always 'Has value'Good
const [count, setCount] = state(0);
const [isVisible, setVisible] = state(false);
// ✅ Use function call syntax
if (count() === 5) {
doSomething();
}
// ✅ Use function call for boolean checks
if (!isVisible()) {
hide();
}
// ✅ Use unary plus for number comparison
if (+count === 5) {
doSomething();
}
// ✅ Use String() for string comparison
if (String(name) === 'Alice') {
greet();
}
// ✅ Direct property access is fine
if (user.id === 1) {
// Works because we're comparing the property, not the proxy
}flexium/no-signal-outside-reactive
Disallow reading signal values outside of reactive contexts.
Why? Signal reads outside of effect(), computed(), or JSX will not be tracked and won't trigger re-renders.
Bad
const count = signal(0);
// ❌ Signal read outside reactive context - won't trigger updates
if (count > 5) {
doSomething();
}Good
const count = signal(0);
// ✅ Signal read inside effect
effect(() => {
if (count > 5) {
doSomething();
}
});
// ✅ Signal read inside computed
const shouldDoSomething = computed(() => count > 5);
// ✅ Signal read inside JSX
const App = () => (
<div>
{count > 5 && <div>Count is greater than 5</div>}
</div>
);flexium/effect-cleanup
Enforce cleanup functions in effects that add event listeners or timers.
Why? Effects that add event listeners or timers without cleanup can cause memory leaks.
Bad
// ❌ No cleanup for event listener
effect(() => {
window.addEventListener('resize', handleResize);
});
// ❌ No cleanup for timer
effect(() => {
const interval = setInterval(() => {
console.log('Tick');
}, 1000);
});Good
// ✅ Returns cleanup function for event listener
effect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
});
// ✅ Returns cleanup function for timer
effect(() => {
const interval = setInterval(() => {
console.log('Tick');
}, 1000);
return () => clearInterval(interval);
});
// ✅ Effect without listeners/timers doesn't need cleanup
effect(() => {
console.log('Count changed:', count);
});flexium/no-side-effect-in-computed
Disallow side effects in computed functions.
Why? Computed values should be pure functions. Side effects belong in effect().
Bad
// ❌ Side effect in computed (console.log)
const doubled = computed(() => {
console.log('Computing...');
return count * 2;
});
// ❌ Mutation in computed
const users = signal([]);
const sortedUsers = computed(() => {
return users.sort(); // Mutates original array!
});
// ❌ DOM manipulation in computed
const displayText = computed(() => {
document.title = String(count); // DOM side effect!
return `Count: ${count}`;
});Good
// ✅ Pure computed
const doubled = computed(() => count * 2);
// ✅ Side effect in effect
effect(() => {
console.log('Count changed:', count);
});
// ✅ Non-mutating computed
const sortedUsers = computed(() => {
return [...users].sort(); // Creates new array
});
// ✅ DOM manipulation in effect
effect(() => {
document.title = String(count);
});flexium/prefer-sync
Suggest using sync() when multiple signals are updated consecutively.
Why? Multiple signal updates without syncing can cause unnecessary re-renders.
Bad
// ⚠️ Warning - multiple updates without sync (3 separate re-renders)
count.value = 1;
name.value = 'test';
active.value = true;
// ⚠️ Multiple signal updates in a function
function updateUser(id: number, data: UserData) {
userId.value = id;
userName.value = data.name;
userEmail.value = data.email;
userActive.value = data.active;
}Good
import { sync } from 'flexium';
// ✅ Synced updates (single re-render)
sync(() => {
count.value = 1;
name.value = 'test';
active.value = true;
});
// ✅ Synced function
function updateUser(id: number, data: UserData) {
sync(() => {
userId.value = id;
userName.value = data.name;
userEmail.value = data.email;
userActive.value = data.active;
});
}
// ✅ Single signal update doesn't need syncing
count.value = 1;Configuration
Configure the threshold for when to warn about consecutive updates:
{
"flexium/prefer-sync": ["warn", { "threshold": 2 }]
}Options:
threshold(default:2): Number of consecutive signal updates before warning.
Integration with Other Configs
The Flexium ESLint plugin works alongside other ESLint configurations:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:flexium/recommended"
],
"plugins": ["@typescript-eslint", "flexium"],
"rules": {
// Override specific rules if needed
"flexium/prefer-sync": "off"
}
}TypeScript Support
The plugin fully supports TypeScript projects. Make sure to install the TypeScript ESLint parser:
npm install @typescript-eslint/parser --save-dev{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["flexium"],
"extends": ["plugin:flexium/recommended"]
}Common Issues
Rule Not Working
If a rule isn't being enforced:
- Verify the plugin is listed in
pluginsarray - Check that the rule is enabled in your config
- Ensure your file extensions are included in ESLint's
--extoption - Restart your editor/IDE
Too Many Warnings
If you're getting too many warnings during migration:
- Start with
recommendedconfig instead ofstrictorall - Disable specific rules temporarily:json
{ "extends": ["plugin:flexium/recommended"], "rules": { "flexium/prefer-sync": "off" } } - Fix violations incrementally
- Gradually enable stricter rules
False Positives
If you encounter false positives:
Use ESLint disable comments for specific cases:
javascript// eslint-disable-next-line flexium/no-signal-outside-reactive const initialValue = count;Report the issue on GitHub
Best Practices
Use the Recommended Config
Start with the recommended config and customize from there:
{
"extends": ["plugin:flexium/recommended"],
"rules": {
// Add project-specific overrides here
}
}Enable Auto-Fix
Many rules support auto-fix. Run ESLint with the --fix flag:
npx eslint . --fixIntegrate with Your Editor
Install ESLint extensions for your editor:
- VS Code: ESLint Extension
- WebStorm: Built-in ESLint support
- Vim: ALE or coc-eslint
Use Pre-Commit Hooks
Enforce ESLint checks before commits using husky and lint-staged:
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix"]
}
}Migrating Existing Projects
When adding ESLint to an existing Flexium project:
Install dependencies:
bashnpm install eslint eslint-plugin-flexium --save-devCreate configuration:
json{ "extends": ["plugin:flexium/recommended"] }Run ESLint:
bashnpx eslint .Fix automatically fixable issues:
bashnpx eslint . --fixFix remaining issues manually, using the rule documentation as a guide
Enable stricter rules gradually as your codebase improves