{"record":{"id":"eb1a3863988fdafe","repo":"denoland/deno","slug":"prefix-linter-plugin-rules-must-be-an-object","errorCode":null,"errorMessage":"${prefix}Linter plugin rules must be an object","messagePattern":"(.+?)Linter plugin rules must be an object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/js/40_lint.js","lineNumber":504,"sourceCode":"    throw new Error(`${prefix}Linter plugin name must be a string`);\n  }\n  if (!/^[a-z-]+$/.test(plugin.name)) {\n    throw new Error(\n      `${prefix}Linter plugin name must only contain lowercase letters (a-z) or hyphens (-).`,\n    );\n  }\n  if (plugin.name.startsWith(\"-\") || plugin.name.endsWith(\"-\")) {\n    throw new Error(\n      `${prefix}Linter plugin name must start and end with a lowercase letter.`,\n    );\n  }\n  if (plugin.name.includes(\"--\")) {\n    throw new Error(\n      `${prefix}Linter plugin name must not have consequtive hyphens.`,\n    );\n  }\n  if (typeof plugin.rules !== \"object\") {\n    throw new Error(`${prefix}Linter plugin rules must be an object`);\n  }\n  if (state.installedPlugins.has(plugin.name)) {\n    throw new Error(`Linter plugin ${plugin.name} has already been registered`);\n  }\n  state.plugins.push(plugin);\n  state.installedPlugins.add(plugin.name);\n\n  return {\n    name: plugin.name,\n    ruleNames: Object.keys(plugin.rules),\n  };\n}\n\n/**\n * @param {AstContext} ctx\n * @param {number} idx\n * @returns {FacadeNode | null}\n */","sourceCodeStart":486,"sourceCodeEnd":522,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/cli/js/40_lint.js#L486-L522","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst rules = {\n  \"no-foo\": { create(ctx) { return {}; } },\n};\nexport default rules;\n\n// after\nconst rules = {\n  \"no-foo\": { create(ctx) { return {}; } },\n};\nexport default { name: \"my-plugin\", rules };","handlingStrategy":"type-guard","validationCode":"function assertPluginShape(plugin) {\n  if (typeof plugin !== \"object\" || plugin === null) throw new Error(\"plugin must be an object\");\n  if (typeof plugin.name !== \"string\") throw new Error(\"plugin.name must be a string\");\n  if (typeof plugin.rules !== \"object\" || plugin.rules === null) {\n    throw new Error(\"plugin.rules must be a non-null object\");\n  }\n}","typeGuard":"/** @param {unknown} p @returns {p is Deno.lint.Plugin} */\nfunction isValidLintPlugin(p) {\n  return typeof p === \"object\" && p !== null &&\n    typeof p.name === \"string\" && /^[a-z-]+$/.test(p.name) &&\n    typeof p.rules === \"object\" && p.rules !== null;\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["deno","deno-lint","lint-plugin","validation","plugin-structure"],"backgroundTag":"plugin-validation-failed","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}