@vmlts/jevlint
v0.1.0
Published
Write ESLint and oxlint rules as questions. Jev answers them.
Readme
@vmlts/jevlint
Write ESLint and oxlint rules as questions. Jev answers them for each node, and your code decides what to report.
npm i -D @vmlts/jevlint
export TYPESAFE_API_KEY=...A rule
// team-rules.js
import { definePlugin, defineRule, noul, choice } from "@vmlts/jevlint";
export default definePlugin({
name: "team",
rules: {
"descriptive-names": defineRule({
on: "FunctionDeclaration, MethodDefinition",
ask: noul("Does the function name describe what its body does?"),
message: "Name does not describe the body",
}),
"actionable-todos": defineRule({
on: "comments",
ask: choice("How actionable is this comment?", {
actionable: "a TODO that names what to do and where",
vague: "a TODO with no concrete action",
not_a_todo: "not a TODO at all",
}),
report: (a) => a.choice === "vague" && "TODO has no concrete action",
}),
},
});Enable it like any plugin.
// eslint.config.js
import team from "./team-rules.js";
export default [{ files: ["**/*.js"], plugins: { team }, rules: { "team/descriptive-names": "warn" } }];// .oxlintrc.json
{ "jsPlugins": ["./team-rules.js"], "rules": { "team/descriptive-names": "warn" } }How a rule decides
| Field | What it does |
|---|---|
| on | An esquery selector, or "comments" for every comment in the file |
| ask | noul(question) for yes or no, choice(question, options) for one of several, score(question, levels) for a degree |
| state | What Jev sees for each node. Defaults to the node's source text. Receives text(), enclosingFunction(), sourceCode and filename |
| threshold | noul: the least acceptable probability of yes, default 0.5. score: the least acceptable score |
| message | Reported when the answer falls below the threshold |
| report | (answer, ctx) => message or false. Replaces threshold and message. Required for choice |
A config can move the threshold: "team/descriptive-names": ["warn", { "threshold": 0.7 }].
Phrase a noul question as the property good code has. The rule reports when Jev says no.
Cost and speed
- All matched nodes in a file go to Jev in one request per rule, ten nodes per request.
- Every answer is cached by question and node text under
node_modules/.cache/jevlint. A second run on unchanged code sends nothing. SetJEVLINT_CACHE_DIRto move the cache, andJEVLINT_LOG=1to print token usage per request. - The first run on a file blocks the linter for one round trip, about one second.
- When Jev cannot be reached, the plugin prints one line to stderr, reports nothing for these rules, and stops asking for the rest of the run. The lint run still passes.
Plugin options
definePlugin({ name, rules, model: "jev-latest", batchSize: 10, timeout: 8000, cacheDir })For tests, pass judge (a function that answers a batch) and cache: new MemoryCache() so no request leaves the machine.
Limits
- No autofix. Jev judges code, it does not write it.
- No type information. oxlint plugins cannot see types.
- Node 20.19 or 22.12 and later.
