After the Typescript version is upgraded, the AST tool annotation is abnormal (Invalid arguments)

The reason is that a project uses the Compiler API that comes with Typescript to generate the corresponding code, and appears when [email protected] > [email protected] is upgraded. createInterfaceDeclaration Invalid arguments.

TypeError: Invalid arguments
    at Object.createInterfaceDeclaration (...\
ode_modules\.pnpm\[email protected]\
ode_modules\typescript\lib\typescript.js:172145:19)
    at makeInterfaceDeclaration (...\src\helper\compiler\helper\ts-interface.ts:46:36)
    at ...\src\helper\compiler\ts-typings.ts:30:89
    at Array. map (<anonymous>)
    at createTSTypingsDeclaration (...\src\helper\compiler\ts-typings.ts:30:29)
    at tsCompiler (...\src\helper\compiler\index.ts:37:69)
    at ...\src\index.ts:52:52
    at ...\
ode_modules\.pnpm\[email protected]\
ode_modules\p-pipe\index.js:12:25
    at async Promise. all (index 0)
    at async openAPIWebClientGenerator (...\src\index.ts:59:3)
    at async actionApiGenerator (...\src\bin\action.ts:54:3)

Troubleshooting

Due to the large difference in the upgraded version, it took a lot of time to troubleshoot errors. The actual specific reason is that Typescript started strong parameter verification due to the abandonment of the decorators parameter.

And factory.createInterfaceDeclaration is also in it, starting at line 173993 of typescript.js:

factory.createInterfaceDeclaration = ts.buildOverload("createInterfaceDeclaration")
    .overload({<!-- -->
    0: function (modifiers, name, typeParameters, heritageClauses, members) {<!-- -->
        return createInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members);
    },
    1: function (_decorators, modifiers, name, typeParameters, heritageClauses, members) {<!-- -->
        return createInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members);
    },
})
    .bind({<!-- -->
    // Validation corresponding to the current version
    0: function (_a) {<!-- -->
        var modifiers = _a[0], name = _a[1], typeParameters = _a[2], heritageClauses = _a[3], members = _a[4], other = _a[5];
        return (other === undefined) & amp; & amp;
            (modifiers === undefined || ts.every(modifiers, ts.isModifier)) & amp; & amp;
            (name === undefined || !ts.isArray(name)) & amp; & amp;
            (typeParameters === undefined || ts.isArray(typeParameters)) & amp; & amp;
            (heritageClauses === undefined || ts.every(heritageClauses, ts.isHeritageClause)) & amp; & amp;
            (members === undefined || ts.every(members, ts.isTypeElement));
    },
    // Validation corresponding to the old version
    1: function (_a) {<!-- -->
        var decorators = _a[0], modifiers = _a[1], name = _a[2], typeParameters = _a[3], heritageClauses = _a[4], members = _a[5];
        return (decorators === undefined || ts.every(decorators, ts.isDecorator)) & amp; & amp;
            (modifiers === undefined || ts.isArray(modifiers)) & amp; & amp;
            (name === undefined || !ts.isArray(name)) & amp; & amp;
            (typeParameters === undefined || ts.every(typeParameters, ts.isTypeParameterDeclaration)) & amp; & amp;
            (heritageClauses === undefined || ts.every(heritageClauses, ts.isHeritageClause)) & amp; & amp;
            (members === undefined || ts.every(members, ts.isTypeElement));
    },
})
  // Corresponding to the old version verification injection obsolete information
  .deprecate({<!-- -->
    1: DISALLOW_DECORATORS
  })
  .finish();

You can see that createInterfaceDeclaration is now built by ts.buildOverload. After buildOverload is built, the API has a strong authentication type. If the incoming parameters are incorrect, it will will throw an error.

Part of the source code:

export function createOverload<T extends OverloadDefinitions>(name: string, overloads: T, binder: OverloadBinders<T>, deprecations?: OverloadDeprecations<T>) {<!-- -->
    Object.defineProperty(call, "name", {<!-- --> ...Object.getOwnPropertyDescriptor(call, "name"), value: name });

    if (deprecations) {<!-- -->
        for (const key of Object. keys(deprecations)) {<!-- -->
            const index = + key as (keyof T & number);
            if (!isNaN(index) & amp; & amp; hasProperty(overloads, `${<!-- -->index}`)) {<!-- -->
                overloads[index] = deprecate(overloads[index], {<!-- --> ...deprecations[index], name });
            }
        }
    }

    // The binder here can be seen as a validator
    const bind = createBinder(overloads, binder);
    return call as OverloadFunction<T>;

    function call(...args: OverloadParameters<T>) {<!-- -->
        const index = bind(args);
        const fn = index !== undefined ? overloads[index] : undefined;
        if (typeof fn === "function") {<!-- -->
            return fn(...args);
        }
        // Failure to pass validation throws an exception Invalid arguments
        throw new TypeError("Invalid arguments");
    }
}
function createBinder<T extends OverloadDefinitions>(overloads: T, binder: OverloadBinders<T>): OverloadBinder<T> {<!-- -->
    return args => {<!-- -->
        for (let i = 0; hasProperty(overloads, `${<!-- -->i}`) & amp; & amp; hasProperty(binder, `${<!-- -->i}`); i + + ) {<!-- -->
            const fn = binder[i];
            // Determine whether the verification is passed
            if (fn(args)) {<!-- -->
                return i as OverloadKeys<T>;
            }
        }
    };
}
export function buildOverload(name: string): OverloadBuilder {<!-- -->
    return {<!-- -->
        overload: overloads => ({<!-- -->
            bind: binder => ({<!-- -->
                finish: () => createOverload(name, overloads, binder),
                deprecate: deprecations => ({<!-- -->
                    finish: () => createOverload(name, overloads, binder, deprecations)
                })
            })
        })
    };
}

Reason of the problem

Since the Typescript Compiler API does not support adding Comments to the properties of the Interface, the previous solution was to insert Comments above the properties when creating the Interface.

 // export identifier
  const exportModifier = factory.createModifier(ts.SyntaxKind.ExportKeyword)
  // method name
  const interfaceName = factory. createIdentifier('MyInterface')
  return factory.createInterfaceDeclaration(
    [exportModifier],
    interfaceName,
    undefined,
    undefined,
    // The parameter definition here can only be inserted into TypeElement, and there is no strong verification before that can pass
    [
      // multi-line comment
      factory.createJSDocComment(
        ['1', '2', '3'].join('\
'),
        [],
      ),
      // interface properties
      factory. createPropertySignature(
        //...
      )
      // single line comment
      ts.addSyntheticLeadingComment(
        factory. createIdentifier(''),
        ts.SyntaxKind.SingleLineCommentTrivia,
        `~~~`,
        false,
      ),
      // interface properties
      factory. createPropertySignature(
        //...
      )
    ],
  )

Solution

Since the Typescript Compiler API does not support the access of annotations, if this method cannot be used, adding annotations will become very troublesome. The actual reason is that the logic of buildOverload is entered after passing in.

// verified the members of createInterfaceDeclaration
members === undefined || ts.every(members, ts.isTypeElement)

Therefore, this method cannot pass the parameter verification. At present, there is no particularly good method to directly repair the source code of createInterfaceDeclaration, but we can make magic changes to the method of isTypeElement , so as to cheat the parameter verification of createInterfaceDeclaration.

const isTypeElement = ts.isTypeElement
ts.isTypeElement = function(node): node is ts.TypeElement {<!-- -->
  return isTypeElement(node) || ts.isJSDoc(node) || ts.isIdentifier(node)
}