{"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/89f33cbef296a2b287f323d42de54c871fa69c77/cli/js/40_lint.js#L486-L522","documentation":"Thrown by installPlugin() in cli/js/40_lint.js when `plugin.rules` is not of type \"object\". The `rules` map is how the linter discovers a plugin's rules (it later iterates Object.keys(plugin.rules)), so a missing or non-object value makes the plugin unusable. Note the check is a loose typeof: `null` slips past it and fails later, but undefined, strings, numbers, booleans, and functions throw here.","triggerScenarios":"A plugin that only declares `name` and forgets `rules`; a plugin whose default export is a rule factory that returns `{ name, rule }` instead of `{ name, rules: { ... } }`; `rules` set to a single rule object's array or a string list.","commonSituations":"Writing a first Deno lint plugin from memory and omitting the `rules` wrapper; migrating an ESLint rule module where the export shape is `{ meta, create }` and assuming it maps directly; a typo like `rule:` instead of `rules:`.","solutions":["Add a `rules` object mapping rule names to rule objects: rules: { \"no-foo\": { create(ctx) { ... } } }","If you exported a single rule, wrap it: export default { name: \"pkg\", rules: { \"rule-name\": theRule } }","Verify the plugin's default export shape against Deno.lint.Plugin types (deno lint --json or the CLI's type declarations)"],"exampleFix":"// before\nexport default {\n  name: \"my-plugin\",\n  rule: {\n    create(ctx) { /* ... */ },\n  },\n};\n\n// after\nexport default {\n  name: \"my-plugin\",\n  rules: {\n    \"no-bad-pattern\": {\n      create(ctx) { /* ... */ },\n    },\n  },\n};","handlingStrategy":"type-guard","validationCode":"function assertPluginShape(plugin) {\n  if (typeof plugin !== \"object\" || plugin === null) throw new TypeError(\"plugin must be an object\");\n  if (typeof plugin.name !== \"string\") throw new TypeError(\"plugin.name must be a string\");\n  if (typeof plugin.rules !== \"object\" || plugin.rules === null) {\n    throw new TypeError(\"plugin.rules must be a non-null object\");\n  }\n  for (const [ruleName, rule] of Object.entries(plugin.rules)) {\n    if (typeof rule?.create !== \"function\") {\n      throw new TypeError(`rule '${ruleName}' is missing create(ctx)`);\n    }\n  }\n}","typeGuard":"/** @param {unknown} p */\nfunction isLintPlugin(p) {\n  return typeof p === \"object\" && p !== null &&\n    typeof /** @type {any} */ (p).name === \"string\" &&\n    typeof /** @type {any} */ (p).rules === \"object\" &&\n    /** @type {any} */ (p).rules !== null;\n}","tryCatchPattern":null,"preventionTips":["Type your plugin export against Deno's lint plugin types so shape errors surface in `deno check`","Add a tiny test importing the plugin default export and asserting name/rules/create presence"],"tags":["lint","plugin","validation","api-shape"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}