demike / TsUML2

Generates UML diagrams from TypeScript source code
MIT License
250 stars 33 forks source link

TypeError: Cannot read properties of undefined (reading 'getName') #21

Open alichry opened 1 year ago

alichry commented 1 year ago

Hi there,

While trying to run tsuml2 --glob test.ts, the following exception was thrown TypeError: Cannot read properties of undefined (reading 'getName'). Here is a reproducible example:

# test.ts
export interface ITest {
  run(): void;
  hide(): void;
}

export type ITransformedTest = {
  [m in string &  keyof ITest as `shouldI${Capitalize<m>}`]: boolean;
} & { // remove this and no exception would be thrown
  shouldIFight(): boolean; 
}

export class Should implements ITransformedTest {
  shouldIRun: boolean = true;
  shouldIHide: boolean = false;

  shouldIFight() {
    return ! this.shouldIRun && ! this.shouldIHide;
  }
}
alichry commented 1 year ago

While the exception is caught and printed, this is output SVG: out After applying the below patch to parser.jsgetClassOrInterfaceName,

...
        if (classOrIf instanceof SimpleAST.Type) {
-             name = classOrIf.getSymbol().getName();
+             name = classOrIf.getSymbol() ? classOrIf.getSymbol().getName() : undefined;

-             if (name === "__type") {
+             if (name === "__type" || name === undefined) {
                  name = classOrIf.getAliasSymbol().getName();
              }
...

no exceptions were thrown, resulting in: out Notice ITransformedTest is now shown in the UML diagram, however, the diagram lacks its properties. This is not ideal but at least it would include the type in the result.

Based on my analysis, when parseTypes is called for ITransformedTest it halts here.

Seems like we have two issues, retrieving a type name and parsing its properties. I am unfamiliar with the codebase but I am happy to create a PR. Do you think the above fix is proper? if so, I am happy to open a PR for the first issue, and if you would give me any pointers, I can start working on the second issue.

demike commented 1 year ago

Would be nice if you could provide a PR for the first issue. I will then take a look at the second problem ( where I placed the 'todo' comment 😁)

alichry commented 1 year ago

Thank you for your response @demike. I have submitted a PR to fix the first issue. I have this snippet in my workshop:

// parser.ts
/* introduce new function getMethodsAndProperties */
function getMethodsAndProperties(typeNode: SimpleAST.TypeNode) {
    console.log(`Node kind name ${typeNode.getKindName()}`);

    let propertyDeclarations: SimpleAST.PropertySignature[] = [];
    let methodDeclarations: SimpleAST.MethodSignature[] = [];
    if (typeNode instanceof SimpleAST.TypeLiteralNode) {
        propertyDeclarations = typeNode.getProperties();
        methodDeclarations = typeNode.getMethods();
    } else if (typeNode instanceof SimpleAST.IntersectionTypeNode) {
        const childNodes = typeNode.getTypeNodes();

        childNodes.forEach((tn, i) => {
            const pm = getMethodsAndProperties(tn);
            if (! pm) {
                return;
            }
            propertyDeclarations = propertyDeclarations.concat(pm.propertyDeclarations);
            methodDeclarations = methodDeclarations.concat(pm.methodDeclarations);
        });
    } else if (typeNode instanceof SimpleAST.MappedTypeNode) {
        const typeParameter = typeNode.getTypeParameter();
        const constraint = typeParameter?.getConstraint();

        console.log("MappedType constraint kind name:",  constraint?.getKindName());

        // typeNode.getChildren().forEach((child, i) => {
        //     console.log(`Child #${i} kind ${child.getKindName()} text ${child.getText()}`);
        //     // Type Parameter
        //     // AsKeyword
        //     if (child instanceof SimpleAST.TemplateLiteralTypeNode) {
        //         console.log("Child TemplateLiteralNode");
        //         console.log(child);
        //         child.forEachChild((ll) => {
        //             console.log(`LL ${ll.getText()}`);
        //         });
        //         child.forEachDescendant((dd) => {
        //             console.log(`DD ${dd}`);
        //         });
        //     }

        //     // Syntax List
        // });

        return;
    } else {
        return;
    }

    return {
        propertyDeclarations,
        methodDeclarations
    }
}

export function parseTypes(typeDeclaration: SimpleAST.TypeAliasDeclaration) {

    const name = getClassOrInterfaceName(typeDeclaration) || 'undefined';
    const t = typeDeclaration.getType();
    const typeNode = typeDeclaration.getTypeNode();

    let propertyDeclarations: SimpleAST.PropertySignature[] = [];
    let methodDeclarations: SimpleAST.MethodSignature[] = [];

    if(typeNode instanceof SimpleAST.TypeLiteralNode) {
        propertyDeclarations = typeNode.getProperties();
        methodDeclarations = typeNode.getMethods();
    } else if (typeNode instanceof SimpleAST.IntersectionTypeNode) {
        const res = getMethodsAndProperties(typeNode);
        if (! res) {
            return;
        }
        propertyDeclarations = res.propertyDeclarations;
        methodDeclarations = res.methodDeclarations;
    } else {
        // no structured type --> lets skip that (for now)
        return;
    }
    ...
}

With this change, shouldIFight is added to the UML. However, shouldIRun and shouldIHide are not present. I am currently trying to derive the property declarations from MappedTypeNode. out