Repository: fabien0102/ts-to-zod (ts)

Issue:
Problem getting imports to work
## Bug description

I'm trying to generate zods from our types. We have a lot of files and some of them use types from others. I wrote a script collecting all the filenames and generating a config file (like mentioned here #151) ... looks good so far ... but. I have a problem with the import of another type from a different file:

```ts
// a.types.ts
export enum TemplateType {
  Accordion = "accordion",
  AlphabetFilter = "alphabet_filter",
  ArticleHeader = "article_header",
  Audio = "audio",
}
```

```ts
// b.types.ts
import { TemplateType } from "./a.types";

export type TAudioData = {
  a: string;
  b?: string;
};

export type TAudioRequest = {
  _template: TemplateType.Audio;
} & TAudioData;
```

and my config looks like this:

```
module.exports = [
  { name: "a", input: "src/a.types.ts", output: "src/a.zod.ts" },
  { name: "b", input: "src/b.types.ts", output: "src/b.zod.ts" },
];
```

If I now run `yarn ts-to-zod --all` I get the following error:

```
$ ts-to-zod --all
Generating "a"
Generating "b"
 ⚠ File "src/a.zod.ts" not found: maybe it hasn't been generated yet?
 ⚠ File "src/b.zod.ts" not found: maybe it hasn't been generated yet?
✖ Validating generated types
 ›   Error: 'tAudioRequestSchema' is not compatible with 'TAudioRequest':
 ›   Argument of type '{ [x: string]: any; _template?: unknown; } & { a: string; b?: 
 ›   string | undefined; }' is not assignable to parameter of type 'TAudioRequest'.
 ›     Type '{ [x: string]: any; _template?: unknown; } & { a: string; b?: string | 
 ›   undefined; }' is not assignable to type '{ _template: TemplateType.Audio; }'.
 ›       Types of property '_template' are incompatible.
 ›         Type 'unknown' is not assignable to type 'TemplateType.Audio'.

✔ Validating generated types
 🎉 Zod schemas generated!

✨  Done in 1.27s.
```

If I put all types in one file and run `ts-to-zod` no error is thrown – so I guess I broke the imports 🤷‍♂️
This should work, right?! Is my config not correct or why am I getting these weird errors?

**Versions**

"ts-to-zod": "^3.8.5"


Pull request:
fix: adding support for imported QualifiedName 
Fixes #242 

