mobxjs/mobx · error

Unexpected type

Error message

Unexpected type

What it means

In the codemod that rewrites legacy decorators to makeObservable calls, when a class has private members the generated initializeObservables call needs the class name as a type argument. If the class node has no simple identifier name (e.g. an anonymous or complex class expression), the transform cannot produce the type reference and throws 'Unexpected type'.

Source

Thrown at packages/mobx-undecorate/src/undecorate.ts:442

        ])
        return property
    }

    function createConstructor(
        clazz: ClassDeclaration,
        members: ObjectExpression,
        privates: string[]
    ) {
        // makeObservable(this, { members })
        const initializeObservablesCall = j.expressionStatement(
            j.callExpression(
                j.identifier("makeObservable"),
                options?.keepDecorators ? [j.thisExpression()] : [j.thisExpression(), members]
            )
        )
        if (privates.length && !options?.keepDecorators) {
            if (typeof clazz.id!.name !== "string") {
                throw new Error("Unexpected type")
            }
            // @ts-ignore
            initializeObservablesCall.expression.typeArguments = j.tsTypeParameterInstantiation([
                j.tsTypeReference(j.identifier(clazz.id!.name)),
                j.tsUnionType(
                    // @ts-ignore
                    privates.map(member => j.tsLiteralType(j.stringLiteral(member)))
                )
            ])
        }

        const needsSuper = !!clazz.superClass
        let constructorIndex = clazz.body.body.findIndex(
            member => j.ClassMethod.check(member) && member.kind === "constructor"
        )

        // create a constructor
        if (constructorIndex === -1) {

View on GitHub (pinned to 01211a698b)

Solutions

  1. Give the class an explicit name before running the transform (e.g. assign the class expression to a named variable or declare a named class)
  2. Use `npx mobx-undecorate --keepDecorators` so the private-member makeObservable path with type arguments is skipped
  3. Manually rewrite the decorators to makeObservable for the offending class and exclude it from the codemod

Example fix

// before
export default class {
    @observable private x = 1
}
// after
class MyStore {
    @observable private x = 1
}
export default MyStore
Defensive patterns

Strategy: validation

Validate before calling

// before running the codemod, ensure classes are named
const files = glob('src/**/*.ts(x)')
for (const f of files) {
    if (/class\s*(\{|extends)/.test(read(f))) console.warn(`${f}: anonymous class found — name it before undecorate`)
}

Type guard

function isNamedClass(node: any): boolean {
    return node?.id?.type === 'Identifier' && typeof node.id.name === 'string'
}

Try / catch

try {
    runCodemod(files)
} catch (e) {
    if (e.message === 'Unexpected type') {
        console.error('Codemod hit an unnamed class; name all classes with private members and rerun')
    } else throw e
}

Prevention

When it happens

Trigger: Running the mobx-undecorate transform on a source file containing a class with private fields and a class node whose `id` is not a named identifier (anonymous class expression or otherwise unnamed class).

Common situations: Running npx mobx-undecorate --keepDecorators=false over codebases containing default-exported anonymous classes or class expressions assigned to variables; generated/minified input class definitions.

Related errors


AI-assisted analysis of mobxjs/mobx@01211a698b (2026-08-28). Data as JSON: /api/errors/6f447967851d8224. Report an issue: GitHub.