ts-morph#SourceFile TypeScript Examples

The following examples show how to use ts-morph#SourceFile. 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: export.ts    From cli with MIT License 7 votes vote down vote up
// 新增 export const x = {}
export function addConstExport(
  source: SourceFile,
  key: string,
  value: any,
  useStringify = true,
  apply = true
) {
  // 又可以抽离成export相关的方法
  // 拿到所有export const 检查是否已存在
  // 不允许value是函数
  // 添加类型提示的方式
  // const exports

  const exportVars = getExportVariableIdentifiers(source);

  if (exportVars.includes(key)) {
    consola.error(`export ${key} exist!`);
    return;
  }

  source
    .addVariableStatement({
      declarationKind: VariableDeclarationKind.Const,
      declarations: [
        {
          name: key,
          initializer: writer =>
            writer.write(useStringify ? JSON.stringify(value) : value),
        },
      ],
    })
    .setIsExported(true);

  apply && source.saveSync();
}
Example #2
Source File: enum-mapping.ts    From selling-partner-api-sdk with MIT License 6 votes vote down vote up
function processArrayNodes(sourceFile: SourceFile, nodes: TypeElementTypes[]): void {
  for (const node of nodes) {
    /**
     * Check both Array<T> and T[] syntax.
     *
     * Docs: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#arrays
     * TODO: implement for a node has union and intersection of enum type. Such as: Enum1 | Enum2, Enum1 & Enum2.
     */
    const child =
      node.getFirstChildByKind(ts.SyntaxKind.TypeReference) ||
      node.getFirstChildByKind(ts.SyntaxKind.ArrayType)
    const typeReference = child?.getFirstChildByKind(ts.SyntaxKind.TypeReference)
    const enumMembers = typeReference && sourceFile.getEnum(typeReference.getText())?.getMembers()

    if (child && typeReference && enumMembers) {
      const filePath = `${sourceFile.getDirectory().getBaseName()}/${sourceFile.getBaseName()}`
      const typeName = child.getText()
      log.info(`Starting mapping ${typeName} in ${filePath}`)

      /**
       * Wrap inside a block for all array syntax.
       * TODO: remove duplicate elements if a node has union and intersection of enum type.
       */
      typeReference.replaceWithText(
        `(${[typeReference.getText(), ...getEnumValues(enumMembers)].join(' | ')})`,
      )

      log.info(`Finished mapping ${typeName} in ${filePath}`)
    }
  }
}
Example #3
Source File: import.ts    From cli with MIT License 6 votes vote down vote up
// 移除导入声明
export function removeImportDeclaration(
  source: SourceFile,
  specifiers: string[],
  apply = true
) {
  const sourceImports = findImportsSpecifier(source);

  const validSpecToRemove = specifiers.filter(spec =>
    sourceImports.includes(spec)
  );

  if (!validSpecToRemove.length) {
    return;
  }

  validSpecToRemove.forEach(spec => {
    const targetImport = findImportsDeclaration(source, spec);

    targetImport.remove();
  });

  apply && source.saveSync();
}
Example #4
Source File: apiCamelCase.ts    From taiga-front-next with GNU Affero General Public License v3.0 6 votes vote down vote up
apiCamelCaseReplacements = (rootNode: SourceFile) => {
  rootNode.forEachDescendant((node) => {
    if (
      node.getKind() === ts.SyntaxKind.StringLiteral &&
      node.getParent()?.getKind() !== ts.SyntaxKind.ImportDeclaration &&
      valid(node.getText())) {
      const text = node.getText();
      node.replaceWithText(text[0] === '\'' ? `'${camelCase(text)}'` : camelCase(text));
    } else if (node.getKind() === ts.SyntaxKind.Identifier &&
        valid(node.getText()) &&
        (node.getParent()?.getKind() === ts.SyntaxKind.PropertySignature ||
        node.getParent()?.getKind() === ts.SyntaxKind.PropertyAssignment ||
        node.getParent()?.getKind() === ts.SyntaxKind.PropertyAccessExpression)) {
        const text = node.getText();
        node.replaceWithText(camelCase(text));
    }
  });
}
Example #5
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 #6
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 #7
Source File: enum-mapping.ts    From selling-partner-api-sdk with MIT License 6 votes vote down vote up
function processEnumNodes(sourceFile: SourceFile, nodes: TypeElementTypes[]): void {
  for (const node of nodes) {
    // TODO: implement for a node has union and intersection of enum type. Such as: Enum1 | Enum2, Enum1 & Enum2.
    const typeReference = node.getFirstChildByKind(ts.SyntaxKind.TypeReference)
    const enumNode = typeReference && sourceFile.getEnum(typeReference.getText())
    const enumMembers = enumNode && enumNode.getMembers()

    if (typeReference && enumMembers) {
      const filePath = `${sourceFile.getDirectory().getBaseName()}/${sourceFile.getBaseName()}`
      const typeName = typeReference.getText()
      log.info(`Starting mapping ${typeName} in ${filePath}`)

      // TODO: remove duplicate elements if a node has union and intersection of enum type.
      typeReference.replaceWithText([typeName, ...getEnumValues(enumMembers)].join(' | '))

      log.info(`Finished mapping ${typeName} in ${filePath}`)
    }
  }
}
Example #8
Source File: class.ts    From cli with MIT License 6 votes vote down vote up
// 获得类的方法声明,可根据方法名查找
export function getExistClassMethodsDeclaration(
  source: SourceFile,
  className: string,
  methodName?: string
) {
  const classDeclarations = source
    .getFirstChildByKind(SyntaxKind.SyntaxList)
    .getChildrenOfKind(SyntaxKind.ClassDeclaration);

  const targetClass = classDeclarations.filter(
    classDecs =>
      classDecs.getFirstChildByKind(SyntaxKind.Identifier).getText() ===
      className
  );

  if (!targetClass.length) {
    return;
  }

  const targetClassItem = targetClass[0];

  const methods = targetClassItem.getMethods();

  if (methodName) {
    return methods.filter(
      m => m.getFirstChildByKind(SyntaxKind.Identifier).getText() === methodName
    )[0];
  } else {
    return methods;
  }
}
Example #9
Source File: mongodb.spec.ts    From prisma-nestjs-graphql with MIT License 5 votes vote down vote up
sourceFiles: SourceFile[]
Example #10
Source File: configuration.ts    From cli with MIT License 5 votes vote down vote up
// 获取生命周期类
export function getLifeCycleClass(source: SourceFile) {
  return getClassDecByName(source, LIFE_CYCLE_CLASS_IDENTIFIER);
}
Example #11
Source File: helpers.ts    From prisma-nestjs-graphql with MIT License 5 votes vote down vote up
export function testSourceFile(args: {
  project: Project;
  file?: string;
  class?: string;
  property?: string;
}) {
  const { project, file, property, class: className } = args;
  let sourceFile: SourceFile;
  if (file) {
    sourceFile = project.getSourceFileOrThrow(s => s.getFilePath().endsWith(file));
  } else if (className) {
    sourceFile = project.getSourceFileOrThrow(s => Boolean(s.getClass(className)));
  } else {
    throw new TypeError('file or class must be provided');
  }

  const importDeclarations = sourceFile
    .getImportDeclarations()
    .map(d => d.getStructure());
  const classFile = sourceFile.getClass(() => true)!;
  const propertyStructure =
    property && classFile.getProperty(p => p.getName() === property)?.getStructure();
  const propertyDecorators = (
    propertyStructure as PropertyDeclarationStructure | undefined
  )?.decorators;
  const fieldDecorator = propertyDecorators?.find(d => d.name === 'Field');

  type ImportElement = { name: string; specifier: string };
  const namedImports: ImportElement[] = [];
  const namespaceImports: ImportElement[] = [];

  for (const d of importDeclarations) {
    if (d.namespaceImport) {
      namespaceImports.push({
        name: d.namespaceImport,
        specifier: d.moduleSpecifier,
      });
    }
    for (const s of (d.namedImports || []) as ImportSpecifierStructure[]) {
      namedImports.push({
        name: s.name,
        specifier: d.moduleSpecifier,
      });
    }
  }

  return {
    sourceFile,
    classFile,
    sourceText: sourceFile.getText(),
    namedImports,
    namespaceImports,
    property: propertyStructure as PropertyDeclarationStructure | undefined,
    propertyDecorators,
    fieldDecorator,
    fieldDecoratorType: fieldDecorator?.arguments?.[0],
    fieldDecoratorOptions: fieldDecorator?.arguments?.[1],
  };
}
Example #12
Source File: index.ts    From tarojs-router-next with MIT License 5 votes vote down vote up
routerSourceFile: SourceFile
Example #13
Source File: test.spec.ts    From prisma-nestjs-graphql with MIT License 5 votes vote down vote up
sourceFiles: SourceFile[]
Example #14
Source File: index.ts    From tarojs-router-next with MIT License 5 votes vote down vote up
/** 解析 route.config.ts 导出的 Params、Data、Ext */
  parseSourceFile(options: { page: Page; project: Project; sourceFile: SourceFile }) {
    const { page, project, sourceFile } = options
    const checker = project.getTypeChecker()

    const pageQuery: {
      params?: QueryMeta
      data?: QueryMeta
      ext?: string
    } = {}

    sourceFile.getExportedDeclarations().forEach((declarations, name) => {
      if (declarations.length > 1) return
      const declaration = declarations[0] as any
      switch (name) {
        case 'Params':
          pageQuery.params = extractType({
            name,
            declaration,
            checker,
          })
          break
        case 'Data':
          pageQuery.data = extractType({
            name,
            declaration,
            checker,
          })
          break
        case 'Ext':
          pageQuery.ext = extractValue({
            name,
            declaration,
            checker,
          })
          break
      }
    })

    page.query = pageQuery

    return page
  }