Patch:
diff --git a/src/core/generate.ts b/src/core/generate.ts
index 154bf15..3733bfe 100644
--- a/src/core/generate.ts
+++ b/src/core/generate.ts
@@ -10,8 +10,9 @@ import {
 import { getSimplifiedJsDocTags } from "../utils/getSimplifiedJsDocTags";
 import { resolveModules } from "../utils/resolveModules";
 import {
-  getExtractedTypeNames,
+  getReferencedTypeNames,
   isTypeNode,
+  TypeNameReference,
   TypeNode,
 } from "../utils/traverseTypes";
 
@@ -103,21 +104,33 @@ export function generate({
   const nodes: Array<TypeNode> = [];
 
   // declare a map to store the interface name and its corresponding zod schema
-  const typeNameMapping = new Map<string, TypeNode>();
+  const typeNameMapping = new Map<string, TypeNode | ts.ImportDeclaration>();
 
-  // handling existing import statements
-  const importNamesAvailable = new Set<string>();
-  const importNodes: ts.ImportDeclaration[] = [];
-  const importNamesUsed: string[] = [];
+  /**
+   * Following const are keeping track of all the things import-related
+   */
+  // All import nodes in the source file
+  const zodImportNodes: ts.ImportDeclaration[] = [];
+
+  // Keep track of all the external import names available in the source file
+  const externalImportNamesAvailable = new Set<string>();
+
+  // Keep track of all the imports that have an entry in the config file
   const importedZodNamesAvailable = new Map<string, string>();
 
-  const typesNeedToBeExtracted = new Set<string>();
+  // Keep track of all referenced types in the source file
+  const candidateTypesToBeExtracted = new Set<TypeNameReference>();
 
   const typeNameMapBuilder = (node: ts.Node) => {
     if (isTypeNode(node)) {
       typeNameMapping.set(node.name.text, node);
+      return;
     }
+
     if (ts.isImportDeclaration(node) && node.importClause) {
+      const identifiers = getImportIdentifiers(node);
+      identifiers.forEach((i) => typeNameMapping.set(i, node));
+
       // Check if we're importing from a mapped file
       const eligibleMapping = inputOutputMappings.find(
         (io: InputOutputMapping) =>
@@ -136,16 +149,15 @@ export function generate({
           importedZodNamesAvailable.set(i, schemaMethod(i))
         );
 
-        const importNode = createImportNode(
+        const zodImportNode = createImportNode(
           identifiers.map(schemaMethod),
           eligibleMapping.output
         );
-        importNodes.push(importNode); // We assume all identifiers will be used so pushing the whole node
+        zodImportNodes.push(zodImportNode);
       }
       // Not a Zod import, handling it as 3rd party import later on
       else {
-        const identifiers = getImportIdentifiers(node);
-        identifiers.forEach((i) => importNamesAvailable.add(i));
+        identifiers.forEach((i) => externalImportNamesAvailable.add(i));
       }
     }
   };
@@ -162,34 +174,66 @@ export function generate({
       if (!jsDocTagFilter(tags)) return;
       if (!nameFilter(node.name.text)) return;
 
-      const typeNames = getExtractedTypeNames(node, sourceFile);
-      typeNames.forEach((typeName) => {
-        typesNeedToBeExtracted.add(typeName);
+      const typeNames = getReferencedTypeNames(node, sourceFile);
+      typeNames.forEach((typeRef) => {
+        candidateTypesToBeExtracted.add(typeRef);
       });
     }
   };
   ts.forEachChild(sourceFile, visitor);
 
+  // All external import names actually used in the source file
+  const importNamesUsed: string[] = [];
+
+  // All zod imports actually used in the source file
   const importedZodSchemas = new Set<string>();
 
-  typesNeedToBeExtracted.forEach((typeName) => {
-    const node = typeNameMapping.get(typeName);
+  // All original import to keep in the target
+  const importsToKeep = new Map<ts.ImportDeclaration, string[]>();
+
+  /**
+   * We browse all the extracted type references from the source file
+   * To check if they reference a node from the file or if they are imported
+   */
+  candidateTypesToBeExtracted.forEach((typeRef) => {
+    const node = typeNameMapping.get(typeRef.name);
+
     if (node) {
-      nodes.push(node);
-      return;
+      // If we have a reference in the file, we add it to the nodes, no import needed
+      if (isTypeNode(node)) {
+        nodes.push(node);
+        return;
+      }
+
+      // If the reference is part of a qualified name, we need to import it from the same file
+      if (typeRef.partOfQualifiedName) {
+        const identifiers = importsToKeep.get(node);
+        if (identifiers) {
+          identifiers.push(typeRef.name);
+        } else {
+          importsToKeep.set(node, [typeRef.name]);
+        }
+        return;
+      }
     }
-    if (importNamesAvailable.has(typeName)) {
-      importNamesUsed.push(typeName);
+
+    // If the reference is coming from an external import, we'll need to generate a specific statement
+    // and keep the external import
+    if (externalImportNamesAvailable.has(typeRef.name)) {
+      importNamesUsed.push(typeRef.name);
       return;
     }
-    if (importedZodNamesAvailable.has(typeName)) {
-      importedZodSchemas.add(importedZodNamesAvailable.get(typeName) as string);
+
+    // If the reference is coming from a mapped import, we'll import the corresponding zod schema
+    if (importedZodNamesAvailable.has(typeRef.name)) {
+      importedZodSchemas.add(
+        importedZodNamesAvailable.get(typeRef.name) as string
+      );
       return;
     }
   });
 
   // Generate zod schemas for type nodes
-
   const getDependencyName = (identifierName: string) => {
     if (importedZodNamesAvailable.has(identifierName)) {
       return importedZodNamesAvailable.get(identifierName) as string;
@@ -237,7 +281,9 @@ export function generate({
     string,
     { typeName: string; value: ts.VariableStatement }
   >();
-  const typeImports: Set<string> = new Set();
+
+  // Keep track of types which need to be imported from the source file
+  const sourceTypeImports: Set<string> = new Set();
 
   // Zod schemas with direct or indirect dependencies that are not in `zodSchemas`, won't be generated
   const zodSchemasWithMissingDependencies = new Set<string>();
@@ -266,14 +312,14 @@ export function generate({
           if (notGeneratedDependencies.length === 0) {
             done = false;
             if (isCircular) {
-              typeImports.add(typeName);
+              sourceTypeImports.add(typeName);
               statements.set(varName, {
                 value: transformRecursiveSchema("z", statement, typeName),
                 typeName,
               });
             } else {
               if (requiresImport) {
-                typeImports.add(typeName);
+                sourceTypeImports.add(typeName);
               }
               statements.set(varName, { value: statement, typeName });
             }
@@ -300,7 +346,7 @@ export function generate({
         !zodSchemasWithMissingDependencies.has(varName)
     )
     .forEach(({ varName, statement, typeName }) => {
-      typeImports.add(typeName);
+      sourceTypeImports.add(typeName);
       statements.set(varName, {
         value: transformRecursiveSchema("z", statement, typeName),
         typeName,
@@ -332,21 +378,39 @@ ${Array.from(zodSchemasWithMissingDependencies).join("\n")}`
 
   const transformedSourceText = printerWithComments.printFile(sourceFile);
 
-  const typeImportsValues = Array.from(typeImports.values());
+  const zodImportToOutput = zodImportNodes.filter((node) => {
+    const nodeIdentifiers = getImportIdentifiers(node);
+    return nodeIdentifiers.some((i) => importedZodSchemas.has(i));
+  });
+
+  const originalImportsToOutput = Array.from(importsToKeep.keys()).map((node) =>
+    createImportNode(
+      importsToKeep.get(node)!,
+      (node.moduleSpecifier as ts.StringLiteral).text
+    )
+  );
+
+  const sourceTypeImportsValues = Array.from(sourceTypeImports.values());
   const getZodSchemasFile = (
     typesImportPath: string
   ) => `// Generated by ts-to-zod
 import { z } from "zod";
 ${
-  typeImportsValues.length
-    ? `import { ${typeImportsValues.join(", ")} } from "${typesImportPath}";\n`
+  sourceTypeImportsValues.length
+    ? `import { ${sourceTypeImportsValues.join(
+        ", "
+      )} } from "${typesImportPath}";\n`
     : ""
 }
 ${
-  importNodes.length
-    ? importNodes.map((node) => print(node)).join("\n") + "\n\n"
+  zodImportToOutput.length
+    ? zodImportToOutput.map((node) => print(node)).join("\n") + "\n\n"
     : ""
-}${Array.from(statements.values())
+}${
+    originalImportsToOutput.length
+      ? originalImportsToOutput.map((node) => print(node)).join("\n") + "\n\n"
+      : ""
+  }${Array.from(statements.values())
     .map((statement) => print(statement.value))
     .join("\n\n")}
 `;
@@ -448,7 +512,7 @@ ${Array.from(statements.values())
     /**
      * `true` if zodSchemaFile have some resolvable circular dependencies
      */
-    hasCircularDependencies: typeImportsValues.length > 0,
+    hasCircularDependencies: sourceTypeImportsValues.length > 0,
   };
 }
 
diff --git a/src/utils/traverseTypes.ts b/src/utils/traverseTypes.ts
index 6caacee..74ff4a5 100644
--- a/src/utils/traverseTypes.ts
+++ b/src/utils/traverseTypes.ts
@@ -15,6 +15,11 @@ export type TypeNode =
   | ts.TypeAliasDeclaration
   | ts.EnumDeclaration;
 
+export type TypeNameReference = {
+  name: string;
+  partOfQualifiedName: boolean;
+};
+
 export function isTypeNode(node: ts.Node): node is TypeNode {
   return (
     ts.isInterfaceDeclaration(node) ||
@@ -23,14 +28,14 @@ export function isTypeNode(node: ts.Node): node is TypeNode {
   );
 }
 
-export function getExtractedTypeNames(
+export function getReferencedTypeNames(
   node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration,
   sourceFile: ts.SourceFile
-): string[] {
-  const referenceTypeNames = new Set<string>();
+): TypeNameReference[] {
+  const referenceTypeNames = new Set<TypeNameReference>();
 
   // Adding the node name
-  referenceTypeNames.add(node.name.text);
+  referenceTypeNames.add({ name: node.name.text, partOfQualifiedName: false });
 
   const visitorExtract = (child: ts.Node) => {
     if (!ts.isPropertySignature(child)) {
@@ -65,12 +70,19 @@ export function getExtractedTypeNames(
   };
 
   const handleTypeReferenceNode = (typeRefNode: ts.TypeReferenceNode) => {
+    if (ts.isQualifiedName(typeRefNode.typeName)) {
+      const typeName = typeRefNode.typeName.left.getText(sourceFile);
+      referenceTypeNames.add({ name: typeName, partOfQualifiedName: true });
+      return;
+    }
+
     const typeName = typeRefNode.typeName.getText(sourceFile);
     if (typeScriptHelper.indexOf(typeName) > -1 && typeRefNode.typeArguments) {
       typeRefNode.typeArguments.forEach((t) => handleTypeNode(t));
-    } else {
-      referenceTypeNames.add(typeName);
+      return;
     }
+
+    referenceTypeNames.add({ name: typeName, partOfQualifiedName: false });
   };
 
   if (ts.isInterfaceDeclaration(node)) {
@@ -87,7 +99,10 @@ export function getExtractedTypeNames(
           }
 
           if (typeScriptHelper.indexOf(typeName) === -1) {
-            referenceTypeNames.add(typeName);
+            referenceTypeNames.add({
+              name: typeName,
+              partOfQualifiedName: false,
+            });
           }
         });
       });


Test patch:
diff --git a/src/core/generate.test.ts b/src/core/generate.test.ts
index db1367e..0c6a811 100644
--- a/src/core/generate.test.ts
+++ b/src/core/generate.test.ts
@@ -929,6 +929,38 @@ describe("generate", () => {
       `);
       });
     });
+
+    describe("reference to an enum value via external import Qualified Name X.Y", () => {
+      const input = "./person";
+
+      const sourceText = `
+        import { PersonEnum } from "${input}"
+        
+        export interface Hero {
+          id: number
+          hero: PersonEnum.Hero
+        }
+        `;
+
+      const { getZodSchemasFile } = generate({
+        sourceText,
+      });
+
+      it("should generate the zod schemas with right import", () => {
+        expect(getZodSchemasFile(input)).toMatchInlineSnapshot(`
+          "// Generated by ts-to-zod
+          import { z } from "zod";
+          
+          import { PersonEnum } from "${input}";
+          
+          export const heroSchema = z.object({
+              id: z.number(),
+              hero: z.literal(PersonEnum.Hero)
+          });
+          "
+        `);
+      });
+    });
   });
 
   describe("with input/output mappings to manage imports", () => {
@@ -1161,5 +1193,84 @@ describe("generate", () => {
       `);
       });
     });
+
+    describe("reference to an enum value via import Qualified Name X.Y referenced in mapping", () => {
+      const input = "./person";
+      const output = "./person.zod";
+      const inputOutputMappings: InputOutputMapping[] = [{ input, output }];
+
+      const sourceText = `
+        import { PersonEnum } from "${input}"
+        
+        export interface Hero {
+          id: number
+          hero: PersonEnum.Hero
+        }
+        `;
+
+      const { getZodSchemasFile } = generate({
+        sourceText,
+        inputOutputMappings,
+      });
+
+      it("should generate the zod schemas with right import", () => {
+        expect(getZodSchemasFile(input)).toMatchInlineSnapshot(`
+          "// Generated by ts-to-zod
+          import { z } from "zod";
+          
+          import { PersonEnum } from "${input}";
+          
+          export const heroSchema = z.object({
+              id: z.number(),
+              hero: z.literal(PersonEnum.Hero)
+          });
+          "
+        `);
+      });
+    });
+
+    describe("with useless imports", () => {
+      const input = "./hero";
+      const output = "./hero.zod";
+
+      const input2 = "./superhero";
+      const output2 = "./superhero.zod";
+      const inputOutputMappings: InputOutputMapping[] = [
+        { input, output },
+        { input: input2, output: output2 },
+      ];
+
+      const sourceText = `
+      import { Hero, Villain } from "${input}"
+      import { SuperHero } from "${input2}"
+    
+      export interface Person {
+        id: number
+        hero: Hero
+        villain: Villain
+      }
+      `;
+
+      const { getZodSchemasFile } = generate({
+        sourceText,
+        inputOutputMappings,
+      });
+
+      it("should generate the zod schemas with right import", () => {
+        expect(getZodSchemasFile(input)).toMatchInlineSnapshot(`
+        "// Generated by ts-to-zod
+        import { z } from "zod";
+        
+        import { heroSchema, villainSchema } from "${output}";
+        
+        export const personSchema = z.object({
+            id: z.number(),
+            hero: heroSchema,
+            villain: villainSchema
+        });
+        "
+      `);
+      });
+    });
   });
 });
diff --git a/src/utils/traverseTypes.test.ts b/src/utils/traverseTypes.test.ts
index 7b0dca8..4048964 100644
--- a/src/utils/traverseTypes.test.ts
+++ b/src/utils/traverseTypes.test.ts
@@ -1,5 +1,5 @@
 import ts from "typescript";
-import { getExtractedTypeNames } from "./traverseTypes";
+import { getReferencedTypeNames } from "./traverseTypes";
 import { findNode } from "./findNode";
 
 describe("traverseTypes", () => {
@@ -7,19 +7,19 @@ describe("traverseTypes", () => {
     it("should find only itself", () => {
       const testCases = [
         `
-        export type Superhero = {
+        export type SuperHero = {
             id: number, 
             name: string
           };
         `,
         `
-        export interface Superhero = {
+        export interface SuperHero = {
             id: number, 
             name: string
           };
         `,
         `
-        export enum Superhero = {
+        export enum SuperHero = {
             Superman = "superman",
             ClarkKent = "clark_kent",
           };
@@ -28,105 +28,135 @@ describe("traverseTypes", () => {
 
       testCases.forEach((source: string) => {
         const result = extractNames(source);
-        expect(result).toEqual(["Superhero"]);
+        expect(result).toEqual([
+          { name: "SuperHero", partOfQualifiedName: false },
+        ]);
       });
     });
 
     it("should extract type referenced in property", () => {
       const source = `
-        export interface Superhero {
+        export interface SuperHero {
             id: number,
             person: Person,
         }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced in extend clause", () => {
       const source = `
-          export interface Superhero extends Person {
+          export interface SuperHero extends Person {
               id: number,
           }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced in multiple extend clauses", () => {
       const source = `
-            export interface Superhero extends Person, Person2 {
+            export interface SuperHero extends Person, Person2 {
                 id: number,
             }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person", "Person2"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Person2", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced in extend clause with Pick helper", () => {
       const source = `
-            export interface Superhero extends Pick<Person, "name"> {
+            export interface SuperHero extends Pick<Person, "name"> {
                 id: number,
             }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced in extend clause with Omit helper", () => {
       const source = `
-            export interface Superhero extends Omit<Person, "name"> {
+            export interface SuperHero extends Omit<Person, "name"> {
                 id: number,
             }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced in extend clause with Partial helper", () => {
       const source = `
-            export interface Superhero extends Partial<Person, "name"> {
+            export interface SuperHero extends Partial<Person, "name"> {
                 id: number,
             }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced in extend clause with Record helper", () => {
       const source = `
-            export interface Superhero extends Record<string, Person2> {
+            export interface SuperHero extends Record<string, Person2> {
                 id: number,
             }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person2"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person2", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced in property as array", () => {
       const source = `
-            export interface Superhero {
+            export interface SuperHero {
                 id: number,
                 sidekicks: Person[],
             }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type referenced as array in union property", () => {
       const source = `
-            export interface Superhero {
+            export interface SuperHero {
                 sidekicks: Person[] | null,
             }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract nested type reference", () => {
       const source = `
-          export interface Superhero {
+          export interface SuperHero {
               id: number,
               person: {
                 type: Person,
@@ -134,7 +164,10 @@ describe("traverseTypes", () => {
           }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Superhero", "Person"]);
+      expect(result).toEqual([
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract union type reference", () => {
@@ -145,7 +178,11 @@ describe("traverseTypes", () => {
         }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract intersection type reference", () => {
@@ -156,7 +193,11 @@ describe("traverseTypes", () => {
         }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract intersection type reference with type literal", () => {
@@ -167,7 +208,11 @@ describe("traverseTypes", () => {
         }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract types between parenthesis", () => {
@@ -178,7 +223,10 @@ describe("traverseTypes", () => {
         }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract union types between parenthesis", () => {
@@ -189,7 +237,11 @@ describe("traverseTypes", () => {
         }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract types from Tuple", () => {
@@ -200,7 +252,11 @@ describe("traverseTypes", () => {
         }`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias", () => {
@@ -208,7 +264,10 @@ describe("traverseTypes", () => {
         export type Person = SuperHero `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with union", () => {
@@ -216,7 +275,11 @@ describe("traverseTypes", () => {
         export type Person = Villain | SuperHero `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain", "SuperHero"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with array", () => {
@@ -224,7 +287,10 @@ describe("traverseTypes", () => {
         export type Person = Villain[] `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with Array helper", () => {
@@ -232,7 +298,10 @@ describe("traverseTypes", () => {
         export type Person = Array<Villain> `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with Promise helper", () => {
@@ -240,7 +309,10 @@ describe("traverseTypes", () => {
         export type Person = Promise<Villain> `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with Required helper", () => {
@@ -248,7 +320,10 @@ describe("traverseTypes", () => {
         export type Person = Required<Villain> `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with Partial helper", () => {
@@ -256,7 +331,10 @@ describe("traverseTypes", () => {
         export type Person = Partial<Villain> `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with Omit helper", () => {
@@ -264,7 +342,10 @@ describe("traverseTypes", () => {
         export type Person = Omit<Villain> `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with Pick helper", () => {
@@ -272,7 +353,10 @@ describe("traverseTypes", () => {
         export type Person = Pick<Villain> `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with Record helper", () => {
@@ -280,7 +364,10 @@ describe("traverseTypes", () => {
         export type Person = Record<string, Villain> `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with parenthesis", () => {
@@ -288,7 +375,10 @@ describe("traverseTypes", () => {
         export type Person = (Villain) `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with parenthesis", () => {
@@ -296,7 +386,11 @@ describe("traverseTypes", () => {
         export type Person = (Villain | Hero)[]`;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain", "Hero"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+        { name: "Hero", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with object literal", () => {
@@ -304,7 +398,10 @@ describe("traverseTypes", () => {
         export type Person = { hero: SuperHero } `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "SuperHero"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract type from type alias with union & object literal", () => {
@@ -312,7 +409,11 @@ describe("traverseTypes", () => {
         export type Person = Villain | { hero: SuperHero } `;
 
       const result = extractNames(source);
-      expect(result).toEqual(["Person", "Villain", "SuperHero"]);
+      expect(result).toEqual([
+        { name: "Person", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+      ]);
     });
 
     it("should extract types from a very weird type definition (testing edge cases)", () => {
@@ -324,18 +425,31 @@ describe("traverseTypes", () => {
 
       const result = extractNames(source);
       expect(result).toEqual([
-        "Person",
-        "SuperHero",
-        "Person2",
-        "SuperHero2",
-        "Villain2",
-        "SuperHero3",
-        "Villain3",
-        "A",
-        "B",
-        "C",
-        "D",
-        "Villain",
+        { name: "Person", partOfQualifiedName: false },
+        { name: "SuperHero", partOfQualifiedName: false },
+        { name: "Person2", partOfQualifiedName: false },
+        { name: "SuperHero2", partOfQualifiedName: false },
+        { name: "Villain2", partOfQualifiedName: false },
+        { name: "SuperHero3", partOfQualifiedName: false },
+        { name: "Villain3", partOfQualifiedName: false },
+        { name: "A", partOfQualifiedName: false },
+        { name: "B", partOfQualifiedName: false },
+        { name: "C", partOfQualifiedName: false },
+        { name: "D", partOfQualifiedName: false },
+        { name: "Villain", partOfQualifiedName: false },
+      ]);
+    });
+
+    it("should extract type when part of QualifiedName", () => {
+      const source = `
+        export type Hero = {
+          qualified: Person.SuperHero
+        }`;
+
+      const result = extractNames(source);
+      expect(result).toEqual([
+        { name: "Hero", partOfQualifiedName: false },
+        { name: "Person", partOfQualifiedName: true },
       ]);
     });
   });
@@ -363,5 +477,5 @@ function extractNames(sourceText: string) {
     throw new Error("No `type` or `interface` found!");
   }
 
-  return getExtractedTypeNames(declaration, sourceFile);
+  return getReferencedTypeNames(declaration, sourceFile);
 }