ts-morph#StructureKind TypeScript Examples

The following examples show how to use ts-morph#StructureKind. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ConfigGenerator.ts    From gengen with MIT License 6 votes vote down vote up
public getEndpointsCodeStructure(controllers: Record<string, Record<string, string>>): StatementStructures[] {
        return [
            {
                kind: StructureKind.Class,
                isExported: true,
                name: configOptions.className,
                properties: this.getProperties(controllers)
            }
        ];
    }
Example #2
Source File: update-object-property.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
export function updateObjectProperty(args: {
  expression: ObjectLiteralExpression;
  name: string;
  value: string | number | boolean | undefined;
}) {
  const { expression, name, value } = args;
  let propertyAssignment = expression.getProperty(name) as
    | PropertyAssignment
    | undefined;

  if (value === undefined) {
    if (propertyAssignment) {
      propertyAssignment.remove();
    }
    return;
  }

  if (!propertyAssignment) {
    propertyAssignment = expression.addProperty({
      name,
      kind: StructureKind.PropertyAssignment,
      initializer: 'undefined',
    }) as PropertyAssignment;
  }

  propertyAssignment.setInitializer(JSON.stringify(value));
}
Example #3
Source File: property-structure.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
/**
 * Get property structure (field) for class.
 */
export function propertyStructure(args: {
  propertyType: string[];
  isList: boolean;
  name: string;
  isNullable?: boolean;
  hasQuestionToken?: boolean;
  hasExclamationToken?: boolean;
}): PropertyDeclarationStructure {
  const {
    isNullable,
    propertyType,
    name,
    isList,
    hasQuestionToken,
    hasExclamationToken,
  } = args;
  const type = propertyType.map(type => (isList ? `Array<${type}>` : type)).join(' | ');

  return {
    kind: StructureKind.Property,
    name,
    type,
    hasQuestionToken: hasQuestionToken ?? isNullable,
    hasExclamationToken: hasExclamationToken ?? !isNullable,
    decorators: [],
    leadingTrivia: '\n',
  };
}
Example #4
Source File: import-declaration-map.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
*toStatements(): Iterable<ImportDeclarationStructure> {
    const iterator = this.values();
    let result = iterator.next();
    while (result.value) {
      yield {
        ...result.value,
        kind: StructureKind.ImportDeclaration,
      };
      result = iterator.next();
    }
  }
Example #5
Source File: register-enum.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
export function registerEnum(enumType: SchemaEnum, args: EventArguments) {
  const { getSourceFile, enums } = args;
  const dataModelEnum = enums[enumType.name];
  const sourceFile = getSourceFile({
    name: enumType.name,
    type: 'enum',
  });

  const importDeclarations = new ImportDeclarationMap();

  importDeclarations.set('registerEnumType', {
    namedImports: [{ name: 'registerEnumType' }],
    moduleSpecifier: '@nestjs/graphql',
  });

  const enumStructure: EnumDeclarationStructure = {
    kind: StructureKind.Enum,
    isExported: true,
    name: enumType.name,
    members: enumType.values.map(v => ({
      name: v,
      initializer: JSON.stringify(v),
    })),
  };

  sourceFile.set({
    statements: [
      ...importDeclarations.toStatements(),
      enumStructure,
      '\n',
      `registerEnumType(${enumType.name}, { name: '${
        enumType.name
      }', description: ${JSON.stringify(dataModelEnum?.documentation)} })`,
    ],
  });
}
Example #6
Source File: re-export.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
function getNamespaceExportDeclaration(
  directory: Directory,
  sourceDirectory: Directory,
): ExportDeclarationStructure {
  return {
    kind: StructureKind.ExportDeclaration,
    moduleSpecifier: directory.getRelativePathAsModuleSpecifierTo(sourceDirectory),
  };
}
Example #7
Source File: re-export.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
function getExportDeclaration(
  directory: Directory,
  sourceFile: SourceFile,
): ExportDeclarationStructure {
  return {
    kind: StructureKind.ExportDeclaration,
    namedExports: sourceFile.getExportSymbols().map(s => ({ name: s.getName() })),
    moduleSpecifier: directory.getRelativePathAsModuleSpecifierTo(sourceFile),
  };
}
Example #8
Source File: model-output-type.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
function getExportDeclaration(name: string, statements: StatementStructures[]) {
  return statements.find(structure => {
    return (
      structure.kind === StructureKind.ExportDeclaration &&
      (structure.namedExports as ExportSpecifierStructure[]).some(
        o => (o.alias || o.name) === name,
      )
    );
  });
}
Example #9
Source File: configuration.ts    From cli with MIT License 6 votes vote down vote up
// 确保类拥有属性
export function ensureLifeCycleClassProperty(
  source: SourceFile,
  className: string,
  propKey: string,
  decorators: string[] = [],
  propType = 'unknown',
  apply = true
) {
  const existClassProps = getExistClassProps(source, className);

  if (existClassProps.includes(propKey)) {
    return;
  }

  const targetClass = getClassDecByName(source, 'ContainerLifeCycle');

  const applyDecorators: Array<DecoratorStructure> = decorators.map(
    decorator => ({
      name: decorator,
      kind: StructureKind.Decorator,
      arguments: [],
    })
  );

  targetClass.addProperty({
    name: propKey,
    decorators: applyDecorators,
    type: propType,
  });

  // FIXME: 生成到所有方法声明前

  apply && source.saveSync();
}
Example #10
Source File: generator.ts    From wsdl-tsclient with MIT License 6 votes vote down vote up
function createProperty(
    name: string,
    type: string,
    doc: string,
    isArray: boolean,
    optional = true
): PropertySignatureStructure {
    return {
        kind: StructureKind.PropertySignature,
        name: sanitizePropName(name),
        docs: [doc],
        hasQuestionToken: true,
        type: isArray ? `Array<${type}>` : type,
    };
}
Example #11
Source File: ConfigGenerator.ts    From gengen with MIT License 6 votes vote down vote up
public getConfigCodeStructure(): StatementStructures[] {
        return [
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: `./${configOptions.className.toLowerCase()}`,
                namedImports: [{ name: configOptions.className }]
            },
            {
                kind: StructureKind.ExportAssignment,
                expression: 'new Set<string>([])',
                isExportEquals: false
            }
        ];
    }
