mobxjs/mobx · warning
Expected exactly one class declaration for '${target.name}'
Error message
Expected exactly one class declaration for '${target.name}' but found ${declarations.length} What it means
When the codemod rewrites a `decorate(ClassName, {...})` call it must locate the class declaration bound to that name via scope bindings, to move annotations into it. If zero declarations are found for the identifier, the transform cannot proceed for that call and warns instead.
Source
Thrown at packages/mobx-undecorate/src/undecorate.ts:181
let canRemoveDecorateCall = true
if (callPath.value.arguments.length !== 2) {
warn("Expected a decorate call with two arguments", callPath.value)
return
}
const target = callPath.value.arguments[0]
const decorators = callPath.value.arguments[1]
if (!j.Identifier.check(target)) {
// not targeting a class, just swap it with makeObservable
changed = true
// @ts-ignore // TODO: or "observable" ?
callPath.value.callee.name = "makeObservable"
needsInitializeImport = true
return
}
const declarations = callPath.scope.getBindings()[target.name]
if (declarations.length === 0) {
warn(
`Expected exactly one class declaration for '${target.name}' but found ${declarations.length}`,
target
)
return
}
const targetDeclaration = declarations[0].parentPath.value
if (!j.ClassDeclaration.check(targetDeclaration)) {
// not targeting a class, just swap it with makeObservable
changed = true
// @ts-ignore // TODO: or "observable" ?
callPath.value.callee.name = "makeObservable"
needsInitializeImport = true
return
}
const clazz: ClassDeclaration = targetDeclaration
// @ts-ignore
createConstructor(clazz, decorators, [])
needsInitializeImport = trueView on GitHub (pinned to 01211a698b)
Solutions
- Ensure the decorated class is declared in the same file as the decorate call; move decorate into the class's defining module before running the codemod.
- Convert the decorate call manually to `makeObservable(this, {...})` inside the class constructor.
- Check for typos in the identifier passed as the first argument.
Example fix
// before (in file A)
import { Store } from './store'
decorate(Store, { count: observable })
// after (move into store.ts constructor)
constructor() { makeObservable(this, { count: observable }) } Defensive patterns
Strategy: validation
Validate before calling
// ensure the decorated name is a class declared in the same file
const hasLocalClass = /class\s+Store\b/.test(src)
if (src.includes('decorate(Store') && !hasLocalClass) {
throw new Error('decorate target must be a class declared in the same file')
} Type guard
function isLocallyDeclaredClass(name, source) {
return new RegExp(`(class|const|function)\\s+${name}\\b`).test(source)
} Prevention
- Only call decorate on classes declared in the same module
- Avoid decorating imported/re-exported classes with the codemod
- Search the file for `class <Name>` matching the decorate target
When it happens
Trigger: `decorate(SomeName, {...})` where `SomeName` has no resolvable binding in the current scope — e.g. the class is imported (bindings resolve to the import, not a class declaration) or the name is undefined.
Common situations: decorate applied to an imported class from another module, decorate applied to re-exported names, or typos in the class name. The codemod only handles classes declared in the same file.
Related errors
- Expected a decorate call with two arguments
- Unexpected type
- Failed to find mobx import, can't add makeObservable as depe
- Found multiple decorators, skipping..
- Found non-mobx decorator @${expr.name}
AI-assisted analysis of mobxjs/mobx@01211a698b (2026-08-28).
Data as JSON: /api/errors/36c0d743b886d6da.
Report an issue: GitHub.