{"record":{"id":"36c0d743b886d6da","repo":"mobxjs/mobx","slug":"expected-exactly-one-class-declaration-for-targ","errorCode":null,"errorMessage":"Expected exactly one class declaration for '${target.name}' but found ${declarations.length}","messagePattern":"Expected exactly one class declaration for '(.+?)' but found (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/mobx-undecorate/src/undecorate.ts","lineNumber":181,"sourceCode":"                let canRemoveDecorateCall = true\n                if (callPath.value.arguments.length !== 2) {\n                    warn(\"Expected a decorate call with two arguments\", callPath.value)\n                    return\n                }\n                const target = callPath.value.arguments[0]\n                const decorators = callPath.value.arguments[1]\n\n                if (!j.Identifier.check(target)) {\n                    // not targeting a class, just swap it with makeObservable\n                    changed = true\n                    // @ts-ignore // TODO: or \"observable\" ?\n                    callPath.value.callee.name = \"makeObservable\"\n                    needsInitializeImport = true\n                    return\n                }\n                const declarations = callPath.scope.getBindings()[target.name]\n                if (declarations.length === 0) {\n                    warn(\n                        `Expected exactly one class declaration for '${target.name}' but found ${declarations.length}`,\n                        target\n                    )\n                    return\n                }\n                const targetDeclaration = declarations[0].parentPath.value\n                if (!j.ClassDeclaration.check(targetDeclaration)) {\n                    // not targeting a class, just swap it with makeObservable\n                    changed = true\n                    // @ts-ignore // TODO: or \"observable\" ?\n                    callPath.value.callee.name = \"makeObservable\"\n                    needsInitializeImport = true\n                    return\n                }\n                const clazz: ClassDeclaration = targetDeclaration\n                // @ts-ignore\n                createConstructor(clazz, decorators, [])\n                needsInitializeImport = true","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/mobxjs/mobx/blob/01211a698b64ea94de8e5f276ff8235fcf8ddf96/packages/mobx-undecorate/src/undecorate.ts#L163-L199","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"// before (in file A)\nimport { Store } from './store'\ndecorate(Store, { count: observable })\n// after (move into store.ts constructor)\nconstructor() { makeObservable(this, { count: observable }) }","handlingStrategy":"validation","validationCode":"// ensure the decorated name is a class declared in the same file\nconst hasLocalClass = /class\\s+Store\\b/.test(src)\nif (src.includes('decorate(Store') && !hasLocalClass) {\n  throw new Error('decorate target must be a class declared in the same file')\n}","typeGuard":"function isLocallyDeclaredClass(name, source) {\n  return new RegExp(`(class|const|function)\\\\s+${name}\\\\b`).test(source)\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["codemod","ast","scope-resolution","mobx-undecorate"],"backgroundTag":"codemod-skipped-unsupported-pattern","analyzedSha":"01211a698b64ea94de8e5f276ff8235fcf8ddf96","analyzedAt":"2026-08-28T22:10:08.831Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}