Example #12
Source File: AngularServicesGenerator.ts    From gengen with MIT License 6 votes vote down vote up
protected getServices(services: IServiceModel[]): ClassDeclarationStructure[] {
        return services.map(
            (service): ClassDeclarationStructure => ({
                kind: StructureKind.Class,
                isExported: true,
                name: `${service.name}Service`,
                extends: service.methods.some((x) => x.kind === MethodKind.Download) ? DOWNLOAD_SERVICE : BASE_SERVICE,
                decorators: [
                    {
                        kind: StructureKind.Decorator,
                        name: 'Injectable',
                        arguments: Writers.object({ providedIn: "'root'" })
                    }
                ],
                ctors: [this.getConstructorStatement(service)],
                methods: this.methodGenerator.getMethodsCodeStructures(service.methods)
            })
        );
    }
Example #13
Source File: ModelsGenerator.ts    From gengen with MIT License 6 votes vote down vote up
private getImports(): ImportDeclarationStructure[] {
        return [
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './Guid',
                namedImports: [{ name: 'Guid' }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './date-converters',
                namedImports: [{ name: 'toDateIn' }, { name: 'toDateOut' }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './types',
                namespaceImport: TYPES_NAMESPACE,
                isTypeOnly: true
            }
        ];
    }
Example #14
Source File: ModelsGenerator.ts    From gengen with MIT License 6 votes vote down vote up
private getIdentities(identities: IIdentityModel[], interfaces: IInterfaceModel[]): StatementStructures[] {
        return identities.map(
            (z): ClassDeclarationStructure => ({
                kind: StructureKind.Class,
                isExported: true,
                name: z.name,
                ctors: [
                    {
                        parameters: [
                            {
                                name: z.property.name,
                                hasQuestionToken: true,
                                type: TypeSerializer.fromTypeName(`${z.property.type} | ${z.property.dtoType}`).toString()
                            }
                        ] as OptionalKind<ParameterDeclarationStructure>[],
                        statements: `this.${z.property.name} = new ${z.property.type}(${z.property.name});`
                    }
                ] as OptionalKind<ConstructorDeclarationStructure>[],
                properties: [{ scope: Scope.Public, name: z.property.name, type: z.property.type }, this.getGuardProperty(z.name)],
                methods: [
                    {
                        scope: Scope.Public,
                        isStatic: true,
                        name: TO_DTO_METHOD,
                        parameters: [{ name: z.property.name, type: z.property.type }],
                        returnType: interfaces.find(
                            (i) =>
                                i.properties.length === 1 &&
                                i.properties.every((x) => x.dtoType === z.property.dtoType && x.name === z.property.name)
                        )?.name,
                        statements: `return { ${z.property.name}: ${z.property.name}.toString() };`
                    }
                ]
            })
        );
    }
Example #15
Source File: ModelsGenerator.ts    From gengen with MIT License 6 votes vote down vote up
private getObjects(objects: IObjectModel[]): ClassDeclarationStructure[] {
        return objects.map((z) => ({
            kind: StructureKind.Class,
            isExported: true,
            name: z.name,
            properties: this.getObjectProperties(z),
            methods: [
                {
                    scope: Scope.Public,
                    isStatic: true,
                    name: TO_DTO_METHOD,
                    parameters: [{ name: 'model', type: `Partial<${z.name}>` }],
                    returnType: z.dtoType,
                    statements: (x) => {
                        x.writeLine('return {');
                        z.properties.forEach((p) =>
                            x.withIndentationLevel(3, () => x.writeLine(`${p.name}: ${this.getToDtoPropertyInitializer(p)},`))
                        );
                        x.writeLine('};');
                    }
                },
                {
                    scope: Scope.Public,
                    isStatic: true,
                    name: FROM_DTO_METHOD,
                    parameters: [{ name: 'dto', type: z.dtoType }],
                    returnType: z.name,
                    statements: (x) => {
                        x.writeLine(`const model = new ${z.name}();`);
                        z.properties.forEach((p) =>
                            x.withIndentationLevel(2, () => x.writeLine(`model.${p.name} = ${this.getFromDtoPropertyInitializer(p)};`))
                        );
                        x.writeLine('return model;');
                    }
                }
            ]
        }));
    }
Example #16
Source File: ModelsGenerator.ts    From gengen with MIT License 6 votes vote down vote up
private getObjectProperties(objectModel: IObjectModel): PropertyDeclarationStructure[] {
        return [
            ...objectModel.properties.map(
                (objectProperty): PropertyDeclarationStructure => ({
                    kind: StructureKind.Property,
                    scope: Scope.Public,
                    name: objectProperty.name,
                    type: new TypeSerializer(objectProperty).toString(),
                    initializer: UNDEFINED_STRING
                })
            ),
            this.getGuardProperty(objectModel.name)
        ];
    }
Example #17
Source File: ModelsGenerator.ts    From gengen with MIT License 6 votes vote down vote up
private getGuardProperty(name: string): PropertyDeclarationStructure {
        return {
            kind: StructureKind.Property,
            scope: Scope.Private,
            name: `__${lowerFirst(name)}`,
            type: 'string',
            hasExclamationToken: true
        };
    }
Example #18
Source File: InterfacesGenerator.ts    From gengen with MIT License 5 votes vote down vote up
public getCodeStructure(interfaces: IInterfaceModel[]): InterfaceDeclarationStructure[] {
        return interfaces.map((z) => ({
            kind: StructureKind.Interface,
            name: z.name,
            isExported: true,
            properties: z.properties.map((x) => this.getInterfaceProperty(x))
        }));
    }
Example #19
Source File: ModelsGenerator.ts    From gengen with MIT License 5 votes vote down vote up
private getEnums(enums: IEnumModel[]): StatementStructures[] {
        return enums.map((z) => ({
            kind: StructureKind.Enum,
            isExported: true,
            name: z.name,
            members: z.items.map((x) => ({ name: x.key, value: x.value }))
        }));
    }
Example #20
Source File: AngularServicesGenerator.ts    From gengen with MIT License 5 votes vote down vote up
protected getImports(): ImportDeclarationStructure[] {
        return [
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: '@angular/common/http',
                namedImports: [{ name: HTTP_CLIENT }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: '@angular/core',
                namedImports: [{ name: 'Injectable' }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: 'rxjs',
                namedImports: [{ name: 'Observable' }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './Guid',
                namedImports: [{ name: 'Guid' }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './base-http.service',
                namedImports: this.settings.withRequestOptions
                    ? [{ name: BASE_SERVICE }, { name: HTTP_REQUEST_OPTIONS }]
                    : [{ name: BASE_SERVICE }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './download.service',
                namedImports: [{ name: DOWNLOAD_SERVICE }, { name: 'IDownloadResult' }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './utils',
                namedImports: [{ name: GET_BASE_PATH_FUNCTION_NAME }]
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './mappers',
                namespaceImport: MAPPERS_NAMESPACE
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: './types',
                namespaceImport: TYPES_NAMESPACE,
                isTypeOnly: true
            },
            {
                kind: StructureKind.ImportDeclaration,
                moduleSpecifier: `./${this.aliasResolver.getModelsModuleName()}`,
                namespaceImport: MODELS_NAMESPACE
            }
        ];
    }
Example #21
Source File: generator.ts    From wsdl-tsclient with MIT License 5 votes vote down vote up
function generateDefinitionFile(
    project: Project,
    definition: null | Definition,
    defDir: string,
    stack: string[],
    generated: Definition[],
    options: GeneratorOptions
): void {
    const defName = definition.name;
    const defFilePath = path.join(defDir, `${defName}.ts`);
    const defFile = project.createSourceFile(defFilePath, "", {
        overwrite: true,
    });

    generated.push(definition);

    const definitionImports: OptionalKind<ImportDeclarationStructure>[] = [];
    const definitionProperties: PropertySignatureStructure[] = [];
    for (const prop of definition.properties) {
        if (options.modelPropertyNaming) {
            switch (options.modelPropertyNaming) {
                case ModelPropertyNaming.camelCase:
                    prop.name = camelcase(prop.name);
                    break;
                case ModelPropertyNaming.PascalCase:
                    prop.name = camelcase(prop.name, { pascalCase: true });
                    break;
            }
        }
        if (prop.kind === "PRIMITIVE") {
            // e.g. string
            definitionProperties.push(createProperty(prop.name, prop.type, prop.description, prop.isArray));
        } else if (prop.kind === "REFERENCE") {
            // e.g. Items
            if (!generated.includes(prop.ref)) {
                // Wasn't generated yet
                generateDefinitionFile(project, prop.ref, defDir, [...stack, prop.ref.name], generated, options);
            }
            // If a property is of the same type as its parent type, don't add import
            if (prop.ref.name !== definition.name) {
                addSafeImport(definitionImports, `./${prop.ref.name}`, prop.ref.name);
            }
            definitionProperties.push(createProperty(prop.name, prop.ref.name, prop.sourceName, prop.isArray));
        }
    }

    defFile.addImportDeclarations(definitionImports);
    defFile.addStatements([
        {
            leadingTrivia: (writer) => writer.newLine(),
            isExported: true,
            name: defName,
            docs: [definition.docs.join("\n")],
            kind: StructureKind.Interface,
            properties: definitionProperties,
        },
    ]);
    Logger.log(`Writing Definition file: ${path.resolve(path.join(defDir, defName))}.ts`);
    defFile.saveSync();
}
Example #22
Source File: AngularServicesGenerator.ts    From gengen with MIT License 5 votes vote down vote up
protected getConstructorStatement(service: IServiceModel): ConstructorDeclarationStructure {
        const superStatement = `super(${GET_BASE_PATH_FUNCTION_NAME}('${this.aliasResolver.alias}', '${service.relativePath}'), ${HTTP_CLIENT_VARIABLE_NAME});`;
        return {
            kind: StructureKind.Constructor,
            parameters: [{ name: HTTP_CLIENT_VARIABLE_NAME, type: HTTP_CLIENT }],
            statements: superStatement
        };
    }
Example #23
Source File: model-output-type.ts    From prisma-nestjs-graphql with MIT License 4 votes vote down vote up
export function modelOutputType(outputType: OutputType, args: EventArguments) {
  const { getSourceFile, models, config, modelFields, fieldSettings, eventEmitter } =
    args;
  const model = models.get(outputType.name);
  ok(model, `Cannot find model by name ${outputType.name}`);

  const sourceFile = getSourceFile({
    name: outputType.name,
    type: 'model',
  });
  const sourceFileStructure = sourceFile.getStructure();
  const exportDeclaration = getExportDeclaration(
    model.name,
    sourceFileStructure.statements as StatementStructures[],
  );
  const importDeclarations = new ImportDeclarationMap();
  const classStructure: ClassDeclarationStructure = {
    kind: StructureKind.Class,
    isExported: true,
    name: outputType.name,
    decorators: [
      {
        name: 'ObjectType',
        arguments: [],
      },
    ],
    properties: [],
  };
  (sourceFileStructure.statements as StatementStructures[]).push(classStructure);
  ok(classStructure.decorators, 'classStructure.decorators is undefined');
  const decorator = classStructure.decorators.find(d => d.name === 'ObjectType');
  ok(decorator, 'ObjectType decorator not found');

  let modelSettings: ObjectSettings | undefined;
  // Get model settings from documentation
  if (model.documentation) {
    const objectTypeOptions: PlainObject = {};
    const { documentation, settings } = createObjectSettings({
      text: model.documentation,
      config,
    });
    if (documentation) {
      if (!classStructure.leadingTrivia) {
        classStructure.leadingTrivia = createComment(documentation);
      }
      objectTypeOptions.description = documentation;
    }
    decorator.arguments = settings.getObjectTypeArguments(objectTypeOptions);
    modelSettings = settings;
  }

  importDeclarations.add('Field', nestjsGraphql);
  importDeclarations.add('ObjectType', nestjsGraphql);

  for (const field of outputType.fields) {
    let fileType = 'model';
    const { location, isList, type, namespace } = field.outputType;

    let outputTypeName = String(type);
    if (namespace !== 'model') {
      fileType = 'output';
      outputTypeName = getOutputTypeName(outputTypeName);
    }
    const modelField = modelFields.get(model.name)?.get(field.name);
    const settings = fieldSettings.get(model.name)?.get(field.name);
    const fieldType = settings?.getFieldType({
      name: outputType.name,
      output: true,
    });
    const propertySettings = settings?.getPropertyType({
      name: outputType.name,
      output: true,
    });

    const propertyType = castArray(
      propertySettings?.name ||
        getPropertyType({
          location,
          type: outputTypeName,
        }),
    );

    // For model we keep only one type
    propertyType.splice(1, propertyType.length);

    if (field.isNullable && !isList) {
      propertyType.push('null');
    }

    let graphqlType: string;

    if (fieldType) {
      graphqlType = fieldType.name;
      importDeclarations.create({ ...fieldType });
    } else {
      const graphqlImport = getGraphqlImport({
        config,
        sourceFile,
        fileType,
        location,
        isId: modelField?.isId,
        noTypeId: config.noTypeId,
        typeName: outputTypeName,
        getSourceFile,
      });

      graphqlType = graphqlImport.name;

      if (graphqlImport.name !== outputType.name && graphqlImport.specifier) {
        importDeclarations.add(graphqlImport.name, graphqlImport.specifier);
      }
    }

    const property = propertyStructure({
      name: field.name,
      isNullable: field.isNullable,
      hasExclamationToken: true,
      hasQuestionToken: location === 'outputObjectTypes',
      propertyType,
      isList,
    });

    if (typeof property.leadingTrivia === 'string' && modelField?.documentation) {
      property.leadingTrivia += createComment(modelField.documentation, settings);
    }

    classStructure.properties?.push(property);

    if (propertySettings) {
      importDeclarations.create({ ...propertySettings });
    } else if (propertyType.includes('Decimal')) {
      importDeclarations.add('Decimal', '@prisma/client/runtime');
    }

    ok(property.decorators, 'property.decorators is undefined');

    if (settings?.shouldHideField({ name: outputType.name, output: true })) {
      importDeclarations.add('HideField', nestjsGraphql);
      property.decorators.push({ name: 'HideField', arguments: [] });
    } else {
      // Generate `@Field()` decorator
      property.decorators.push({
        name: 'Field',
        arguments: [
          isList ? `() => [${graphqlType}]` : `() => ${graphqlType}`,
          JSON5.stringify({
            ...settings?.fieldArguments(),
            nullable: Boolean(field.isNullable),
            defaultValue: ['number', 'string', 'boolean'].includes(
              typeof modelField?.default,
            )
              ? modelField?.default
              : undefined,
            description: modelField?.documentation,
          }),
        ],
      });

      for (const setting of settings || []) {
        if (shouldBeDecorated(setting) && (setting.match?.(field.name) ?? true)) {
          property.decorators.push({
            name: setting.name,
            arguments: setting.arguments as string[],
          });
          ok(setting.from, "Missed 'from' part in configuration or field setting");
          importDeclarations.create(setting);
        }
      }

      for (const decorate of config.decorate) {
        if (decorate.isMatchField(field.name) && decorate.isMatchType(outputTypeName)) {
          property.decorators.push({
            name: decorate.name,
            arguments: decorate.arguments?.map(x => pupa(x, { propertyType })),
          });
          importDeclarations.create(decorate);
        }
      }
    }

    eventEmitter.emitSync('ClassProperty', property, {
      location,
      isList,
      propertyType,
    });
  }

  // Generate class decorators from model settings
  for (const setting of modelSettings || []) {
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
    if (shouldBeDecorated(setting)) {
      classStructure.decorators.push({
        name: setting.name,
        arguments: setting.arguments as string[],
      });
      importDeclarations.create(setting);
    }
  }

  if (exportDeclaration) {
    sourceFile.set({
      statements: [exportDeclaration, '\n', classStructure],
    });
    const classDeclaration = sourceFile.getClassOrThrow(model.name);
    const commentedText = classDeclaration
      .getText()
      .split('\n')
      .map(x => `// ${x}`);
    classDeclaration.remove();
    sourceFile.addStatements(['\n', ...commentedText]);
  } else {
    sourceFile.set({
      statements: [...importDeclarations.toStatements(), classStructure],
    });
  }
}
Example #24
Source File: output-type.ts    From prisma-nestjs-graphql with MIT License 4 votes vote down vote up
export function outputType(outputType: OutputType, args: EventArguments) {
  const { getSourceFile, models, eventEmitter, fieldSettings, getModelName, config } =
    args;
  const importDeclarations = new ImportDeclarationMap();

  const fileType = 'output';
  const modelName = getModelName(outputType.name) || '';
  const model = models.get(modelName);
  const isAggregateOutput =
    model &&
    /(?:Count|Avg|Sum|Min|Max)AggregateOutputType$/.test(outputType.name) &&
    String(outputType.name).startsWith(model.name);
  const isCountOutput =
    model?.name && outputType.name === `${model.name}CountOutputType`;
  // Get rid of bogus suffixes
  outputType.name = getOutputTypeName(outputType.name);

  if (isAggregateOutput) {
    eventEmitter.emitSync('AggregateOutput', { ...args, outputType });
  }

  const sourceFile = getSourceFile({
    name: outputType.name,
    type: fileType,
  });

  const classStructure: ClassDeclarationStructure = {
    kind: StructureKind.Class,
    isExported: true,
    name: outputType.name,
    decorators: [
      {
        name: 'ObjectType',
        arguments: [],
      },
    ],
    properties: [],
  };

  importDeclarations.add('Field', nestjsGraphql);
  importDeclarations.add('ObjectType', nestjsGraphql);

  for (const field of outputType.fields) {
    const { location, isList, type } = field.outputType;
    const outputTypeName = getOutputTypeName(String(type));
    const settings = isCountOutput
      ? undefined
      : model && fieldSettings.get(model.name)?.get(field.name);
    const propertySettings = settings?.getPropertyType({
      name: outputType.name,
      output: true,
    });
    const isCustomsApplicable =
      outputTypeName === model?.fields.find(f => f.name === field.name)?.type;

    field.outputType.type = outputTypeName;

    const propertyType = castArray(
      propertySettings?.name ||
        getPropertyType({
          location,
          type: outputTypeName,
        }),
    );

    const property = propertyStructure({
      name: field.name,
      isNullable: field.isNullable,
      hasQuestionToken: isCountOutput ? true : undefined,
      propertyType,
      isList,
    });

    classStructure.properties?.push(property);

    if (propertySettings) {
      importDeclarations.create({ ...propertySettings });
    } else if (propertyType.includes('Decimal')) {
      importDeclarations.add('Decimal', '@prisma/client/runtime');
    }

    // Get graphql type
    let graphqlType: string;
    const shouldHideField =
      settings?.shouldHideField({
        name: outputType.name,
        output: true,
      }) ||
      config.decorate.some(
        d =>
          d.name === 'HideField' &&
          d.from === '@nestjs/graphql' &&
          d.isMatchField(field.name) &&
          d.isMatchType(outputTypeName),
      );

    const fieldType = settings?.getFieldType({
      name: outputType.name,
      output: true,
    });

    if (fieldType && isCustomsApplicable && !shouldHideField) {
      graphqlType = fieldType.name;
      importDeclarations.create({ ...fieldType });
    } else {
      const graphqlImport = getGraphqlImport({
        config,
        sourceFile,
        fileType,
        location,
        isId: false,
        typeName: outputTypeName,
        getSourceFile,
      });

      graphqlType = graphqlImport.name;
      let referenceName = propertyType[0];
      if (location === 'enumTypes') {
        referenceName = last(referenceName.split(' ')) as string;
      }

      if (
        graphqlImport.specifier &&
        !importDeclarations.has(graphqlImport.name) &&
        ((graphqlImport.name !== outputType.name && !shouldHideField) ||
          (shouldHideField && referenceName === graphqlImport.name))
      ) {
        importDeclarations.set(graphqlImport.name, {
          namedImports: [{ name: graphqlImport.name }],
          moduleSpecifier: graphqlImport.specifier,
        });
      }
    }

    ok(property.decorators, 'property.decorators is undefined');

    if (shouldHideField) {
      importDeclarations.add('HideField', nestjsGraphql);
      property.decorators.push({ name: 'HideField', arguments: [] });
    } else {
      // Generate `@Field()` decorator
      property.decorators.push({
        name: 'Field',
        arguments: [
          isList ? `() => [${graphqlType}]` : `() => ${graphqlType}`,
          JSON5.stringify({
            ...settings?.fieldArguments(),
            nullable: Boolean(field.isNullable),
          }),
        ],
      });

      if (isCustomsApplicable) {
        for (const options of settings || []) {
          if (
            (options.kind === 'Decorator' &&
              options.output &&
              options.match?.(field.name)) ??
            true
          ) {
            property.decorators.push({
              name: options.name,
              arguments: options.arguments as string[],
            });
            ok(options.from, "Missed 'from' part in configuration or field setting");
            importDeclarations.create(options);
          }
        }
      }
    }

    eventEmitter.emitSync('ClassProperty', property, {
      location,
      isList,
      propertyType,
    });
  }

  sourceFile.set({
    statements: [...importDeclarations.toStatements(), classStructure],
  });
}
Example #25
Source File: input-type.ts    From prisma-nestjs-graphql with MIT License 4 votes vote down vote up
export function inputType(
  args: EventArguments & {
    inputType: InputType;
    fileType: string;
    classDecoratorName: string;
  },
) {
  const {
    classDecoratorName,
    classTransformerTypeModels,
    config,
    eventEmitter,
    fieldSettings,
    fileType,
    getModelName,
    getSourceFile,
    inputType,
    models,
    removeTypes,
    typeNames,
  } = args;

  typeNames.add(inputType.name);

  const importDeclarations = new ImportDeclarationMap();
  const sourceFile = getSourceFile({
    name: inputType.name,
    type: fileType,
  });
  const classStructure: ClassDeclarationStructure = {
    kind: StructureKind.Class,
    isExported: true,
    name: inputType.name,
    decorators: [
      {
        name: classDecoratorName,
        arguments: [],
      },
    ],
    properties: [],
  };
  const modelName = getModelName(inputType.name) || '';
  const model = models.get(modelName);
  const modelFieldSettings = model && fieldSettings.get(model.name);
  const moduleSpecifier = '@nestjs/graphql';

  // console.log('sourceFile.getBaseName()', sourceFile.getBaseName());

  importDeclarations
    .set('Field', {
      namedImports: [{ name: 'Field' }],
      moduleSpecifier,
    })
    .set(classDecoratorName, {
      namedImports: [{ name: classDecoratorName }],
      moduleSpecifier,
    });

  const useInputType = config.useInputType.find(x =>
    inputType.name.includes(x.typeName),
  );

  for (const field of inputType.fields) {
    field.inputTypes = field.inputTypes.filter(t => !removeTypes.has(String(t.type)));
    eventEmitter.emitSync('BeforeGenerateField', field, args);

    const { inputTypes, isRequired, name } = field;

    if (inputTypes.length === 0) {
      // No types
      continue;
    }

    const usePattern = useInputType?.ALL || useInputType?.[name];
    const graphqlInputType = getGraphqlInputType(inputTypes, usePattern);
    const { isList, location, type } = graphqlInputType;
    const typeName = String(type);
    const settings = modelFieldSettings?.get(name);
    const propertySettings = settings?.getPropertyType({
      name: inputType.name,
      input: true,
    });
    const modelField = model?.fields.find(f => f.name === name);
    const isCustomsApplicable = typeName === modelField?.type;
    const propertyType = castArray(
      propertySettings?.name ||
        getPropertyType({
          location,
          type: typeName,
        }),
    );
    const property = propertyStructure({
      name,
      isNullable: !isRequired,
      propertyType,
      isList,
    });
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    classStructure.properties!.push(property);

    if (propertySettings) {
      importDeclarations.create({ ...propertySettings });
    } else if (propertyType.includes('Decimal')) {
      importDeclarations.add('Decimal', '@prisma/client/runtime');
    }

    // Get graphql type
    let graphqlType: string;
    const shouldHideField =
      settings?.shouldHideField({
        name: inputType.name,
        input: true,
      }) ||
      config.decorate.some(
        d =>
          d.name === 'HideField' &&
          d.from === '@nestjs/graphql' &&
          d.isMatchField(name) &&
          d.isMatchType(inputType.name),
      );

    const fieldType = settings?.getFieldType({
      name: inputType.name,
      input: true,
    });

    if (fieldType && isCustomsApplicable && !shouldHideField) {
      graphqlType = fieldType.name;
      importDeclarations.create({ ...fieldType });
    } else {
      // Import property type class
      const graphqlImport = getGraphqlImport({
        config,
        sourceFile,
        location,
        typeName,
        getSourceFile,
      });

      graphqlType = graphqlImport.name;
      let referenceName = propertyType[0];
      if (location === 'enumTypes') {
        referenceName = last(referenceName.split(' ')) as string;
      }

      if (
        graphqlImport.specifier &&
        !importDeclarations.has(graphqlImport.name) &&
        graphqlImport.name !== inputType.name
        // ((graphqlImport.name !== inputType.name && !shouldHideField) ||
        //     (shouldHideField && referenceName === graphqlImport.name))
      ) {
        importDeclarations.set(graphqlImport.name, {
          namedImports: [{ name: graphqlImport.name }],
          moduleSpecifier: graphqlImport.specifier,
        });
      }
    }

    ok(property.decorators, 'property.decorators is undefined');

    if (shouldHideField) {
      importDeclarations.add('HideField', '@nestjs/graphql');
      property.decorators.push({ name: 'HideField', arguments: [] });
    } else {
      // Generate `@Field()` decorator
      property.decorators.push({
        name: 'Field',
        arguments: [
          isList ? `() => [${graphqlType}]` : `() => ${graphqlType}`,
          JSON5.stringify({
            ...settings?.fieldArguments(),
            nullable: !isRequired,
          }),
        ],
      });

      // Debug
      // if (classStructure.name === 'XCreateInput') {
      //   console.log({
      //     field,
      //     property,
      //     modelField,
      //     graphqlInputType,
      //     'args.inputType': args.inputType,
      //     'classStructure.name': classStructure.name,
      //     classTransformerTypeModels,
      //     modelName,
      //     graphqlType,
      //   });
      // }

      if (graphqlType === 'GraphQLDecimal') {
        importDeclarations.add('transformToDecimal', 'prisma-graphql-type-decimal');
        importDeclarations.add('Transform', 'class-transformer');
        importDeclarations.add('Type', 'class-transformer');

        property.decorators.push(
          {
            name: 'Type',
            arguments: ['() => Object'],
          },
          {
            name: 'Transform',
            arguments: ['transformToDecimal'],
          },
        );
      } else if (
        location === 'inputObjectTypes' &&
        (modelField?.type === 'Decimal' ||
          [
            'connect',
            'connectOrCreate',
            'create',
            'createMany',
            'data',
            'delete',
            'deleteMany',
            'disconnect',
            'set',
            'update',
            'updateMany',
            'upsert',
            'where',
          ].includes(name) ||
          classTransformerTypeModels.has(getModelName(graphqlType) || ''))
      ) {
        importDeclarations.add('Type', 'class-transformer');
        property.decorators.push({ name: 'Type', arguments: [`() => ${graphqlType}`] });
      }

      if (isCustomsApplicable) {
        for (const options of settings || []) {
          if (
            (options.kind === 'Decorator' && options.input && options.match?.(name)) ??
            true
          ) {
            property.decorators.push({
              name: options.name,
              arguments: options.arguments as string[],
            });
            ok(options.from, "Missed 'from' part in configuration or field setting");
            importDeclarations.create(options);
          }
        }
      }

      for (const decorate of config.decorate) {
        if (decorate.isMatchField(name) && decorate.isMatchType(inputType.name)) {
          property.decorators.push({
            name: decorate.name,
            arguments: decorate.arguments?.map(x => pupa(x, { propertyType })),
          });
          importDeclarations.create(decorate);
        }
      }
    }

    eventEmitter.emitSync('ClassProperty', property, {
      location,
      isList,
      propertyType,
    });
  }

  sourceFile.set({
    statements: [...importDeclarations.toStatements(), classStructure],
  });
}
Example #26
Source File: generate-files.ts    From prisma-nestjs-graphql with MIT License 4 votes vote down vote up
export async function generateFiles(args: EventArguments) {
  const { project, config, output, eventEmitter } = args;

  if (config.emitSingle) {
    const rootDirectory =
      project.getDirectory(output) || project.createDirectory(output);
    const sourceFile =
      rootDirectory.getSourceFile('index.ts') ||
      rootDirectory.createSourceFile('index.ts', undefined, { overwrite: true });
    const statements = project.getSourceFiles().flatMap(s => {
      if (s === sourceFile) {
        return [];
      }
      const classDeclaration = s.getClass(() => true);
      const statements = s.getStructure().statements;
      // Reget decorator full name
      // TODO: Check possible bug of ts-morph
      if (Array.isArray(statements)) {
        for (const statement of statements) {
          if (
            !(typeof statement === 'object' && statement.kind === StructureKind.Class)
          ) {
            continue;
          }
          for (const property of statement.properties || []) {
            for (const decorator of property.decorators || []) {
              const fullName = classDeclaration
                ?.getProperty(property.name)
                ?.getDecorator(decorator.name)
                ?.getFullName();
              ok(
                fullName,
                `Cannot get full name of decorator of class ${statement.name!}`,
              );
              decorator.name = fullName;
            }
          }
        }
      }

      project.removeSourceFile(s);
      return statements;
    });
    const imports = new ImportDeclarationMap();
    const enums: (StatementStructures | string)[] = [];
    const classes: ClassDeclarationStructure[] = [];
    for (const statement of statements as (StatementStructures | string)[]) {
      if (typeof statement === 'string') {
        if (statement.startsWith('registerEnumType')) {
          enums.push(statement);
        }
        continue;
      }
      switch (statement.kind) {
        case StructureKind.ImportDeclaration:
          if (
            statement.moduleSpecifier.startsWith('./') ||
            statement.moduleSpecifier.startsWith('..')
          ) {
            continue;
          }
          for (const namedImport of statement.namedImports as ImportSpecifierStructure[]) {
            const name = namedImport.alias || namedImport.name;
            imports.add(name, statement.moduleSpecifier);
          }
          if (statement.defaultImport) {
            imports.create({
              from: statement.moduleSpecifier,
              name: statement.defaultImport,
              defaultImport: statement.defaultImport,
            });
          }
          if (statement.namespaceImport) {
            imports.create({
              from: statement.moduleSpecifier,
              name: statement.namespaceImport,
              namespaceImport: statement.namespaceImport,
            });
          }
          break;
        case StructureKind.Enum:
          enums.unshift(statement);
          break;
        case StructureKind.Class:
          classes.push(statement);
          break;
      }
    }
    sourceFile.set({
      kind: StructureKind.SourceFile,
      statements: [...imports.toStatements(), ...enums, ...classes],
    });
  }

  if (config.emitCompiled) {
    project.compilerOptions.set({
      declaration: true,
      declarationDir: output,
      rootDir: output,
      outDir: output,
      emitDecoratorMetadata: false,
      skipLibCheck: true,
    });
    const emitResult = await project.emit();
    const errors = emitResult.getDiagnostics().map(d => String(d.getMessageText()));
    if (errors.length > 0) {
      eventEmitter.emitSync('Warning', errors);
    }
  } else {
    await project.save();
  }
}
Example #27
Source File: generator.ts    From wsdl-tsclient with MIT License 4 votes vote down vote up
export async function generate(
    parsedWsdl: ParsedWsdl,
    outDir: string,
    options: Partial<GeneratorOptions>
): Promise<void> {
    const mergedOptions: GeneratorOptions = {
        ...defaultOptions,
        ...options,
    };
    const project = new Project();

    const portsDir = path.join(outDir, "ports");
    const servicesDir = path.join(outDir, "services");
    const defDir = path.join(outDir, "definitions");

    const allMethods: Method[] = [];
    const allDefinitions: Definition[] = [];

    const clientImports: Array<OptionalKind<ImportDeclarationStructure>> = [];
    const clientServices: Array<OptionalKind<PropertySignatureStructure>> = [];
    for (const service of parsedWsdl.services) {
        const serviceFilePath = path.join(servicesDir, `${service.name}.ts`);
        const serviceFile = project.createSourceFile(serviceFilePath, "", {
            overwrite: true,
        });

        const serviceImports: Array<OptionalKind<ImportDeclarationStructure>> = [];
        const servicePorts: Array<OptionalKind<PropertySignatureStructure>> = [];
        for (const port of parsedWsdl.ports) {
            const portFilePath = path.join(portsDir, `${port.name}.ts`);
            const portFile = project.createSourceFile(portFilePath, "", {
                overwrite: true,
            });

            const portImports: Array<OptionalKind<ImportDeclarationStructure>> = [];
            const portFileMethods: Array<OptionalKind<MethodSignatureStructure>> = [];
            for (const method of port.methods) {
                // TODO: Deduplicate PortImports
                if (method.paramDefinition !== null) {
                    if (!allDefinitions.includes(method.paramDefinition)) {
                        // Definition is not generated
                        generateDefinitionFile(
                            project,
                            method.paramDefinition,
                            defDir,
                            [method.paramDefinition.name],
                            allDefinitions,
                            mergedOptions
                        );
                        addSafeImport(
                            clientImports,
                            `./definitions/${method.paramDefinition.name}`,
                            method.paramDefinition.name
                        );
                    }
                    addSafeImport(
                        portImports,
                        `../definitions/${method.paramDefinition.name}`,
                        method.paramDefinition.name
                    );
                }
                if (method.returnDefinition !== null) {
                    if (!allDefinitions.includes(method.returnDefinition)) {
                        // Definition is not generated
                        generateDefinitionFile(
                            project,
                            method.returnDefinition,
                            defDir,
                            [method.returnDefinition.name],
                            allDefinitions,
                            mergedOptions
                        );
                        addSafeImport(
                            clientImports,
                            `./definitions/${method.returnDefinition.name}`,
                            method.returnDefinition.name
                        );
                    }
                    addSafeImport(
                        portImports,
                        `../definitions/${method.returnDefinition.name}`,
                        method.returnDefinition.name
                    );
                }
                // TODO: Deduplicate PortMethods
                allMethods.push(method);
                portFileMethods.push({
                    name: sanitizePropName(method.name),
                    parameters: [
                        {
                            name: camelcase(method.paramName),
                            type: method.paramDefinition ? method.paramDefinition.name : "{}",
                        },
                        {
                            name: "callback",
                            type: `(err: any, result: ${
                                method.returnDefinition ? method.returnDefinition.name : "unknown"
                            }, rawResponse: any, soapHeader: any, rawRequest: any) => void`, // TODO: Use ts-morph to generate proper type
                        },
                    ],
                    returnType: "void",
                });
            } // End of PortMethod
            if (!mergedOptions.emitDefinitionsOnly) {
                addSafeImport(serviceImports, `../ports/${port.name}`, port.name);
                servicePorts.push({
                    name: sanitizePropName(port.name),
                    isReadonly: true,
                    type: port.name,
                });
                portFile.addImportDeclarations(portImports);
                portFile.addStatements([
                    {
                        leadingTrivia: (writer) => writer.newLine(),
                        isExported: true,
                        kind: StructureKind.Interface,
                        name: port.name,
                        methods: portFileMethods,
                    },
                ]);
                Logger.log(`Writing Port file: ${path.resolve(path.join(portsDir, port.name))}.ts`);
                portFile.saveSync();
            }
        } // End of Port

        if (!mergedOptions.emitDefinitionsOnly) {
            addSafeImport(clientImports, `./services/${service.name}`, service.name);
            clientServices.push({ name: sanitizePropName(service.name), type: service.name });

            serviceFile.addImportDeclarations(serviceImports);
            serviceFile.addStatements([
                {
                    leadingTrivia: (writer) => writer.newLine(),
                    isExported: true,
                    kind: StructureKind.Interface,
                    name: service.name,
                    properties: servicePorts,
                },
            ]);
            Logger.log(`Writing Service file: ${path.resolve(path.join(servicesDir, service.name))}.ts`);
            serviceFile.saveSync();
        }
    } // End of Service

    if (!mergedOptions.emitDefinitionsOnly) {
        const clientFilePath = path.join(outDir, "client.ts");
        const clientFile = project.createSourceFile(clientFilePath, "", {
            overwrite: true,
        });
        clientFile.addImportDeclaration({
            moduleSpecifier: "soap",
            namedImports: [
                { name: "Client", alias: "SoapClient" },
                { name: "createClientAsync", alias: "soapCreateClientAsync" },
            ],
        });
        clientFile.addImportDeclarations(clientImports);
        clientFile.addStatements([
            {
                leadingTrivia: (writer) => writer.newLine(),
                isExported: true,
                kind: StructureKind.Interface,
                // docs: [`${parsedWsdl.name}Client`],
                name: `${parsedWsdl.name}Client`,
                properties: clientServices,
                extends: ["SoapClient"],
                methods: allMethods.map<OptionalKind<MethodSignatureStructure>>((method) => ({
                    name: sanitizePropName(`${method.name}Async`),
                    parameters: [
                        {
                            name: camelcase(method.paramName),
                            type: method.paramDefinition ? method.paramDefinition.name : "{}",
                        },
                    ],
                    returnType: `Promise<[result: ${
                        method.returnDefinition ? method.returnDefinition.name : "unknown"
                    }, rawResponse: any, soapHeader: any, rawRequest: any]>`,
                })),
            },
        ]);
        const createClientDeclaration = clientFile.addFunction({
            name: "createClientAsync",
            docs: [`Create ${parsedWsdl.name}Client`],
            isExported: true,
            parameters: [
                {
                    isRestParameter: true,
                    name: "args",
                    type: "Parameters<typeof soapCreateClientAsync>",
                },
            ],
            returnType: `Promise<${parsedWsdl.name}Client>`, // TODO: `any` keyword is very dangerous
        });
        createClientDeclaration.setBodyText("return soapCreateClientAsync(args[0], args[1], args[2]) as any;");
        Logger.log(`Writing Client file: ${path.resolve(path.join(outDir, "client"))}.ts`);
        clientFile.saveSync();
    }

    // Create index file with re-exports
    const indexFilePath = path.join(outDir, "index.ts");
    const indexFile = project.createSourceFile(indexFilePath, "", {
        overwrite: true,
    });

    indexFile.addExportDeclarations(
        allDefinitions.map((def) => ({
            namedExports: [def.name],
            moduleSpecifier: `./definitions/${def.name}`,
        }))
    );
    if (!mergedOptions.emitDefinitionsOnly) {
        // TODO: Aggregate all exports during declarations generation
        // https://ts-morph.com/details/exports
        indexFile.addExportDeclarations([
            {
                namedExports: ["createClientAsync", `${parsedWsdl.name}Client`],
                moduleSpecifier: "./client",
            },
        ]);
        indexFile.addExportDeclarations(
            parsedWsdl.services.map((service) => ({
                namedExports: [service.name],
                moduleSpecifier: `./services/${service.name}`,
            }))
        );
        indexFile.addExportDeclarations(
            parsedWsdl.ports.map((port) => ({
                namedExports: [port.name],
                moduleSpecifier: `./ports/${port.name}`,
            }))
        );
    }

    Logger.log(`Writing Index file: ${path.resolve(path.join(outDir, "index"))}.ts`);

    indexFile.saveSync();
}