lib/generator.js (2,021 lines of code) (raw):

'use strict'; const assert = require('assert'); const path = require('path'); const fs = require('fs'); const DSL = require('@darabonba/parser'); const { _name, _string, _type, _subModelName, _vid, _isBinaryOp, _escape, _snakeCase, _camelCase, _avoidModel, REQUEST, RESPONSE, CORE, _upperFirst, _removeFilesInDirectory } = require('./helper'); const getBuiltin = require('./builtin'); const { Tag } = require('@darabonba/parser/lib/tag'); const Annotation = require('@darabonba/annotation-parser'); function avoidReserveName(name) { const reserves = [ 'function' ]; if (reserves.indexOf(name) !== -1) { return `_${name}`; } return name; } function getAttr(node, attrName) { for (let i = 0; i < node.attrs.length; i++) { if (_name(node.attrs[i].attrName) === attrName) { return node.attrs[i].attrValue.string || node.attrs[i].attrValue.lexeme; } } } class Visitor { static get supportGenerateTest() { return true; } constructor(option = {}) { this.config = { outputDir: '', indent: ' ', packageName: '', clientPath: option.testFile === true ? 'test/client.spec.ts' : 'src/client.ts', ...option }; this.output = ''; this.outputDir = option.outputDir; this.__module = {}; this.__externModule = new Map(); this.modelPath = 'src/models'; this.exceptionPath = 'src/exceptions'; this.config.libraries = this.config.libraries || {}; this.typedef = option.ts && option.ts.typedef ? option.ts.typedef : {}; this.exports = option.exports ? option.exports : {}; this.typedefImport = []; this.moduleExport = []; Object.keys(this.typedef).forEach(type => { if (type.import && !this.typedefImport.includes(type.import)) { this.typedefImport.push(type.import); } }); // Object.keys(this.exports).forEach(moduleName => { // const modulePath = path.resolve(__dirname, this.exports[moduleName]); // const mainPath = path.resolve(__dirname, this.main); // this.moduleExport.push({ // name: moduleName, // path: path.relative(path.dirname(mainPath), modulePath) // }); // }); if (!this.outputDir) { throw new Error('`option.outputDir` should not empty'); } if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } this.package = {}; const packagePath = path.join(this.outputDir, 'package.json'); if (fs.existsSync(packagePath)) { try { this.package = JSON.parse(fs.readFileSync(packagePath)); } catch (err) { throw new Error('invalid package.json'); } } this.initPackage(); } saveTsConfig(tsConfigPath) { const config = { compilerOptions: { target: 'es2017', module: 'commonjs', declaration: true, sourceMap: true, outDir: './dist', esModuleInterop: true }, include: [ 'src/**/*' ] }; fs.writeFileSync(tsConfigPath, JSON.stringify(config, null, 2)); } initPackage() { this.package.name = this.package.name || this.config.packageName; this.package.version = this.package.version || '1.0.0'; this.package.description = this.package.description || ''; this.package.main = this.package.main || 'dist/client.js'; if (!this.package.scripts) { this.package.scripts = { test: 'mocha --reporter spec --timeout 3000 test/*.test.js', 'test-cov': 'nyc -e .ts -r=html -r=text -r=lcov npm run test', build: 'tsc', prepublishOnly: 'tsc' }; } this.package.author = this.package.author || ''; this.package.license = this.package.license || 'ISC'; if (!this.package.devDependencies) { this.package.devDependencies = { '@types/node': '^12.12.26', nyc: '^15.0.0', 'source-map-support': '^0.5.16', 'ts-node': '^8.6.2', typescript: '^3.7.5' }; } if (!this.package.dependencies) { this.package.dependencies = { '@darabonba/typescript': '^1.0.0' }; } Object.keys(this.typedef).forEach(type => { if (this.typedef[type].package) { let [pkgName, version] = this.typedef[type].package.split(':'); if (!this.package.dependencies[pkgName]) { this.package.dependencies[pkgName] = version; } } }); if (!this.package.files) { this.package.files = [ 'dist', 'src' ]; } } saveInnerModule(ast, targetPath) { const keys = ast.innerModule.keys(); let data = keys.next(); while (!data.done) { const aliasId = data.value; const moduleAst = ast.innerDep.get(aliasId); const filepath = path.join(path.dirname(targetPath), ast.innerModule.get(aliasId)); this.modelPath = filepath.replace('.ts', 'Models'); this.exceptionPath = filepath.replace('.ts', 'Exceptions'); this.visitModule(moduleAst, filepath, 0); data = keys.next(); } } visitHeader() { this.header = ''; if (this.config.editable !== true) { this.header += `// This file is auto-generated, don't edit it\n`; } let str = ''; if (this.usedTypes.includes('Readable')) { str += 'Readable'; } if (this.usedTypes.includes('Writable')) { str += ', Writable'; } if (str) { this.header += `import { ${str} } from 'stream';\n`; } this.header += `import * as $dara from '@darabonba/typescript';\n`; this.typedefImport.forEach(type => { if(!this.moudleUsed.includes(type)) { return; } this.header += `import * as ${type} from '${type}';\n`; }); // this.emit(`\n`); [...new Set(this.subModelUsed)].map(modelName => { this.header += `import { ${modelName} } from "./${_avoidModel(_camelCase(_snakeCase(modelName)))}";\n`; }); [...new Set(this.exceptionUsed)].map(ExceptionName => { this.header += `import { ${ExceptionName}Error } from "./${_camelCase(_snakeCase(ExceptionName))}Error";\n`; }); Object.keys(this.headerImport).map(aliasId => { if(!this.modelUsed.includes(aliasId) && !this.moudleUsed.includes(aliasId)) { return; } const { pkgName, inner } = this.headerImport[aliasId]; this.header += 'import '; let sep = false; if(this.moudleUsed.includes(aliasId)) { if(inner) { this.header += `{ ${inner}${inner !== aliasId ? ` as ${aliasId}` : ''}`; } else { this.header += `${aliasId}`; } sep = true; } if(this.modelUsed.includes(aliasId)) { if(inner) { this.header += `${sep ? ', ' : ''}$${inner}${inner !== aliasId ? ` as $${aliasId}` : ''} }`; } else { this.header += `${sep ? ', ' : ''}* as $${aliasId} `; } } else { this.header += ` ${inner ? '}' : ''}`; } this.header += `from '${pkgName}';\n`; }); this.output = this.header + '\n\n' + this.output; } save(filepath, noHeader = false) { const targetPath = path.join(this.outputDir, filepath); const packagePath = path.join(this.outputDir, 'package.json'); fs.mkdirSync(path.dirname(targetPath), { recursive: true }); if(!noHeader) { this.visitHeader(); } fs.writeFileSync(targetPath, this.output); if (!fs.existsSync(packagePath)) { fs.writeFileSync(packagePath, JSON.stringify(this.package, null, 2)); } const tsConfigPath = path.join(this.outputDir, 'tsconfig.json'); if (!fs.existsSync(tsConfigPath)) { this.saveTsConfig(tsConfigPath); } this.output = ''; } emit(str, level) { this.output += ' '.repeat(level * 2) + str; } visit(ast, level = 0) { this.visitModule(ast, this.config.clientPath, level); } overwrite(ast, filepath) { if(!ast.moduleBody.nodes || !ast.moduleBody.nodes.length) { return; } const beginNotes = DSL.note.getNotes(this.notes, 0, ast.moduleBody.nodes[0].tokenRange[0]); const overwirte = beginNotes.find(note => note.note.lexeme === '@overwrite'); const targetPath = path.join(this.outputDir, filepath); if(overwirte && overwirte.arg.value === false && fs.existsSync(targetPath)) { return false; } return true; } visitModule(ast, filepath, level) { assert.equal(ast.type, 'module'); this.predefined = ast.models; this.usedExternException = ast.usedExternException; this.parentModule = ast.extends; this.comments = ast.comments; this.notes = ast.notes; this.builtin = getBuiltin(this); ast.innerModule = new Map(); this.headerImport = {}; this.usedTypes = []; this.modelUsed = []; this.moudleUsed = []; this.subModelUsed = []; this.exceptionUsed = []; this.fileBuffer = {}; this.hasException = false; this.hasModel = false; if(this.overwrite(ast, filepath) === false) { return; } this.importBefore(level); this.visitAnnotation(ast.annotation, level); this.eachImport(ast.imports, ast.usedExternModel, ast.innerModule, level); this.modelBefore(ast); const subModels = Object.keys(this.predefined).filter((key) => { return !key.startsWith('$') && this.predefined[key].type === 'model' && key.indexOf('.') !== -1; }).map((key) => { return this.predefined[key]; }); for (let i = 0; i < subModels.length; i++) { this.eachSubModel(subModels[i], level); } const models = ast.moduleBody.nodes.filter((item) => { return item.type === 'model'; }); for (let i = 0; i < models.length; i++) { this.eachModel(models[i], level); } this.visitModelIndex(subModels.concat(models)); const exceptions = ast.moduleBody.nodes.filter((item) => { return item.type === 'exception'; }); for (let i = 0; i < exceptions.length; i++) { this.eachException(exceptions[i], level); } this.visitExceptionIndex(exceptions); this.flushBuffer(); this.moduleBefore(ast); // models definition this.apiBefore(level); const types = ast.moduleBody.nodes.filter((item) => { return item.type === 'type'; }); const inits = ast.moduleBody.nodes.filter((item) => { return item.type === 'init'; }); const [init] = inits; if (init) { this.visitInit(init, types, level); } const apis = ast.moduleBody.nodes.filter((item) => { return item.type === 'api'; }); for (let i = 0; i < apis.length; i++) { if (i !== 0) { this.emit('\n'); } this.eachAPI(apis[i], level + 1); } this.functionBefore(); const functions = ast.moduleBody.nodes.filter((item) => { return item.type === 'function'; }); for (let i = 0; i < functions.length; i++) { if (i !== 0) { this.emit('\n'); } this.eachFunction(functions[i], level + 1); } this.moduleAfter(); if (this.config.exec) { this.emitExec(); } this.save(filepath); this.saveInnerModule(ast, filepath); } emitExec() { this.emit(` Client.main(process.argv.slice(2));`); } visitComments(comments, level) { comments.forEach(comment => { this.emit(`${comment.value}`, level); this.emit(`\n`); }); } visitAnnotation(annotation, level) { if (!annotation || !annotation.value) { return; } let comments = DSL.comment.getFrontComments(this.comments, annotation.index); this.visitComments(comments, level); var ast = Annotation.parse(annotation.value); var description = ast.items.find((item) => { return item.type === 'description'; }); var summary = ast.items.find((item) => { return item.type === 'summary'; }); var _return = ast.items.find((item) => { return item.type === 'return'; }); var deprecated = ast.items.find((item) => { return item.type === 'deprecated'; }); var params = ast.items.filter((item) => { return item.type === 'param'; }).map((item) => { return { name: item.name.id, text: item.text.text.trimEnd() }; }); var throws = ast.items.filter((item) => { return item.type === 'throws'; }).map((item) => { return item.text.text.trimEnd(); }); const descriptionText = description ? description.text.text.trimEnd() : ''; const summaryText = summary ? summary.text.text.trimEnd() : ''; const returnText = _return ? _return.text.text.trimEnd() : ''; let hasNextSection = false; this.emit(`/**\n`, level); if (summaryText !== '') { summaryText.split('\n').forEach((line) => { this.emit(` * ${line}\n`, level); }); hasNextSection = true; } if (descriptionText !== '') { if (hasNextSection) { this.emit(` * \n`, level); } this.emit(` * @remarks\n`, level); descriptionText.split('\n').forEach((line) => { this.emit(` * ${line}\n`, level); }); hasNextSection = true; } if (deprecated) { if (hasNextSection) { this.emit(` * \n`, level); } const deprecatedText = deprecated.text.text.trimEnd(); this.emit(` * @deprecated`, level); deprecatedText.split('\n').forEach((line, index) => { if (index === 0) { this.emit(` ${line}\n`); } else { this.emit(` * ${line}\n`, level); } }); hasNextSection = true; } if (params.length > 0) { if (hasNextSection) { this.emit(` * \n`, level); } params.forEach((item) => { // 遵循 TSDoc 风格:@param x - The first input number this.emit(` * @param ${item.name} - `, level); const items = item.text.trimEnd().split('\n'); items.forEach((line, index) => { if (index === 0) { this.emit(`${line}\n`); } else { this.emit(` * ${line}\n`, level); } }); }); } if (returnText !== '') { this.emit(` * @returns`, level); const returns = returnText.split('\n'); returns.forEach((line, index) => { if (index === 0) { this.emit(` ${line}\n`); } else { this.emit(` * ${line}\n`, level); } }); } if (throws.length > 0) { this.emit(` * \n`, level); throws.forEach((item) => { this.emit(' * @throws', level); const items = item.trimEnd().split('\n'); items.forEach((line, index) => { if (index === 0) { this.emit(` ${line}\n`); } else { this.emit(` * ${line}\n`, level); } }); }); } this.emit(` */`, level); this.emit(`\n`); } visitInit(ast, types, level) { assert.equal(ast.type, 'init'); types.forEach((item) => { let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]); this.visitComments(comments, level + 1); this.emit(`${_vid(item.vid)}: `, level + 1); this.visitType(item.value); this.emit(`;\n`); }); this.emit('\n'); this.visitAnnotation(ast.annotation, level + 1); let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]); this.visitComments(comments, level + 1); this.emit(`constructor`, level + 1); this.visitParams(ast.params, level); this.emit(` {\n`); if (ast.initBody) { this.visitStmts(ast.initBody, level + 2); } this.emit(`}\n`, level + 1); this.emit(`\n`); } eachImport(imports, usedModels, innerModule, level) { this.imports = new Map(); this.moduleTypedef = {}; if (imports.length > 0) { const lockPath = path.join(this.config.pkgDir, '.libraries.json'); const lock = fs.existsSync(lockPath) ? JSON.parse(fs.readFileSync(lockPath, 'utf8')) : {}; for (let i = 0; i < imports.length; i++) { const item = imports[i]; let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]); this.visitComments(comments, level); const aliasId = item.lexeme; const main = item.mainModule; const inner = item.module; const moduleDir = main ? this.config.libraries[main] : this.config.libraries[aliasId]; const innerPath = item.innerPath; this.headerImport[aliasId] = {}; // when test.ts import own client.ts if (!moduleDir) { this.headerImport[aliasId].pkgName = '../src/client'; if (innerPath) { const tsPath = innerPath.replace(/(\.tea)$|(\.spec)$|(\.dara)$/gi, ''); innerModule.set(aliasId, `${tsPath}.ts`); this.headerImport[aliasId].pkgName = tsPath; } continue; } let targetPath = ''; if (moduleDir.startsWith('./') || moduleDir.startsWith('../')) { targetPath = path.join(this.config.pkgDir, moduleDir); } else if (moduleDir.startsWith('/')) { targetPath = moduleDir; } else { targetPath = path.join(this.config.pkgDir, lock[moduleDir]); } const pkgPath = fs.existsSync(path.join(targetPath, 'Teafile')) ? path.join(targetPath, 'Teafile') : path.join(targetPath, 'Darafile'); const pkg = JSON.parse(fs.readFileSync(pkgPath)); const tsPkg = pkg.releases && pkg.releases.ts; if (!tsPkg) { throw new Error(`The '${aliasId}' has no TypeScript supported.`); } const [pkgName, version] = tsPkg.split(':'); this.package.dependencies[pkgName] = `${version}`; this.headerImport[aliasId].pkgName = pkgName; if(inner) { this.headerImport[aliasId].inner = inner; } else { this.imports.set(aliasId, pkgName); } const typedef = pkg.ts && pkg.ts.typedef || {}; this.moduleTypedef[aliasId] = typedef; Object.keys(typedef).forEach(type => { if (typedef[type].import && !this.typedefImport.includes(typedef[type].import)) { this.typedefImport.push(typedef[type].import); } if (typedef[type].package) { let [pack, ver] = typedef[type].package.split(':'); if (!this.package.dependencies[pack]) { this.package.dependencies[pack] = ver; } } }); } this.__externModule = usedModels; } } visitTypedef(type, module) { if (module && module.idType === 'module') { const aliasId = _name(module); if (this.moduleTypedef[aliasId] && this.moduleTypedef[aliasId][type]) { let typeInfo = []; if (this.moduleTypedef[aliasId][type].import) { this.moudleUsed.push(this.moduleTypedef[aliasId][type].import); typeInfo.push(this.moduleTypedef[aliasId][type].import); } if (this.moduleTypedef[aliasId][type].type) { typeInfo.push(this.moduleTypedef[aliasId][type].type); } return typeInfo.join('.'); } } if (type.idType === 'typedef' && this.typedef[type.lexeme]) { if (this.typedef[type.lexeme]) { let typeInfo = []; if (this.typedef[type.lexeme].import) { this.moudleUsed.push(this.typedef[type.lexeme].import); typeInfo.push(this.typedef[type.lexeme].import); } if (this.typedef[type.lexeme].type) { typeInfo.push(this.typedef[type.lexeme].type); } return typeInfo.join('.'); } } return _type(type, this.usedTypes); } visitParams(ast, level) { assert.equal(ast.type, 'params'); this.emit('('); for (var i = 0; i < ast.params.length; i++) { if (i !== 0) { this.emit(', '); } const node = ast.params[i]; assert.equal(node.type, 'param'); const name = _name(node.paramName); this.emit(`${avoidReserveName(name)}: `); this.visitType(node.paramType, level); } this.emit(')'); } visitModuleName(aliasId, type) { if(type === 'model') { this.modelUsed.push(aliasId); this.emit(`$${aliasId}.`); return; } this.moudleUsed.push(aliasId); this.emit(`${aliasId}`); } visitType(ast, level) { if (ast.type === 'array') { this.visitType(ast.subType, level); this.emit(`[]`); } else if (ast.type === 'moduleModel') { const [moduleId, ...rest] = ast.path; const modelName = _subModelName(rest.map((item) => item.lexeme).join('.')); this.visitModuleName(moduleId.lexeme, 'model'); this.emit(`${modelName}`); const externEx = this.usedExternException.get(_name(moduleId)); if (externEx && externEx.has(modelName)) { this.emit('Error'); } } else if (ast.type === 'subModel') { const [moduleId, ...rest] = ast.path; const modelName = _subModelName([moduleId.lexeme, ...rest.map((item) => { return item.lexeme; })].join('.')); this.emit(`$_model.${modelName}`); } else if (ast.type === 'moduleTypedef') { const [moduleId, ...rest] = ast.path; const type = rest.map((item) => { return item.lexeme; }).join('.'); this.emit(this.visitTypedef(type, moduleId)); } else if (ast.type === 'typedef' || ast.idType === 'typedef') { this.emit(this.visitTypedef(ast)); } else if (ast.type === 'map') { this.emit(`{[key: `); this.visitType(ast.keyType, level); this.emit(` ]: `); this.visitType(ast.valueType, level); this.emit(`}`); } else if (ast.type === 'model') { let externEx; let type = 'model'; if (ast.moduleName) { externEx = this.usedExternException.get(ast.moduleName); this.visitModuleName(ast.moduleName, 'model'); } if ((externEx && externEx.has(ast.name)) || (this.predefined[ast.name] && this.predefined[ast.name].isException)) { type = 'error'; } if(!ast.moduleName) { this.emit(`$_${type}.`); } this.emit(`${ast.name}`); if(type === 'error') { this.emit('Error'); } } else if(ast.idType === 'model') { let externEx; let type = 'model'; if (ast.moduleName) { externEx = this.usedExternException.get(ast.moduleName); this.visitModuleName(ast.moduleName, 'model'); } if (externEx && externEx.has(ast.lexeme) || (this.predefined[ast.lexeme] && this.predefined[ast.lexeme].isException)) { type = 'error'; } if(!ast.moduleName) { this.emit(`$_${type}.`); } this.emit(`${ast.lexeme}`); if(type === 'error') { this.emit('Error'); } } else if (ast.idType === 'module' || ast.type === 'module') { this.visitModuleName(_name(ast), 'module'); } else if (this.isIterator(ast)) { this.visitType(ast.valueType); } else if (ast.type === 'entry') { this.emit('[string, '); this.visitType(ast.valueType); this.emit(']'); } else if(this.predefined[_name(ast)]) { let type = this.predefined[_name(ast)].isException ? 'error' : 'model'; this.emit(`$_${type}.${_name(ast)}`); if(type === 'error') { this.emit('Error'); } } else { this.emit(_type(_name(ast), this.usedTypes)); } } visitReturnType(ast, level) { this.emit(`: `); if (ast.returnType.type === 'asyncIterator') { this.emit(`AsyncGenerator<`); } else if (ast.isAsync) { this.emit(`Promise<`); } if (ast.returnType.type === 'iterator') { this.emit(`Generator<`); } this.visitType(ast.returnType, level); if (this.isIterator(ast.returnType)) { this.emit(', any, unknown>'); } if (ast.isAsync && ast.returnType.type !== 'asyncIterator') { this.emit(`>`); } this.emit(` `); } visitAPIBody(ast, level) { assert.equal(ast.type, 'apiBody'); this.emit(`let ${REQUEST} = new ${CORE}.Request();\n`, level); this.visitStmts(ast.stmts, level); } visitRuntimeBefore(ast, level) { assert.equal(ast.type, 'object'); this.emit('let _runtime: { [key: string]: any } = ', level); this.visitObject(ast, level); this.emit('\n'); this.emit('\n'); this.emit('let _retriesAttempted = 0;\n', level); this.emit('let _lastRequest = null, _lastResponse = null;\n', level); this.emit(`let _context = new ${CORE}.RetryPolicyContext({\n`, level); this.emit('retriesAttempted: _retriesAttempted,\n', level + 1); this.emit('});\n', level); this.emit(`while (${CORE}.shouldRetry(_runtime['retryOptions'], _context)) {\n`, level); this.emit('if (_retriesAttempted > 0) {\n', level + 1); this.emit(`let _backoffTime = ${CORE}.getBackoffDelay(_runtime['retryOptions'], _context);\n`, level + 2); this.emit('if (_backoffTime > 0) {\n', level + 2); this.emit(`await ${CORE}.sleep(_backoffTime);\n`, level + 3); this.emit('}\n', level + 2); this.emit('}\n', level + 1); this.emit('\n'); this.emit('_retriesAttempted = _retriesAttempted + 1;\n', level + 1); this.emit('try {\n', level + 1); } visitStmt(ast, level) { let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]); this.visitComments(comments, level); if (ast.type === 'return') { this.visitReturn(ast, level); } else if (ast.type === 'yield') { this.visitYield(ast, level); } else if (ast.type === 'if') { this.visitIf(ast, level); } else if (ast.type === 'throw') { this.visitThrow(ast, level); } else if (ast.type === 'assign') { this.visitAssign(ast, level); } else if (ast.type === 'retry') { this.visitRetry(ast, level); } else if (ast.type === 'break') { this.emit(`break;\n`, level); } else if (ast.type === 'declare') { this.visitDeclare(ast, level); } else if (ast.type === 'while') { this.visitWhile(ast, level); } else if (ast.type === 'for') { this.visitFor(ast, level); } else if (ast.type === 'try') { this.visitTry(ast, level); } else { this.emit(``, level); this.visitExpr(ast, level); this.emit(';\n'); } } visitFieldType(value, level, modelName, fieldName) { if (value.type === 'modelBody') { const subModelName = _subModelName([modelName, fieldName].join('.')); this.subModelUsed.push(subModelName); this.emit(subModelName); } else if (value.type === 'array') { this.visitFieldType(value.subType, level, modelName, fieldName); this.emit(`[]`); } else if (value.fieldType === 'array') { this.visitFieldType(value.fieldItemType, level, modelName, fieldName); this.emit(`[]`); } else if (value.fieldType === 'map') { this.emit(`{ [key: ${value.keyType.lexeme}]: `); this.visitFieldType(value.valueType); this.emit(` }`); } else if (value.type === 'map') { this.emit(`{ [key: ${value.keyType.lexeme}]: `); this.visitFieldType(value.valueType); this.emit(` }`); } else if (value.tag === Tag.TYPE) { this.emit(`${_type(value.lexeme, this.usedTypes)}`); } else if (value.tag === Tag.ID) { if(this.predefined[value.lexeme]) { const arr = this.predefined[value.lexeme].isException ? this.exceptionUsed : this.subModelUsed; arr.push(value.lexeme); } this.emit(`${value.lexeme}`); } else if (value.type === 'moduleModel') { const [moduleId, ...models] = value.path; const modelName = _subModelName(models.map((item) => item.lexeme).join('.')); this.visitModuleName(moduleId.lexeme, 'model'); this.emit(`${modelName}`); const externEx = this.usedExternException.get(_name(moduleId.lexeme)); if (externEx && externEx.has(modelName)) { this.emit('Error'); } } else if (value.type === 'subModel') { const [moduleId, ...rest] = value.path; const subModelName = _subModelName([moduleId.lexeme, ...rest.map((item) => { return item.lexeme; })].join('.')); this.subModelUsed.push(subModelName); this.emit(subModelName); } else if (typeof value.fieldType === 'string') { this.emit(`${_type(value.fieldType, this.usedTypes)}`); } else if (value.fieldType.type === 'moduleModel') { const [moduleId, ...models] = value.fieldType.path; const modelName = _subModelName(models.map((item) => item.lexeme).join('.')); this.visitModuleName(moduleId.lexeme, 'model'); this.emit(`${modelName}`); const externEx = this.usedExternException.get(_name(moduleId.lexeme)); if (externEx && externEx.has(modelName)) { this.emit('Error'); } } else if (value.fieldType.type === 'moduleTypedef') { const [moduleId, ...rest] = value.fieldType.path; const type = rest.map((item) => { return item.lexeme; }).join('.'); this.emit(this.visitTypedef(type, moduleId)); } else if (value.fieldType.type === 'typedef' || value.fieldType.idType === 'typedef') { this.emit(this.visitTypedef(value.fieldType)); } else if (value.fieldType.type) { this.emit(`${_type(value.fieldType.lexeme, this.usedTypes)}`); } else if (value.fieldType.idType === 'model') { let arr = this.subModelUsed; let type = _type(value.fieldType.lexeme, this.usedTypes); if (this.predefined[type] && this.predefined[type].isException) { type += 'Error'; arr = this.exceptionUsed; } arr.push(type); this.emit(type); } else if (value.fieldType.idType === 'module') { this.visitModuleName(_type(value.fieldType.lexeme, this.usedTypes), 'moudle'); } else if (value.fieldType.idType === 'builtin_model') { this.emit(`${_type(value.fieldType.lexeme, this.usedTypes)}`); } } visitFieldTypeString(value, level, modelName, fieldName) { if (value.type === 'modelBody') { this.emit(_subModelName([modelName, fieldName].join('.'))); } else if (value.type === 'array') { this.emit(`{ 'type': 'array', 'itemType': `); this.visitFieldTypeString(value.subType, level, modelName, fieldName); this.emit(` }`); } else if (value.fieldType === 'array') { this.emit(`{ 'type': 'array', 'itemType': `); this.visitFieldTypeString(value.fieldItemType, level, modelName, fieldName); this.emit(` }`); } else if (value.fieldType === 'map') { this.emit(`{ 'type': 'map', 'keyType': '${value.keyType.lexeme}', 'valueType': `); this.visitFieldTypeString(value.valueType); this.emit(` }`); } else if (value.type === 'map') { this.emit(`{ 'type': 'map', 'keyType': '${value.keyType.lexeme}', 'valueType': `); this.visitFieldTypeString(value.valueType); this.emit(` }`); } else if (value.fieldType === 'object') { this.emit(`{ 'type': 'map', 'keyType': 'string', 'valueType': 'any' }`); } else if (value.tag === Tag.TYPE) { this.emit(`'${_type(value.lexeme)}'`); } else if (value.tag === Tag.ID) { this.emit(`${_type(value.lexeme)}`); } else if (value.type === 'moduleModel') { const [moduleId, ...models] = value.path; const modelName = _subModelName(models.map((item) => item.lexeme).join('.')); this.emit(`$${moduleId.lexeme}.${modelName}`); const externEx = this.usedExternException.get(_name(moduleId.lexeme)); if (externEx && externEx.has(modelName)) { this.emit('Error'); } } else if (value.type === 'subModel') { const [moduleId, ...rest] = value.path; this.emit(_subModelName([moduleId.lexeme, ...rest.map((item) => { return item.lexeme; })].join('.'))); } else if (typeof value.fieldType === 'string') { this.emit(`'${_type(value.fieldType)}'`); } else if (value.fieldType.type === 'moduleModel') { const [moduleId, ...models] = value.fieldType.path; const modelName = _subModelName(models.map((item) => item.lexeme).join('.')); this.emit(`$${moduleId.lexeme}.${modelName}`); const externEx = this.usedExternException.get(_name(moduleId.lexeme)); if (externEx && externEx.has(modelName)) { this.emit('Error'); } } else if (value.fieldType.type === 'moduleTypedef') { const [moduleId, ...rest] = value.fieldType.path; const type = rest.map((item) => { return item.lexeme; }).join('.'); this.emit(this.visitTypedef(type, moduleId)); } else if (value.fieldType.type === 'typedef' || value.fieldType.idType === 'typedef') { let type = this.visitTypedef(value.fieldType); if(DSL.util.isBasicType(type)) { type = `'${_type(type)}'`; } this.emit(type); } else if (value.fieldType.type) { this.emit(`${_type(value.fieldType.lexeme)}`); } else if (value.fieldType.idType === 'model') { const type = _type(value.fieldType.lexeme); if (this.predefined[_name(type)] && this.predefined[_name(type)].isException) { return `${_name(type)}Error`; } this.emit(type); } else if (value.fieldType.idType === 'module') { this.emit(`${_type(value.fieldType.lexeme)}`); } else if (value.fieldType.idType === 'builtin_model') { this.emit(`${_type(value.fieldType.lexeme)}`); } } visitModelBody(ast, level, modelName) { assert.equal(ast.type, 'modelBody'); let node; for (let i = 0; i < ast.nodes.length; i++) { node = ast.nodes[i]; let comments = DSL.comment.getFrontComments(this.comments, node.tokenRange[0]); const description = getAttr(node, 'description'); const example = getAttr(node, 'example'); const checkBlank = getAttr(node, 'checkBlank'); const nullable = getAttr(node, 'nullable'); const sensitive = getAttr(node, 'sensitive'); const deprecated = getAttr(node, 'deprecated'); let hasNextSection = false; if (deprecated === 'true' || description || example || typeof checkBlank !== 'undefined' || typeof nullable !== 'undefined' || typeof sensitive !== 'undefined') { this.emit('/**\n', level); if (description) { this.emit(' * @remarks\n', level); const descriptions = description.split('\n'); for (let j = 0; j < descriptions.length; j++) { if (descriptions[j] === '') { this.emit(` * \n`, level); } else { this.emit(` * ${descriptions[j]}\n`, level); } } hasNextSection = true; } if (example) { if (hasNextSection) { this.emit(` * \n`, level); } const examples = example.split('\n'); this.emit(' * @example\n', level); for (let j = 0; j < examples.length; j++) { if (examples[j] === '') { this.emit(` * \n`, level); } else { this.emit(` * ${examples[j]}\n`, level); } } hasNextSection = true; } if (typeof checkBlank !== 'undefined') { if (hasNextSection) { this.emit(` * \n`, level); } this.emit(' * **check if is blank:**\n', level); this.emit(` * ${checkBlank}\n`, level); hasNextSection = true; } if (typeof nullable !== 'undefined') { if (hasNextSection) { this.emit(` * \n`, level); } this.emit(' * **if can be null:**\n', level); this.emit(` * ${nullable}\n`, level); hasNextSection = true; } if (typeof sensitive !== 'undefined') { if (hasNextSection) { this.emit(` * \n`, level); } this.emit(' * **if sensitive:**\n', level); this.emit(` * ${sensitive}\n`, level); hasNextSection = true; } if (deprecated === 'true') { if (hasNextSection) { this.emit(` * \n`, level); } this.emit(` * @deprecated\n`, level); } this.emit(' */\n', level); } this.visitComments(comments, level); this.emit(`${_escape(_name(node.fieldName))}${node.required ? '' : '?'}: `, level); this.visitFieldType(node.fieldValue, level, modelName, _name(node.fieldName)); this.emit(';\n'); } if (node) { //find the last node's back comment let comments = DSL.comment.getBetweenComments(this.comments, node.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level); } if (ast.nodes.length === 0) { //empty block's comment let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level); } this.emit(`static names(): { [key: string]: string } {\n`, level); this.emit(`return {\n`, level + 1); for (let i = 0; i < ast.nodes.length; i++) { const node = ast.nodes[i]; const nameAttr = node.attrs.find((item) => { return item.attrName.lexeme === 'name'; }); if (nameAttr) { this.emit(`${_escape(_name(node.fieldName))}: '${_string(nameAttr.attrValue)}',\n`, level + 2); } else { this.emit(`${_escape(_name(node.fieldName))}: '${_name(node.fieldName)}',\n`, level + 2); } } this.emit(`};\n`, level + 1); this.emit(`}\n`, level); this.emit('\n'); this.emit(`static types(): { [key: string]: any } {\n`, level); this.emit(`return {\n`, level + 1); for (let i = 0; i < ast.nodes.length; i++) { const node = ast.nodes[i]; this.emit(`${_escape(_name(node.fieldName))}: `, level + 2); this.visitFieldTypeString(node.fieldValue, level + 2, modelName, _name(node.fieldName)); this.emit(',\n'); } this.emit(`};\n`, level + 1); this.emit(`}\n`, level); this.emit('\n'); this.emit(`validate() {\n`, level); this.visitModelValidate(ast, level + 1); this.emit(`}\n`, level); } getAttributes(ast, name){ const attr = ast.attrs.find((item) => { return item.attrName.lexeme === name; }); if(!attr) { return; } return attr.attrValue.string || attr.attrValue.lexeme || attr.attrValue.value; } visitFieldValidate(value, level, name) { if (value.type === 'array' || value.fieldType === 'array') { this.emit(`if(Array.isArray(${name})) {\n`, level); this.emit(`$dara.Model.validateArray(${name});\n`, level + 1); this.emit('}\n', level); } else if (value.fieldType === 'map' || value.type === 'map') { this.emit(`if(${name}) {\n`, level); this.emit(`$dara.Model.validateMap(${name});\n`, level + 1); this.emit('}\n', level); } else if (value.type === 'moduleModel' || value.type === 'modelBody' || value.type === 'subModel' || value.fieldType.type === 'moduleModel' || value.fieldType.idType === 'model' || value.fieldType.idType === 'module') { this.emit(`if(${name} && typeof (${name} as any).validate === 'function') {\n`, level); this.emit(`(${name} as any).validate();\n`, level + 1); this.emit('}\n', level); } } visitModelValidate(ast, level) { for (let i = 0; i < ast.nodes.length; i++) { const node = ast.nodes[i]; this.visitFieldValidate(node.fieldValue, level, `this.${_escape(_name(node.fieldName))}`); const attrName = _name(node.fieldName); const pattern = this.getAttributes(node, 'pattern') || ''; const maxLength = this.getAttributes(node, 'maxLength') || 0; const minLength = this.getAttributes(node, 'minLength') || 0; const maximum = this.getAttributes(node, 'maximum') || 0; const minimum = this.getAttributes(node, 'minimum') || 0; const required = node.required || false; if (required || maxLength > 0 || minLength > 0 || maximum > 0 || pattern !== '') { if (required) { this.emit(`$dara.Model.validateRequired("${attrName}", this.${attrName});\n`, level); } if (pattern !== '') { this.emit(`$dara.Model.validatePattern("${attrName}", this.${attrName}, "${pattern}");\n`, level); } if (maxLength > 0 && maxLength <= 2147483647) { this.emit(`$dara.Model.validateMaxLength("${attrName}", this.${attrName}, ${maxLength});\n`, level); } if (minLength > 0 && minLength <= 2147483647) { this.emit(`$dara.Model.validateMinLength("${attrName}", this.${attrName}, ${minLength});\n`, level); } // 不能超过JS中最大安全整数 if (maximum > 0 && maximum <= Number.MAX_SAFE_INTEGER) { this.emit(`$dara.Model.validateMaximum("${attrName}", this.${attrName}, ${maximum});\n`, level); } // 不能超过JS中最大安全整数 if (minimum > 0 && minimum <= Number.MAX_SAFE_INTEGER) { this.emit(`$dara.Model.validateMinimum("${attrName}", this.${attrName}, ${minimum});\n`, level); } } } this.emit('super.validate();\n', level); } visitExtendOn(extendOn, type = 'model') { if (!extendOn) { return type === 'model' ? this.emit(`${CORE}.Model`) : this.emit(`${CORE}.BaseError`); } switch(_name(extendOn)) { case '$Error': this.emit(`${CORE}.BaseError`); return; case '$ResponseError': this.emit(`${CORE}.ResponseError`); return; case '$Model': this.emit(`${CORE}.Model`); return; } let emitName = ''; if (extendOn.type === 'moduleModel') { const [moduleId, ...rest] = extendOn.path; this.visitModuleName(moduleId.lexeme, 'model'); this.emit(_subModelName(rest.map((item) => { return item.lexeme; }).join('.'))); } else if (extendOn.type === 'subModel') { const [moduleId, ...rest] = extendOn.path; emitName = _subModelName([moduleId.lexeme, ...rest.map((item) => { return item.lexeme; })].join('.')); this.emit(emitName); } else { const modelName = _upperFirst(_name(extendOn)); if (extendOn.moduleName) { this.visitModuleName(extendOn.moduleName, 'model'); } else { emitName = modelName; } this.emit(modelName); } if(type === 'model' && emitName) { this.subModelUsed.push(emitName); } if(type === 'exception') { if(emitName) { this.exceptionUsed.push(emitName); } this.emit('Error'); } } visitModelIndex(models) { if(models.length === 0) { return; } this.hasModel = true; for (let i = 0; i < models.length; i++) { const ast = models[i]; const modelName = _subModelName(_name(ast.modelName)); this.emit(`export { ${modelName} } from './${_avoidModel(_camelCase(_snakeCase(modelName)))}';\n`); } this.save(path.join(this.modelPath, 'model.ts'), true); } visitModel(modelBody, modelName, extendOn, level) { this.emit(`export class ${modelName} extends `, level); this.visitExtendOn(extendOn); this.emit(' {\n'); this.visitModelBody(modelBody, level + 1, modelName); this.emit(`\n`); this.emit(`constructor(map?: { [key: string]: any }) {\n`, level + 1); this.emit(`super(map);\n`, level + 2); this.emit(`}\n`, level + 1); this.emit('}\n\n', level); this.subModelUsed = this.subModelUsed.filter(name => { return name !== modelName; }); this.saveBuffer(path.join(this.modelPath, `${_avoidModel(_camelCase(_snakeCase(modelName)))}.ts`)); this.usedTypes = []; this.modelUsed = []; this.moudleUsed = []; this.exceptionUsed = []; this.subModelUsed = []; this.output = ''; } saveBuffer(filepath) { if(this.fileBuffer[filepath]) { this.fileBuffer[filepath].moudleUsed = this.moudleUsed.concat(this.fileBuffer[filepath].moudleUsed); this.fileBuffer[filepath].modelUsed = this.modelUsed.concat(this.fileBuffer[filepath].modelUsed); this.fileBuffer[filepath].usedTypes = this.usedTypes.concat(this.fileBuffer[filepath].usedTypes); this.fileBuffer[filepath].subModelUsed = this.subModelUsed.concat(this.fileBuffer[filepath].subModelUsed); this.fileBuffer[filepath].exceptionUsed = this.exceptionUsed.concat(this.fileBuffer[filepath].exceptionUsed); this.fileBuffer[filepath].output = this.output + this.fileBuffer[filepath].output; return; } this.fileBuffer[filepath] = { modelUsed: this.modelUsed, moudleUsed: this.moudleUsed, usedTypes: this.usedTypes, output: this.output, subModelUsed: this.subModelUsed, exceptionUsed: this.exceptionUsed, }; } flushBuffer() { Object.keys(this.fileBuffer).map(filepath => { this.output = this.fileBuffer[filepath].output; this.moudleUsed = this.fileBuffer[filepath].moudleUsed; this.modelUsed = this.fileBuffer[filepath].modelUsed; this.usedTypes = this.fileBuffer[filepath].usedTypes; this.exceptionUsed = this.fileBuffer[filepath].exceptionUsed; this.subModelUsed = this.fileBuffer[filepath].subModelUsed; this.save(filepath); this.output = ''; this.moudleUsed = []; this.modelUsed = []; this.usedTypes = []; this.exceptionUsed = []; this.subModelUsed = []; }); } eachModel(ast, level) { assert.equal(ast.type, 'model'); const modelName = _upperFirst(_name(ast.modelName)); this.visitAnnotation(ast.annotation, level); let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]); this.visitComments(comments, level); this.visitModel(ast.modelBody, modelName, ast.extendOn, level); } visitEcxceptionBody(ast, level, ExceptionName) { assert.equal(ast.type, 'exceptionBody'); let node; for (let i = 0; i < ast.nodes.length; i++) { node = ast.nodes[i]; const fieldName = _escape(_name(node.fieldName)); if(fieldName === 'message' || fieldName === 'name' || fieldName === 'code') { node.required = true; } let comments = DSL.comment.getFrontComments(this.comments, node.tokenRange[0]); this.visitComments(comments, level); this.emit(`${fieldName}${node.required ? '' : '?'}: `, level); this.visitFieldType(node.fieldValue, level, ExceptionName, _name(node.fieldName)); this.emit(';\n'); } if (node) { //find the last node's back comment let comments = DSL.comment.getBetweenComments(this.comments, node.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level); } if (ast.nodes.length === 0) { //empty block's comment let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level); } } visitEcxceptionConstrutor(ast, exceptionName, level) { assert.equal(ast.type, 'exceptionBody'); let node; for (let i = 0; i < ast.nodes.length; i++) { node = ast.nodes[i]; const fieldName = _escape(_name(node.fieldName)); if(fieldName === 'message') { continue; } if(fieldName === 'name') { continue; } this.emit(`this.${fieldName} = map.${fieldName};\n`, level); } } visitException(exceptionBody, exceptionName, extendOn, level) { this.emit(`export class ${exceptionName}Error extends `, level); this.visitExtendOn(extendOn, 'exception'); this.emit(' {\n'); this.visitEcxceptionBody(exceptionBody, level + 1, exceptionName); this.emit(`\n`); this.emit(`constructor(map?: { [key: string]: any }) {\n`, level + 1); this.emit(`super(map);\n`, level + 2); this.emit(`this.name = "${exceptionName}Error";\n`, level + 2); this.emit(`Object.setPrototypeOf(this, ${exceptionName}Error.prototype);\n`, level + 2); this.visitEcxceptionConstrutor(exceptionBody, exceptionName, level + 2); this.emit(`}\n`, level + 1); this.emit('}\n\n', level); this.exceptionUsed = this.exceptionUsed.filter(name => { return name !== exceptionName; }); this.saveBuffer(path.join(this.exceptionPath, `${_camelCase(_snakeCase(exceptionName))}Error.ts`)); this.usedTypes = []; this.modelUsed = []; this.moudleUsed = []; this.exceptionUsed = []; this.subModelUsed = []; this.output = ''; } visitExceptionIndex(exceptions) { if(exceptions.length === 0) { return; } this.hasException = true; for (let i = 0; i < exceptions.length; i++) { const ast = exceptions[i]; const exceptionName = _subModelName(_name(ast.exceptionName)); this.emit(`export { ${exceptionName}Error } from './${_camelCase(_snakeCase(exceptionName))}Error';\n`); } this.save(path.join(this.exceptionPath, 'error.ts'), true); } eachException(ast, level) { assert.equal(ast.type, 'exception'); const exceptionName = _upperFirst(_name(ast.exceptionName)); this.visitAnnotation(ast.annotation, level); let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]); this.visitComments(comments, level); this.visitException(ast.exceptionBody, exceptionName, ast.extendOn, level); } eachSubModel(ast, level) { assert.equal(ast.type, 'model'); const modelName = _subModelName(_name(ast.modelName)); this.visitModel(ast.modelBody, modelName, ast.extendOn, level); } visitObjectFieldValue(ast, level) { this.visitExpr(ast, level); } visitObjectField(ast, level) { let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]); this.visitComments(comments, level); if (ast.type === 'objectField') { var key = _escape(_name(ast.fieldName) || _string(ast.fieldName)); this.emit(`${key}: `, level); this.visitObjectFieldValue(ast.expr, level); } else if (ast.type === 'expandField') { // TODO: more cases this.emit('...', level); this.visitExpr(ast.expr, level); } else { throw new Error('unimpelemented'); } this.emit(',\n'); } visitObject(ast, level) { assert.equal(ast.type, 'object'); if (ast.fields.length === 0) { this.emit('{ '); let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]); if (comments.length > 0) { this.emit('\n'); this.visitComments(comments, level + 1); this.emit('', level); } this.emit('}'); } else { this.emit('{\n'); for (var i = 0; i < ast.fields.length; i++) { this.visitObjectField(ast.fields[i], level + 1); } //find the last item's back comment let comments = DSL.comment.getBetweenComments(this.comments, ast.fields[i - 1].tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level + 1); this.emit('}', level); } } visitMethodCall(ast, level) { assert.equal(ast.left.type, 'method_call'); if (ast.isAsync) { this.emit(`await `); } const name = _name(ast.left.id); if (name.startsWith('$') && this.builtin[name]) { const method = name.replace('$', ''); this.builtin[name][method](ast.args, level); return; } else if (ast.isStatic) { this.emit(`Client.${name}`); } else { this.emit(`this.${name}`); } this.visitArgs(ast.args, level); } visitInstanceCall(ast, level) { assert.equal(ast.left.type, 'instance_call'); const method = _name(ast.left.propertyPath[0]); if (ast.isAsync) { this.emit(`await `); } if (ast.builtinModule && this.builtin[ast.builtinModule] && this.builtin[ast.builtinModule][method]) { this.builtin[ast.builtinModule][method](ast, level); } else { if (ast.left.id.tag === DSL.Tag.Tag.VID) { this.emit(`this.${_vid(ast.left.id)}`); } else { this.emit(`${_name(ast.left.id)}`); } this.emit(`.${(method)}`); this.visitArgs(ast.args, level); } } visitStaticCall(ast, level) { assert.equal(ast.left.type, 'static_call'); if (ast.isAsync) { this.emit(`await `); } if (ast.left.id.type === 'builtin_module') { this.visitBuiltinStaticCall(ast); return; } this.visitModuleName(_name(ast.left.id), 'moudle'); this.emit(`.${_name(ast.left.propertyPath[0])}`); this.visitArgs(ast.args, level); } visitBuiltinStaticCall(ast) { const moduleName = _name(ast.left.id); const builtiner = this.builtin[moduleName]; if (!builtiner) { throw new Error('un-implemented'); } const func = _name(ast.left.propertyPath[0]); builtiner[func](ast.args); } visitCall(ast, level) { assert.equal(ast.type, 'call'); if (ast.left.type === 'method_call') { this.visitMethodCall(ast, level); } else if (ast.left.type === 'instance_call') { this.visitInstanceCall(ast, level); } else if (ast.left.type === 'static_call') { this.visitStaticCall(ast, level); } else { console.log(ast.left.propertyPath); throw new Error('un-implemented'); } } visitConstruct(ast, level) { assert.equal(ast.type, 'construct'); this.emit('new '); this.visitModuleName(_type(ast.aliasId.lexeme, this.usedTypes), 'moudle'); this.visitArgs(ast.args, level); } visitSuper(ast, level) { assert.equal(ast.type, 'super'); this.emit(`super`); this.visitArgs(ast.args, level); } visitArgs(args, level) { this.emit('('); for (let i = 0; i < args.length; i++) { const expr = args[i]; if (expr.needCast) { this.emit('$dara.toMap('); this.visitExpr(expr, level); this.emit(')'); } else { this.visitExpr(expr, level); } if (i !== args.length - 1) { this.emit(', '); } } this.emit(')'); } visitPropertyAccess(ast) { assert.ok(ast.type === 'property_access' || ast.type === 'property'); var id = _name(ast.id); if (ast.id.tag === Tag.VID) { id = `this.${_vid(ast.id)}`; } var expr = avoidReserveName(id); var current = ast.id.inferred; for (var i = 0; i < ast.propertyPath.length; i++) { var name = _name(ast.propertyPath[i]); if (current.type === 'model') { expr += `.${name}`; } else { expr += `["${name}"]`; } current = ast.propertyPathTypes[i]; } this.emit(expr); } visitExpr(ast, level) { if (ast.type === 'boolean') { this.emit(ast.value); } else if (ast.type === 'property_access') { this.visitPropertyAccess(ast); } else if (ast.type === 'string') { this.emit(`"${_string(ast.value)}"`); } else if (ast.type === 'number') { this.emit(ast.value.value); } else if (ast.type === 'null') { this.emit(`null`); } else if (ast.type === 'object') { this.visitObject(ast, level); } else if (ast.type === 'variable') { if(ast.inferred && ast.inferred.type === 'basic' && ast.inferred.name === 'class' && ast.id.type === 'model') { let type = 'model'; let name = _name(ast.id); if (this.predefined[name] && this.predefined[name].isException) { type = 'error'; } if(!name.startsWith('$') && this.predefined[name]) { this.emit(`$_${type}.`); } this.emit(_type(name)); } else { this.emit(avoidReserveName(_name(ast.id))); } } else if (ast.type === 'virtualVariable') { this.emit(`this.${_vid(ast.vid)}`); } else if (ast.type === 'decrement') { if (ast.position === 'front') { this.emit('--'); } this.visitExpr(ast.expr, level); if (ast.position === 'backend') { this.emit('--'); } } else if (ast.type === 'increment') { if (ast.position === 'front') { this.emit('++'); } this.visitExpr(ast.expr, level); if (ast.position === 'backend') { this.emit('++'); } } else if (ast.type === 'template_string') { this.emit('`'); for (var i = 0; i < ast.elements.length; i++) { var item = ast.elements[i]; if (item.type === 'element') { this.emit(_string(item.value)); } else if (item.type === 'expr') { this.emit('${'); this.visitExpr(item.expr, level); this.emit('}'); } else { throw new Error('unimpelemented'); } } this.emit('`'); } else if (ast.type === 'call') { this.visitCall(ast, level); } else if (ast.type === 'construct') { this.visitConstruct(ast, level); } else if (ast.type === 'array') { this.visitArray(ast, level); } else if (ast.type === 'group') { this.emit('('); this.visitExpr(ast.expr, level); this.emit(')'); } else if (_isBinaryOp(ast.type)) { this.visitExpr(ast.left, level); if (ast.type === 'or') { this.emit(' || '); } else if (ast.type === 'add') { this.emit(' + '); } else if (ast.type === 'subtract') { this.emit(' - '); } else if (ast.type === 'div') { this.emit(' / '); } else if (ast.type === 'multi') { this.emit(' * '); } else if (ast.type === 'and') { this.emit(' && '); } else if (ast.type === 'or') { this.emit(' || '); } else if (ast.type === 'lte') { this.emit(' <= '); } else if (ast.type === 'lt') { this.emit(' < '); } else if (ast.type === 'gte') { this.emit(' >= '); } else if (ast.type === 'gt') { this.emit(' > '); } else if (ast.type === 'neq') { this.emit(' != '); } else if (ast.type === 'eq') { this.emit(' == '); } this.visitExpr(ast.right, level); } else if (ast.type === 'group') { this.emit('('); this.visitExpr(ast.expr, level); this.emit('('); } else if (ast.type === 'not') { this.emit('!'); this.visitExpr(ast.expr, level); } else if (ast.type === 'construct_model') { this.visitConstructModel(ast, level); } else if (ast.type === 'map_access') { this.visitMapAccess(ast); } else if (ast.type === 'array_access') { this.visitArrayAccess(ast); } else if (ast.type === 'super') { this.visitSuper(ast, level); } else { throw new Error('unimpelemented'); } } visitConstructModel(ast, level) { assert.equal(ast.type, 'construct_model'); if (ast.aliasId.isModule) { let moduleName = ast.aliasId.lexeme; let modelName = _subModelName(ast.propertyPath.map((item) => { return item.lexeme; }).join('.')); this.emit('new '); this.visitModuleName(moduleName, 'model'); this.emit(modelName); const externEx = this.usedExternException.get(moduleName); if (externEx && externEx.has(modelName)) { this.emit('Error'); } } if (ast.aliasId.isModel) { let mainModelName = ast.aliasId.lexeme; let type = 'model'; this.emit(`new `); mainModelName = _subModelName([mainModelName, ...ast.propertyPath.map((item) => { return item.lexeme; })].join('.')); if(mainModelName.startsWith('$') && !this.predefined[mainModelName]) { this.emit(`${_type(mainModelName, this.usedTypes)}`); } else { if (this.predefined[mainModelName] && this.predefined[mainModelName].isException) { type = 'error'; } this.emit(`$_${type}.${_type(mainModelName, this.usedTypes)}`); if(type === 'error') { this.emit('Error'); } } } this.emit(`(`); if (ast.object) { this.visitObject(ast.object, level); } else { this.emit(`{ }`); } this.emit(')'); } visitMapAccess(ast, level) { assert.equal(ast.type, 'map_access'); let expr; if (ast.id.tag === DSL.Tag.Tag.VID) { expr = `this.${_vid(ast.id)}`; } else { expr = `${_name(ast.id)}`; } if (ast.propertyPath && ast.propertyPath.length) { var current = ast.id.inferred; for (var i = 0; i < ast.propertyPath.length; i++) { var name = _name(ast.propertyPath[i]); if (current.type === 'model') { expr += `.${name}`; } else { expr += `["${name}"]`; } current = ast.propertyPathTypes[i]; } } this.emit(`${expr}[`, level); this.visitExpr(ast.accessKey, level); this.emit(`]`); } visitArrayAccess(ast, level) { assert.equal(ast.type, 'array_access'); let expr; if (ast.id.tag === DSL.Tag.Tag.VID) { expr = `this.${_vid(ast.id)}`; } else { expr = `${_name(ast.id)}`; } if (ast.propertyPath && ast.propertyPath.length) { var current = ast.id.inferred; for (var i = 0; i < ast.propertyPath.length; i++) { var name = _name(ast.propertyPath[i]); if (current.type === 'model') { expr += `.${name}`; } else { expr += `["${name}"]`; } current = ast.propertyPathTypes[i]; } } this.emit(`${expr}[`, level); this.visitExpr(ast.accessKey, level); this.emit(`]`); } visitArray(ast, level) { assert.equal(ast.type, 'array'); let arrayComments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]); if (ast.items.length === 0) { this.emit('[ '); if (arrayComments.length > 0) { this.emit('\n'); this.visitComments(arrayComments, level + 1); this.emit('', level); } this.emit(']'); return; } this.emit('[\n'); let item; for (let i = 0; i < ast.items.length; i++) { item = ast.items[i]; let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]); this.visitComments(comments, level + 1); this.emit('', level + 1); this.visitExpr(item, level + 1); if (i < ast.items.length - 1) { this.emit(','); } this.emit('\n'); } if (item) { //find the last item's back comment let comments = DSL.comment.getBetweenComments(this.comments, item.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level + 1); } this.emit(']', level); } visitYield(ast, level) { assert.equal(ast.type, 'yield'); this.emit('yield ', level); if (!ast.expr) { this.emit(';\n'); return; } if (ast.needCast) { this.emit(`${CORE}.cast<`); this.visitType(ast.expectedType); this.emit(`>(`); } this.visitExpr(ast.expr, level); if (ast.needCast) { this.emit(`, new `); this.visitType(ast.expectedType); this.emit('({}))'); } this.emit(';\n'); } visitReturn(ast, level) { assert.equal(ast.type, 'return'); this.emit('return ', level); if (!ast.expr) { this.emit(';\n'); return; } if (ast.needCast) { this.emit(`${CORE}.cast<`); this.visitType(ast.expectedType); this.emit(`>(`); } this.visitExpr(ast.expr, level); if (ast.needCast) { this.emit(`, new `); this.visitType(ast.expectedType); this.emit('({}))'); } this.emit(';\n'); } visitRetry(ast, level) { assert.equal(ast.type, 'retry'); this.emit(`throw $dara.newRetryError(${REQUEST}, ${RESPONSE});\n`, level); } visitTry(ast, level) { assert.equal(ast.type, 'try'); this.emit('try {\n', level); this.visitStmts(ast.tryBlock, level + 1); this.emit('}', level); if (ast.catchBlocks && ast.catchBlocks.length > 0) { this.emit(` catch (__err) {\n`); ast.catchBlocks.forEach(catchBlock => { if (!catchBlock.id) { return; } if (!catchBlock.id.type) { this.emit(`if (__err instanceof ${CORE}.BaseError) {\n`, level + 1); } else { this.emit(`if (__err instanceof `, level + 1); this.visitType(catchBlock.id.type); this.emit(') {\n'); } this.emit(`const ${_name(catchBlock.id)} = __err;\n`, level + 2); this.visitStmts(catchBlock.catchStmts, level + 2); this.emit('}\n', level + 1); }); this.emit('}', level); } else if (ast.catchBlock && ast.catchBlock.stmts.length > 0) { this.emit(` catch (${_name(ast.catchId)}) {\n`); this.visitStmts(ast.catchBlock, level + 1); this.emit('}', level); } if (ast.finallyBlock && ast.finallyBlock.stmts.length > 0) { this.emit(' finally {\n'); this.visitStmts(ast.finallyBlock, level + 1); this.emit('}', level); } this.emit('\n', level); } visitWhile(ast, level) { assert.equal(ast.type, 'while'); this.emit('\n'); this.emit('while (', level); this.visitExpr(ast.condition, level + 1); this.emit(') {\n'); this.visitStmts(ast.stmts, level + 1); this.emit('}\n', level); } visitFor(ast, level) { assert.equal(ast.type, 'for'); this.emit('\n'); this.emit('for', level); if (ast.list.inferred.type === 'asyncIterator') { this.emit(' await '); } this.emit(`(let ${_name(ast.id)} of `); this.visitExpr(ast.list, level + 1); this.emit(') {\n'); this.visitStmts(ast.stmts, level + 1); this.emit('}\n', level); } visitIf(ast, level) { assert.equal(ast.type, 'if'); this.emit('if (', level); this.visitExpr(ast.condition, level + 1); this.emit(') {\n'); this.visitStmts(ast.stmts, level + 1); this.emit('}', level); if (ast.elseIfs) { for (let i = 0; i < ast.elseIfs.length; i++) { const branch = ast.elseIfs[i]; this.emit(' else if ('); this.visitExpr(branch.condition, level + 1); this.emit(') {\n'); this.visitStmts(branch.stmts, level + 1); this.emit('}', level); } } if (ast.elseStmts) { this.emit(' else {\n'); for (let i = 0; i < ast.elseStmts.stmts.length; i++) { this.visitStmt(ast.elseStmts.stmts[i], level + 1); } if (ast.elseStmts.stmts.length === 0) { const comments = DSL.comment.getBetweenComments(this.comments, ast.elseStmts.tokenRange[0], ast.elseStmts.tokenRange[1]); this.visitComments(comments, level + 1); } this.emit('}', level); } this.emit('\n'); this.emit('\n'); } visitThrow(ast, level) { this.emit('throw ', level); if (ast.expr.type === 'construct_model') { this.visitConstructModel(ast.expr, level); this.emit(';\n'); } else { this.emit(`${CORE}.newError(`); this.visitObject(ast.expr, level); this.emit(');\n'); } } visitAssign(ast, level) { if (ast.left.type === 'property_assign' || ast.left.type === 'property') { this.emit(``, level); this.visitPropertyAccess(ast.left); } else if (ast.left.type === 'virtualVariable') { // vid this.emit(`this.${_vid(ast.left.vid)}`, level); } else if (ast.left.type === 'variable') { this.emit(`${_name(ast.left.id)}`, level); } else if (ast.left.type === 'map_access') { this.visitMapAccess(ast.left, level); } else if (ast.left.type === 'array_access') { this.visitArrayAccess(ast.left, level); } else { throw new Error('unimpelemented'); } this.emit(' = '); if (ast.expr.needToReadable) { this.emit(`new ${CORE}.BytesReadable(`); } this.visitExpr(ast.expr, level); if (ast.expr.needToReadable) { this.emit(`)`); } this.emit(';\n'); } visitDeclare(ast, level) { var id = _name(ast.id); this.emit(`let ${id}`, level); if (ast.expectedType && !this.isIterator(ast.expectedType)) { this.emit(` : `); this.visitType(ast.expectedType, level); } this.emit(` = `); this.visitExpr(ast.expr, level); this.emit(';\n'); } visitStmts(ast, level) { assert.equal(ast.type, 'stmts'); let node; for (var i = 0; i < ast.stmts.length; i++) { node = ast.stmts[i]; this.visitStmt(node, level); } if (node) { //find the last node's back comment let comments = DSL.comment.getBackComments(this.comments, node.tokenRange[1]); this.visitComments(comments, level); } if (ast.stmts.length === 0) { //empty block's comment let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]); this.visitComments(comments, level); } } visitReturnBody(ast, level) { assert.equal(ast.type, 'returnBody'); this.emit('\n'); this.visitStmts(ast.stmts, level); } visitDefaultReturnBody(level) { this.emit('\n'); this.emit('return {}\n', level); } visitFunctionBody(ast, level) { assert.equal(ast.type, 'functionBody'); this.visitStmts(ast.stmts, level); } isIterator(returnType) { if (returnType.type === 'iterator' || returnType.type === 'asyncIterator') { return true; } return false; } eachFunction(ast, level) { this.visitAnnotation(ast.annotation, level); let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]); this.visitComments(comments, level); const functionName = _name(ast.functionName); this.emit('', level); if (ast.isStatic) { this.emit('static '); } if (ast.isAsync) { this.emit('async '); } if (this.isIterator(ast.returnType)) { this.emit('*'); } this.emit(`${functionName}`); this.visitParams(ast.params, level); this.visitReturnType(ast); this.emit('{\n'); if (ast.functionBody) { this.visitFunctionBody(ast.functionBody, level + 1); } else { // interface mode this.emit(`throw new Error('Un-implemented!');\n`, level + 1); } this.emit('}\n', level); } eachAPI(ast, level) { // if (ast.annotation) { // this.emit(`${_anno(ast.annotation.value)}\n`, level); // } this.visitAnnotation(ast.annotation, level); let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]); this.visitComments(comments, level); const apiName = _name(ast.apiName); this.emit(`async ${this.isIterator(ast.returnType) ? '*' : ''}${apiName}`, level); this.visitParams(ast.params, level); this.visitReturnType(ast); this.emit('{\n'); let baseLevel = ast.runtimeBody ? level + 2 : level; // api level if (ast.runtimeBody) { this.visitRuntimeBefore(ast.runtimeBody, level + 1); } // temp level this.visitAPIBody(ast.apiBody, baseLevel + 1); this.emit(`let ${RESPONSE} = await ${CORE}.doAction(${REQUEST}`, baseLevel + 1); if (ast.runtimeBody) { this.emit(', _runtime'); } this.emit(');\n'); if (ast.runtimeBody) { this.emit(`_lastRequest = ${REQUEST};\n`, baseLevel + 1); this.emit(`_lastResponse = ${RESPONSE};\n`, baseLevel + 1); } if (ast.returns) { this.visitReturnBody(ast.returns, baseLevel + 1); } else { this.visitDefaultReturnBody(baseLevel + 1); } if (ast.runtimeBody) { this.visitRuntimeAfter(ast.runtimeBody, level + 1); } this.emit('}\n', level); } visitRuntimeAfter(ast, level) { this.emit('} catch (ex) {\n', level + 1); this.emit(`_context = new ${CORE}.RetryPolicyContext({\n`, level + 2); this.emit('retriesAttempted : _retriesAttempted,\n', level + 3); this.emit('httpRequest : _lastRequest,\n', level + 3); this.emit('httpResponse : _lastResponse,\n', level + 3); this.emit('exception : ex,\n', level + 3); this.emit(`});\n`, level + 2); this.emit('continue;\n', level + 2); this.emit('}\n', level + 1); this.emit('}\n', level); this.emit('\n'); this.emit(`throw ${CORE}.newUnretryableError(_context);\n`, level); } importBefore(level) { // Nothing } modelBefore() { _removeFilesInDirectory(this.modelPath); _removeFilesInDirectory(this.exceptionPath); } moduleBefore(ast) { if(!this.exportGen && Object.keys(this.exports).length > 0) { this.exportGen = true; Object.keys(this.exports).map(aliasId => { const tsPath = this.exports[aliasId].replace(/(\.tea)$|(\.spec)$|(\.dara)$/gi, ''); ast.innerModule.set(aliasId, `${tsPath}.ts`); this.emit(`export * as $${aliasId} from '${tsPath}';\n`); this.emit(`export { default as ${aliasId} } from '${tsPath}';\n`); }); this.emit(`\n`); } if(this.hasException) { this.emit(`import * as $_error from './${path.basename(this.exceptionPath)}/error';\n`); this.emit(`export * from './${path.basename(this.exceptionPath)}/error';\n`); } if(this.hasModel) { this.emit(`import * as $_model from './${path.basename(this.modelPath)}/model';\n`); this.emit(`export * from './${path.basename(this.modelPath)}/model';\n`); } } apiBefore(level) { this.emit(`\n`); this.emit(`export default class Client`, level); if (this.parentModule) { this.moudleUsed.push(this.parentModule.lexeme); this.emit(` extends ${this.parentModule.lexeme}`); } this.emit(` {\n`); } functionBefore() { this.emit(`\n`); } moduleAfter() { this.emit(` } `); } } module.exports = Visitor;