denoland/deno · error · Error
Linter plugin rules must be an object
Error message
${prefix}Linter plugin rules must be an object What it means
A Deno lint plugin must expose its rules as an object: `rules` maps rule names (like "no-foo") to rule objects with a create(ctx) method. installPlugin in cli/js/40_lint.js throws this error when typeof plugin.rules !== "object" — i.e. `rules` is missing, or is a string, number, boolean, or function. The check is prefixed with the plugin specifier when available.
Solutions
- Add a `rules` property that is a plain object: `rules: { "no-foo": { create(ctx) { ... } } }`.
- If you export the rules map separately, wrap it: `export default { name: "my-plugin", rules };`.
- Make sure the default export is the plugin object itself, not a function or the rules map.
- Add a smoke test that imports the plugin and asserts typeof plugin.rules === "object" && plugin.rules !== null.
Example fix
// before
const rules = {
"no-foo": { create(ctx) { return {}; } },
};
export default rules;
// after
const rules = {
"no-foo": { create(ctx) { return {}; } },
};
export default { name: "my-plugin", rules }; Defensive patterns
Strategy: type-guard
Validate before calling
function assertPluginShape(plugin) {
if (typeof plugin !== "object" || plugin === null) throw new Error("plugin must be an object");
if (typeof plugin.name !== "string") throw new Error("plugin.name must be a string");
if (typeof plugin.rules !== "object" || plugin.rules === null) {
throw new Error("plugin.rules must be a non-null object");
}
} Type guard
/** @param {unknown} p @returns {p is Deno.lint.Plugin} */
function isValidLintPlugin(p) {
return typeof p === "object" && p !== null &&
typeof p.name === "string" && /^[a-z-]+$/.test(p.name) &&
typeof p.rules === "object" && p.rules !== null;
} Prevention
- Always export `{ name, rules }` as the default export, never the rules map alone.
- Type the plugin as Deno.lint.Plugin so the compiler catches a missing `rules` key.
- Smoke-test plugin modules in CI before enabling them in lint.plugins.
When it happens
Trigger: Installing a plugin whose default export is `{ name: "my-plugin" }` (no `rules` key), exports the rules object directly instead of wrapping it, or sets `rules` to a string/number/function. Note that `rules: null` and arrays pass the typeof check and fail later (Object.keys on null throws), so the real requirement is a non-null plain object.
Common situations: Exporting `export default rules;` instead of `export default { name, rules };` when porting an ESLint plugin; forgetting the rules key in a minimal plugin skeleton; `rules` accidentally overwritten by a build step.
Related errors
- Linter plugin name must not have consequtive hyphens.
- Linter plugin name must only contain lowercase letters…
- Linter plugin name must start and end with a lowercase…
- Destroy hook of " " errored
- Empty node
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/eb1a3863988fdafe.
Report an issue: GitHub.
Appendix: source
Thrown at cli/js/40_lint.js:504
throw new Error(`${prefix}Linter plugin name must be a string`);
}
if (!/^[a-z-]+$/.test(plugin.name)) {
throw new Error(
`${prefix}Linter plugin name must only contain lowercase letters (a-z) or hyphens (-).`,
);
}
if (plugin.name.startsWith("-") || plugin.name.endsWith("-")) {
throw new Error(
`${prefix}Linter plugin name must start and end with a lowercase letter.`,
);
}
if (plugin.name.includes("--")) {
throw new Error(
`${prefix}Linter plugin name must not have consequtive hyphens.`,
);
}
if (typeof plugin.rules !== "object") {
throw new Error(`${prefix}Linter plugin rules must be an object`);
}
if (state.installedPlugins.has(plugin.name)) {
throw new Error(`Linter plugin ${plugin.name} has already been registered`);
}
state.plugins.push(plugin);
state.installedPlugins.add(plugin.name);
return {
name: plugin.name,
ruleNames: Object.keys(plugin.rules),
};
}
/**
* @param {AstContext} ctx
* @param {number} idx
* @returns {FacadeNode | null}
*/View on GitHub (pinned to 9ad36f7a2c)