UNPKG

as2dts

Version:

A command line tool that converts ActionScript 3 classes and interfaces to TypeScript definitions (d.ts files)

677 lines (676 loc) 22.2 kB
"use strict"; const NodeKind = require('./nodeKind'); const KeyWords = require('./keywords'); const Node = require('./node'); //Utils function assign(target, ...items) { return items.reduce(function (target, source) { return Object.keys(source).reduce((target, key) => { target[key] = source[key]; return target; }, target); }, target); } var keywordsCheck = Object.keys(KeyWords).reduce((result, key) => { var keyword = KeyWords[key]; result[keyword] = true; return result; }, {}); function isKeyWord(text) { return !!keywordsCheck[text]; } function transformAST(node, parentNode) { //we don't care about comment var newNode = new Node(node.content(), node.kind, node.start, node.end, node.text, [], parentNode); newNode.children = node.children.filter(child => !!child && child.kind !== NodeKind.AS_DOC && child.kind !== NodeKind.MULTI_LINE_COMMENT).map(child => transformAST(child, newNode)); return newNode; } var globVars = [ 'undefined', 'NaN', 'Infinity', 'Array', 'Boolean', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'escape', 'int', 'isFinite', 'isNaN', 'isXMLName', 'Number', 'Object', 'parseFloat', 'parseInt', 'String', 'trace', 'uint', 'unescape', 'Vector', 'XML', 'XMLList', 'ArgumentError', 'arguments', 'Class', 'Date', 'DefinitionError', 'Error', 'EvalError', 'Function', 'Math', 'Namespace', 'QName', 'RangeError', 'ReferenceError', 'RegExp', 'SecurityError', 'SyntaxError', 'TypeError', 'URIError', 'VerifyError' ]; var defaultEmitterOptions = { defsOnly: false }; var data; var state; var output; function emit(ast, source, options) { data = { source: source, options: assign(defaultEmitterOptions, options || {}) }; Object.freeze(data); state = { index: 0, currentClassName: '', inConstructor: false, scope: null, isNew: false, emitThisForNextIdent: true }; output = ''; enterScope([]); visitNode(transformAST(ast, null)); catchup(data.source.length - 1); exitScope(); return output; } exports.emit = emit; function visitNodes(nodes) { if (!nodes) { return; } nodes.forEach(node => visitNode(node)); } var visitors = {}; visitors[NodeKind.PACKAGE] = emitPackage; visitors[NodeKind.META] = emitMeta; visitors[NodeKind.IMPORT] = emitImport; visitors[NodeKind.INCLUDE] = visitors[NodeKind.USE] = emitInclude; visitors[NodeKind.FUNCTION] = emitFunction; visitors[NodeKind.LAMBDA] = emitFunction; visitors[NodeKind.INTERFACE] = emitInterface; visitors[NodeKind.CLASS] = emitClass; visitors[NodeKind.VECTOR] = emitVector; visitors[NodeKind.SHORT_VECTOR] = emitShortVector; visitors[NodeKind.TYPE] = emitNodeText; visitors[NodeKind.NAME] = emitNodeText; visitors[NodeKind.EXTENDS] = emitNodeText; visitors[NodeKind.IMPLEMENTS] = emitNodeText; visitors[NodeKind.CALL] = emitCall; visitors[NodeKind.NEW] = emitNew; visitors[NodeKind.RELATION] = emitRelation; visitors[NodeKind.OP] = emitOp; visitors[NodeKind.IDENTIFIER] = emitIdent; visitors[NodeKind.XML_LITERAL] = emitXMLLiteral; visitors[NodeKind.CONST_LIST] = emitConstList; visitors[NodeKind.REST] = emitRest; function visitNode(node) { if (!node) return; var defsOnly = data.options.defsOnly; if (defsOnly && node.kind === NodeKind.CONTENT && node.parent.kind !== NodeKind.PACKAGE) { // skip anything outside a package block catchup(node.start); skipTo(node.subtreeEnd); } else if (defsOnly && node.kind === NodeKind.NAME && node.findParent(NodeKind.PARAMETER) && (node.parent.findChild(NodeKind.INIT) || state.inConstructor)) { // denote optional parameters emitNodeText(node); insert('?'); } else if (defsOnly && node.kind === NodeKind.INIT && node.parent.kind === NodeKind.NAME_TYPE_INIT) { // skip default values skipTo(node.subtreeEnd); } else if (defsOnly && node.kind === NodeKind.BLOCK && (node.parent.kind === NodeKind.FUNCTION || node.parent.kind === NodeKind.GET || node.parent.kind === NodeKind.SET)) { // skip children of function block skipTo(node.subtreeEnd); if (state.inConstructor) { if (node.findParent(NodeKind.CLASS).findChild(NodeKind.EXTENDS)) insert(' { super(); }'); else insert(' { }'); } else if (node.parent.kind === NodeKind.SET) { insert(' { }'); } else { insert(' { return null as any; }'); } } else if (visitors.hasOwnProperty(node.kind)) { visitors[node.kind](node); } else { catchup(node.start); visitNodes(node.children); } } var GLOBAL_MODULE = '__global__'; var globalExports = []; function emitRest(node) { // emit "...rest" catchup(node.end); insert(':'); // emit type visitNodes(node.children); } function emitPackage(node) { catchup(node.start); skip(NodeKind.PACKAGE.length); insert('module'); if (!node.findChild(NodeKind.NAME).text) insert(' ' + GLOBAL_MODULE); visitNodes(node.children); catchup(node.subtreeEnd); var name; while (name = globalExports.shift()) insert('\nimport ' + name + ' = ' + GLOBAL_MODULE + '.' + name + ';'); } function emitMeta(node) { catchup(node.start); commentNode(node, false); } function emitInclude(node) { catchup(node.start); commentNode(node, true); } function emitImport(node) { catchup(node.start + NodeKind.IMPORT.length + 1); var split = node.text.split('.'); var name = split[split.length - 1]; insert(name + ' = '); catchup(node.end); state.scope.declarations.push({ name: name }); } function emitInterface(node) { var exported = emitDeclaration(node); //we'll catchup the other part var name = node.findChild(NodeKind.NAME).text; state.scope.declarations.push({ name: name }); var content = node.findChild(NodeKind.CONTENT); var contentsNode = content && content.children; var foundVariables = {}; if (contentsNode) { contentsNode.forEach(node => { visitNode(node.findChild(NodeKind.META_LIST)); catchup(node.start); if (node.kind === NodeKind.FUNCTION) { skip(8); visitNodes(node.children); } else if (node.kind === NodeKind.GET || node.kind === NodeKind.SET) { var name = node.findChild(NodeKind.NAME), paramerterList = node.findChild(NodeKind.PARAMETER_LIST); if (!foundVariables[name.text]) { skipTo(name.start); catchup(name.end); foundVariables[name.text] = true; if (node.kind === NodeKind.GET) { skipTo(paramerterList.end); var type = node.findChild(NodeKind.TYPE); if (type) { emitNodeText(type); } } else if (node.kind === NodeKind.SET) { var setParam = paramerterList .findChild(NodeKind.PARAMETER).children[0]; skipTo(setParam.findChild(NodeKind.NAME).end); var type = setParam.findChild(NodeKind.TYPE); if (type) { emitNodeText(type); } skipTo(node.end); } } else { commentNode(node, true); } } else { //include or import in interface content not supported commentNode(node, true); } }); } catchup(node.subtreeEnd); if (exported) insert('\nexport var ' + name + ':new(..._:any[])=>' + name + ';'); } function emitFunction(node) { emitDeclaration(node); enterFunctionScope(node); var rest = node.getChildrenStartingFrom(NodeKind.MOD_LIST); exitScope(); visitNodes(rest); } function emitClass(node) { emitDeclaration(node); var name = node.findChild(NodeKind.NAME); state.currentClassName = name.text.split('<')[0]; visitNode(name); var content = node.findChild(NodeKind.CONTENT); var contentsNode = content && content.children; if (contentsNode) { //collects declarations enterClassScope(contentsNode); contentsNode.forEach(node => { visitNode(node.findChild(NodeKind.META_LIST)); catchup(node.start); switch (node.kind) { case NodeKind.SET: emitSet(node); break; case NodeKind.GET: case NodeKind.FUNCTION: emitMethod(node); break; case NodeKind.VAR_LIST: emitPropertyDecl(node); break; case NodeKind.CONST_LIST: emitPropertyDecl(node, true); break; default: visitNode(node); } }); exitScope(); } state.currentClassName = null; } function emitSet(node) { if (!emitClassField(node)) { skipTo(node.subtreeEnd); return; } var name = node.findChild(NodeKind.NAME); consume('function', name.start); var params = node.findChild(NodeKind.PARAMETER_LIST); visitNode(params); catchup(params.end); var type = node.findChild(NodeKind.TYPE); if (type) { skipTo(type.end); } enterFunctionScope(node); visitNodes(node.getChildrenStartingFrom(NodeKind.TYPE)); exitScope(); } function emitConstList(node) { catchup(node.start); var nameTypeInit = node.findChild(NodeKind.NAME_TYPE_INIT); skipTo(nameTypeInit.start); insert('var '); visitNode(nameTypeInit); } function emitMethod(node) { var name = node.findChild(NodeKind.NAME); state.inConstructor = node.kind === NodeKind.FUNCTION && name.text === state.currentClassName; if (state.inConstructor) { var mods = node.findChild(NodeKind.MOD_LIST); if (mods) { catchup(mods.start); } insert('constructor'); skipTo(name.end); } else if (emitClassField(node)) { consume('function', name.start); emitNodeText(name); } else { skipTo(node.subtreeEnd); return; } enterFunctionScope(node); visitNodes(node.getChildrenStartingFrom(NodeKind.NAME)); exitScope(); state.inConstructor = false; } function emitPropertyDecl(node, isConst = false) { var names = node.findChildren(NodeKind.NAME_TYPE_INIT); if (!emitClassField(node)) { skipTo(node.subtreeEnd); return; } for (var i = 0; i < names.length; i++) { var name = names[i]; if (i == 0) { consume(isConst ? 'const' : 'var', name.start); visitNode(name); } else { insert('; '); emitClassField(node, true); skipTo(name.start); visitNode(name); } } } function emitClassField(node, again = false) { var mods = node.findChild(NodeKind.MOD_LIST); if (mods) { catchup(mods.start); if (data.options.defsOnly && (mods.findChild(NodeKind.MODIFIER, 'private') || mods.findChild(NodeKind.MODIFIER, 'internal'))) { return false; } mods.children.forEach(node => { catchup(node.start); if (node.text === 'private' || node.text === 'public' || node.text === 'protected' || node.text === 'static') { if (again) insert(node.text + ' '); } else { commentNode(node, false); } catchup(node.end); }); } return true; } function emitDeclaration(node) { catchup(node.start); visitNode(node.findChild(NodeKind.META_LIST)); var mods = node.findChild(NodeKind.MOD_LIST); if (mods && mods.children.length) { catchup(mods.start); var insertExport = false; mods.children.forEach(node => { if (node.text !== 'private') { insertExport = true; } skipTo(node.end); }); if (insertExport) { insert('export'); var pkg = node.findParent(NodeKind.PACKAGE); if (pkg && !pkg.findChild(NodeKind.NAME).text) globalExports.push(node.findChild(NodeKind.NAME).text); } } return insertExport; } var typeMapping = { 'Array': 'any[]', 'Boolean': 'boolean', 'Class': 'new(..._:any[])=>any', 'int': 'number', 'Number': 'number', 'String': 'string', 'uint': 'number', '*': 'any' }; function emitNodeText(node) { catchup(node.start); skipTo(node.end); insert(' '); insert(typeMapping.hasOwnProperty(node.text) ? typeMapping[node.text] : node.text); insert(' '); } function emitVector(node) { catchup(node.start); var type = node.findChild(NodeKind.TYPE); if (type) { skipTo(type.start); emitNodeText(type); insert('[]'); } else { insert('any[]'); } skipTo(node.end); } function emitShortVector(node) { catchup(node.start); var vector = node.findChild(NodeKind.VECTOR); insert('Array'); var type = vector.findChild(NodeKind.TYPE); if (type) { emitNodeText(type); } else { insert('any'); } catchup(vector.end); insert('('); var arrayLiteral = node.findChild(NodeKind.ARRAY); if (arrayLiteral.children && arrayLiteral.children.length) { skipTo(arrayLiteral.children[0].start); visitNodes(arrayLiteral.children); catchup(arrayLiteral.lastChild.end); } insert(')'); skipTo(node.end); } function emitNew(node) { catchup(node.start); state.isNew = true; state.emitThisForNextIdent = false; visitNodes(node.children); state.isNew = false; } function emitCall(node) { catchup(node.start); var isNew = state.isNew; state.isNew = false; if (node.children[0].kind === NodeKind.VECTOR) { if (isNew) { var vector = node.children[0]; catchup(vector.start); insert('Array'); insert('<'); var type = vector.findChild(NodeKind.TYPE); if (type) { skipTo(type.start); emitNodeText(type); } else { insert('any'); } skipTo(vector.end); insert('>'); visitNodes(node.getChildrenStartingFrom(NodeKind.VECTOR)); return; } var args = node.findChild(NodeKind.ARGUMENTS); //vector conversion lets just cast to array if (args.children.length === 1) { insert('(<'); emitVector(node.children[0]); insert('>'); skipTo(args.children[0].start); visitNode(args.children[0]); catchup(node.end); return; } } visitNodes(node.children); } function emitRelation(node) { catchup(node.start); var as = node.findChild(NodeKind.AS); if (as) { if (node.lastChild.kind === NodeKind.IDENTIFIER) { insert('<'); insert(node.lastChild.text); insert('>'); visitNodes(node.getChildrenUntil(NodeKind.AS)); catchup(as.start); skipTo(node.end); } else { commentNode(node, false); } return; } visitNodes(node.children); } function emitOp(node) { catchup(node.start); if (node.text === "is") { insert('instanceof'); skipTo(node.end); return; } catchup(node.end); } function emitIdent(node) { catchup(node.start); if (node.parent && node.parent.kind === NodeKind.DOT) { //in case of dot just check the first if (node.parent.children[0] !== node) { return; } } if (isKeyWord(node.text)) { return; } var def = findDefInScope(node.text); if (def && def.bound) { insert(def.bound + '.'); } if (!def && state.currentClassName && globVars.indexOf(node.text) === -1 && state.emitThisForNextIdent && node.text !== state.currentClassName) { insert('this.'); } state.emitThisForNextIdent = true; } function emitXMLLiteral(node) { catchup(node.start); insert(JSON.stringify(node.text)); skipTo(node.end); } function enterClassScope(contentsNode) { var found = {}; var declarations = contentsNode.map(node => { var nameNode; var isStatic; switch (node.kind) { case NodeKind.SET: case NodeKind.GET: case NodeKind.FUNCTION: nameNode = node.findChild(NodeKind.NAME); break; case NodeKind.VAR_LIST: case NodeKind.CONST_LIST: nameNode = node.findChild(NodeKind.NAME_TYPE_INIT).findChild(NodeKind.NAME); break; } if (!nameNode || found[nameNode.text]) { return null; } found[nameNode.text] = true; if (nameNode.text === state.currentClassName) { return; } var modList = node.findChild(NodeKind.MOD_LIST); var isStatic = modList && modList.children.some(mod => mod.text === 'static'); return { name: nameNode.text, bound: isStatic ? state.currentClassName : 'this' }; }).filter(el => !!el); enterScope(declarations); } function enterFunctionScope(node) { var decls = []; var params = node.findChild(NodeKind.PARAMETER_LIST); if (params && params.children.length) { decls = params.children.map(param => { var nameTypeInit = param.findChild(NodeKind.NAME_TYPE_INIT); if (nameTypeInit) { return { name: nameTypeInit.findChild(NodeKind.NAME).text }; } var rest = param.findChild(NodeKind.REST); return { name: rest.text }; }); } var block = node.findChild(NodeKind.BLOCK); if (block) { function traverse(node) { var result = new Array(); if (node.kind === NodeKind.VAR_LIST || node.kind === NodeKind.CONST_LIST || node.kind === NodeKind.VAR || node.kind === NodeKind.CONST) { result = result.concat(node .findChildren(NodeKind.NAME_TYPE_INIT) .map(node => ({ name: node.findChild(NodeKind.NAME).text }))); } if (node.kind !== NodeKind.FUNCTION && node.children && node.children.length) { result = Array.prototype.concat.apply(result, node.children.map(traverse)); } return result.filter(decl => !!decl); } decls = decls.concat(traverse(block)); } enterScope(decls); } function enterScope(decls) { state.scope = { parent: state.scope, declarations: decls, get isTopLevel() { return !state.scope; } }; } function exitScope() { state.scope = state.scope && state.scope.parent; } function findDefInScope(text) { var scope = state.scope; while (scope) { for (var i = 0; i < scope.declarations.length; i++) { if (scope.declarations[i].name === text) { return scope.declarations[i]; } } scope = scope.parent; } return null; } function commentNode(node, catchSemi) { insert('/*'); var start = output.length; catchup(node.end); var index = state.index; if (catchSemi) { while (true) { if (index >= data.source.length) { break; } if (data.source[index] === '\n') { catchup(index); break; } if (data.source[index] === ';') { catchup(index + 1); break; } index++; } } // disable nested block comments output = output.substr(0, start) + output.substr(start).split('*').join(' * '); insert('*/'); } function catchup(index) { if (state.index > index) { return; } while (state.index !== index) { output += data.source[state.index]; state.index++; } } function skipTo(index) { if (index < 0) state.index = data.source.length; else state.index = index; } function skip(number) { state.index += number; } function insert(string) { output += string; } function consume(string, limit) { var index = data.source.indexOf(string, state.index) + string.length; if (index > limit || index < state.index) { console.log("started searching at: " + data.source.substr(state.index)); console.log('output:', output); throw new Error('invalid consume ' + JSON.stringify({ string: string, index: index, limit: limit, "state": state }, null, 3)); } state.index = index; }