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 |
// 新增 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 |
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 |
// 移除导入声明
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 |
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 |
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 |
// 确保类拥有属性
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 |
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 |
// 获得类的方法声明,可根据方法名查找
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 |
sourceFiles: SourceFile[]
Example #10
Source File: configuration.ts From cli with MIT License | 5 votes |
// 获取生命周期类
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 |
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 |
routerSourceFile: SourceFile
Example #13
Source File: test.spec.ts From prisma-nestjs-graphql with MIT License | 5 votes |
sourceFiles: SourceFile[]
Example #14
Source File: index.ts From tarojs-router-next with MIT License | 5 votes |
/** 解析 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 |
// 获取类的所有方法名称
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 |
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 |
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 |
// 更新数组类型的装饰器参数
// 暂时只支持@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();
}