@llui/lint-idiomatic
@llui/lint-idiomatic
AST linter for idiomatic LLui patterns. Catches common anti-patterns at the source level.
pnpm add -D @llui/lint-idiomatic
Usage
import { lintIdiomatic } from '@llui/lint-idiomatic'
const source = `
function update(state: State, msg: Msg): [State, Effect[]] {
switch (msg.type) {
case 'increment':
state.count++ // mutation!
return [state, []]
}
}
`
const { violations, score } = lintIdiomatic(source, 'counter.ts')
console.log(score) // 5 (out of 6)
console.log(violations) // [{ rule: 'state-mutation', line: 5, message: '...' }]
API
lintIdiomatic(source: string, filename?: string) => { violations: Violation[], score: number }
| Field |
Type |
Description |
violations |
Violation[] |
List of rule violations found |
score |
number |
Idiomatic score 0-15 (15 = fully idiomatic) |
Violation
| Field |
Type |
Description |
rule |
string |
Rule identifier |
line |
number |
Source line number |
message |
string |
Human-readable explanation |
Rules
| Rule |
Description |
state-mutation |
Direct mutation of state in update() instead of returning a new object |
missing-memo |
Expensive derived computation in view() without memo() |
each-closure-violation |
Capturing mutable outer variable inside each() render callback |
map-on-state-array |
Calling .map() on a state array in view() (use each() instead) |
unnecessary-child |
Using child() boundary when a view function would suffice |
form-boilerplate |
Repetitive form field pattern that could use a view function |
async-update |
Using async/await in update() -- must be synchronous and pure |
direct-state-in-view |
Stale state capture in event handler instead of using an accessor |
exhaustive-effect-handling |
Empty .else() handler silently drops unhandled effects |
effect-without-handler |
Component returns effects but has no onEffect handler |
forgotten-spread |
show()/branch()/each() used without spread in children array |
string-effect-callback |
Deprecated string-based onSuccess/onError in effect declarations |
nested-send-in-update |
Calling send() inside update() causes recursive dispatch |
imperative-dom-in-view |
Using document.querySelector etc. in view() instead of primitives |
accessor-side-effect |
Side effects (fetch, console.log, etc.) inside reactive accessor functions |
License
MIT