Example #15
Source File: class.ts    From cli with MIT License 5 votes vote down vote up
// 获取类的所有方法名称
export function getExistClassMethods(source: SourceFile, className: string) {
  return getExistClassMethodsDeclaration(source, className).map(m =>
    m.getName()
  );
}
Example #16
Source File: gen-dts.ts    From vue-components-lib-seed with MIT License 4 votes vote down vote up
genVueTypes = async () => {
  const project = new Project({
    compilerOptions: {
      allowJs: true,
      declaration: true,
      emitDeclarationOnly: true,
      noEmitOnError: true,
      outDir: path.resolve(cwd(), 'dist'),
    },
    tsConfigFilePath: TSCONFIG_PATH,
    skipAddingFilesFromTsConfig: true,
  })

  const sourceFiles: SourceFile[] = []

  const entry = await parseComponentExports()
  const entrySourceFile = project.createSourceFile(
    path.resolve(cwd(), 'src/packages/my-lib.ts'),
    entry,
    { overwrite: true }
  )

  sourceFiles.push(entrySourceFile)

  const filePaths = klawSync(
    path.resolve(cwd(), 'src/packages'),
    {
      nodir: true,
    }
  )
    .map((item) => item.path)
    .filter((path) => !DEMO_RE.test(path))
    .filter((path) => !TEST_RE.test(path))

  await Promise.all(
    filePaths.map(async (file) => {
      if (file.endsWith('.vue')) {
        const content = await fs.promises.readFile(
          file,
          'utf-8'
        )
        const sfc = vueCompiler.parse(
          content as unknown as string
        )
        const { script, scriptSetup } = sfc.descriptor
        if (script || scriptSetup) {
          let content = ''
          let isTS = false
          if (script && script.content) {
            content += script.content
            if (script.lang === 'ts') isTS = true
          }
          if (scriptSetup) {
            const compiled = vueCompiler.compileScript(
              sfc.descriptor,
              {
                id: 'xxx',
              }
            )
            content += compiled.content
            if (scriptSetup.lang === 'ts') isTS = true
          }
          const sourceFile = project.createSourceFile(
            path.relative(process.cwd(), file) +
              (isTS ? '.ts' : '.js'),
            content
          )
          sourceFiles.push(sourceFile)
        }
      } else if (file.endsWith('.ts')) {
        const sourceFile = project.addSourceFileAtPath(file)
        sourceFiles.push(sourceFile)
      }
    })
  )

  const diagnostics = project.getPreEmitDiagnostics()

  console.log(
    project.formatDiagnosticsWithColorAndContext(
      diagnostics
    )
  )

  project.emitToMemory()

  for (const sourceFile of sourceFiles) {
    const emitOutput = sourceFile.getEmitOutput()
    for (const outputFile of emitOutput.getOutputFiles()) {
      const filepath = outputFile.getFilePath()

      await fs.promises.mkdir(path.dirname(filepath), {
        recursive: true,
      })

      await fs.promises.writeFile(
        filepath,
        outputFile.getText(),
        'utf8'
      )
    }
  }
}
Example #17
Source File: custom-decorators.spec.ts    From prisma-nestjs-graphql with MIT License 4 votes vote down vote up
describe('custom decorators namespace both input and output', () => {
  before(async () => {
    ({ project } = await testGenerate({
      schema: `
                model User {
                    id Int @id
                    /// @Validator.MaxLength(30)
                    name String
                    /// @Validator.Max(999)
                    /// @Validator.Min(18)
                    age Int
                    /// @Validator.IsEmail()
                    /// @FieldType({ name: 'Scalars.GraphQLEmailAddress', from: 'graphql-scalars', input: true })
                    email String?
                }`,
      options: [
        `outputFilePattern = "{name}.{type}.ts"`,
        // custom decorators (validate)
        // import * as Validator from 'class-validator'
        // @Validator.IsEmail()
        // email: string
        `fields_Validator_from = "class-validator"`,
        `fields_Validator_input = true`,
      ],
    }));
  });

  describe('aggregates should not have validators', () => {
    it('user-count-aggregate.input', () => {
      const s = testSourceFile({
        project,
        file: 'user-count-aggregate.input.ts',
        property: 'email',
      });
      expect(s.propertyDecorators).toHaveLength(1);
      expect(s.propertyDecorators).not.toContainEqual(
        expect.objectContaining({
          name: 'IsEmail',
        }),
      );
      expect(s.fieldDecoratorType).toEqual('() => Boolean');
    });

    it('user-count-order-by-aggregate.input name is type of sort order', () => {
      const s = testSourceFile({
        project,
        file: 'user-count-order-by-aggregate.input.ts',
        property: 'email',
      });
      expect(
        s.propertyDecorators?.find(d => d.name.includes('IsEmail')),
      ).toBeUndefined();
      expect(s.fieldDecoratorType).toEqual('() => SortOrder');
    });
  });

  describe('custom decorators in user create input', () => {
    let sourceFile: SourceFile;
    let importDeclarations: any[];
    let classFile: any;

    before(() => {
      ({ sourceFile, classFile } = testSourceFile({
        project,
        file: 'user-create.input.ts',
      }));
      importDeclarations = sourceFile
        .getImportDeclarations()
        .map(d => d.getStructure());
    });

    it('decorator validator maxlength should exists', () => {
      const d = classFile
        .getProperty('name')
        ?.getDecorator(d => d.getFullName() === 'Validator.MaxLength');
      expect(d).toBeTruthy();
      expect(d?.getText()).toBe('@Validator.MaxLength(30)');
    });

    it('imports should contains custom import', () => {
      expect(importDeclarations).toContainEqual(
        expect.objectContaining({
          namespaceImport: 'Validator',
          moduleSpecifier: 'class-validator',
        }),
      );
    });

    it('several decorators length', () => {
      const s = testSourceFile({
        project,
        file: 'user-create.input.ts',
        property: 'age',
      });
      expect(s.propertyDecorators).toHaveLength(3);
    });

    it('validator should be imported once', () => {
      expect(
        importDeclarations.filter(x => x.moduleSpecifier === 'class-validator'),
      ).toHaveLength(1);
    });
  });

  describe('should not have metadata in description', () => {
    it('age', () => {
      const s = testSourceFile({
        project,
        file: 'user.model.ts',
        property: 'age',
      });
      expect(s.fieldDecoratorOptions).not.toContain('description');
    });

    it('name', () => {
      const s = testSourceFile({
        project,
        file: 'user.model.ts',
        property: 'name',
      });
      expect(s.fieldDecoratorOptions).not.toContain('description');
    });

    it('email', () => {
      const s = testSourceFile({
        project,
        file: 'user.model.ts',
        property: 'email',
      });
      expect(s.fieldDecoratorOptions).not.toContain('description');
    });
  });

  it('output model has no maxlength decorator', () => {
    const s = testSourceFile({
      project,
      file: 'user.model.ts',
      property: 'name',
    });
    expect(s.propertyDecorators?.find(d => d.name === 'MaxLength')).toBeFalsy();
  });
});
Example #18
Source File: decorator.ts    From cli with MIT License 4 votes vote down vote up
// 更新数组类型的装饰器参数
// 暂时只支持@Deco({  })
export function updateDecoratorArrayArgs(
  source: SourceFile,
  decoratorName: string,
  argKey: string,
  identifier: string,
  apply = true
) {
  const decoratorSyntaxList = source
    .getFirstChildByKind(SyntaxKind.SyntaxList)
    .getFirstChildByKind(SyntaxKind.ClassDeclaration)
    .getFirstChildByKind(SyntaxKind.SyntaxList);

  const correspondingDecorator = decoratorSyntaxList
    .getChildren()
    .filter(child => {
      if (child.getKind() !== SyntaxKind.Decorator) {
        return false;
      } else {
        return (
          child
            .getFirstChildByKind(SyntaxKind.CallExpression)
            .getFirstChildByKind(SyntaxKind.Identifier)
            .getText() === decoratorName
        );
      }
    })[0]
    .asKind(SyntaxKind.Decorator);

  const decoratorArg = correspondingDecorator
    .getArguments()[0]
    .asKind(SyntaxKind.ObjectLiteralExpression);

  const currentArgObjectKeys = decoratorArg
    .getFirstChildByKind(SyntaxKind.SyntaxList)
    .getChildrenOfKind(SyntaxKind.PropertyAssignment)
    .map(assign => assign.getFirstChildByKind(SyntaxKind.Identifier).getText());

  if (currentArgObjectKeys.includes(argKey)) {
    // 参数已存在 合并
    // imports: [orm]
    // add args by addChildText
    const propAssignments = decoratorArg
      .getFirstChildByKind(SyntaxKind.SyntaxList)
      .getChildrenOfKind(SyntaxKind.PropertyAssignment)
      .find(
        assign =>
          assign.getChildrenOfKind(SyntaxKind.Identifier)[0].getText() ===
          argKey
      );

    // orm
    const existPropAssignedValue = propAssignments
      .getFirstChildByKindOrThrow(SyntaxKind.ArrayLiteralExpression)
      .getFirstChildByKind(SyntaxKind.SyntaxList);

    existPropAssignedValue.getText()
      ? existPropAssignedValue.addChildText(`, ${identifier}`)
      : existPropAssignedValue.addChildText(identifier);

    // const existPropAssign = decoratorArg
    //   .getProperty(argKey)
    //   .getFirstChildByKind(SyntaxKind.ArrayLiteralExpression)
    //   .getFirstChildByKind(SyntaxKind.SyntaxList);

    // const existPropAssignValue = existPropAssign.getFirstChildByKind(
    //   SyntaxKind.Identifier
    // );

    // const val: string[] = [];

    // if (!existPropAssignValue) {
    //   val.push(identifier);
    // } else {
    //   val.push(String(existPropAssignValue.getText()), `, ${identifier}`);
    // }
  } else {
    // TODO: support insert at start or end
    decoratorArg.insertPropertyAssignment(0, {
      name: argKey,
      initializer: `[${identifier}]`,
    });
  }

  apply && source.saveSync();
}