import * as fs from 'fs'; import * as path from 'path'; import * as globby from 'globby'; import * as babel from '@babel/core'; import generator from '@babel/generator'; import traverse from '@babel/traverse'; const isFirstUpperCase = (str: string) => str[0].toUpperCase() === str[0]; const firstToLowerCase = (str: string) => str[0].toLowerCase() + str.slice(1); interface ConceptProperty { name: string, value?: string, type?: string, immutable?: boolean, circular?: boolean, excludedInJSON?: boolean, action?: string, inherited?: boolean, } interface Concept { name: string, propertyList: Array, } const conceptMap: { [name: string]: Concept } = {}; function parse() { globby.sync(['./src/types/**/*.ts']).forEach((filePath) => { const baseName = path.basename(filePath, '.ts'); if (!isFirstUpperCase(baseName)) return; const code = fs.readFileSync(filePath, 'utf8'); const ast = babel.parse(code, { filename: filePath, plugins: [ require('@babel/plugin-syntax-typescript'), [require('@babel/plugin-proposal-decorators'), { decoratorsBeforeExport: false, legacy: false, }], ], }); traverse(ast, { enter(path) { if (path.isClassDeclaration()) { const concept: Concept = { name: path.node.id.name, propertyList: path.node.body.body .filter((property) => property.type === 'ClassProperty') .map((property) => { property = property as babel.types.ClassProperty; const result: ConceptProperty = { name: (property.key as babel.types.Identifier).name, type: property.typeAnnotation ? generator(property.typeAnnotation).code.replace(/^:\s*/, '') : undefined, }; property.decorators && property.decorators.forEach((decorator) => { if (decorator.expression.type === 'CallExpression' && decorator.expression.callee.type === 'Identifier') { if (decorator.expression.callee.name === 'immutable') result.immutable = true; else if (decorator.expression.callee.name === 'circular') result.circular = true; else if (decorator.expression.callee.name === 'excludedInJSON') result.excludedInJSON = true; else if (decorator.expression.callee.name === 'action') result.action = 'true'; } }); return result; }), }; conceptMap[concept.name] = concept; } }, }); }); } function generateConcept(concept: Concept) { const outputs = []; outputs.push(`${concept.name}`); concept.propertyList.forEach((property) => { outputs.push(` ${property.name}: ${property.type}`); }); return outputs.join('\n'); } function generate() { const json = JSON.stringify(conceptMap, null, 4); fs.writeFileSync('./spec/conceptMap.json', json, 'utf8'); } parse(); generate();