UNPKG

ts2dart

Version:

Transpile TypeScript code to Dart

535 lines (533 loc) 23.2 kB
"use strict"; var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var ts = require('typescript'); var base = require('./base'); var DeclarationTranspiler = (function (_super) { __extends(DeclarationTranspiler, _super); function DeclarationTranspiler(tr, fc, enforceUnderscoreConventions) { _super.call(this, tr); this.fc = fc; this.enforceUnderscoreConventions = enforceUnderscoreConventions; } DeclarationTranspiler.prototype.visitNode = function (node) { switch (node.kind) { case ts.SyntaxKind.VariableDeclarationList: // Note: VariableDeclarationList can only occur as part of a for loop. var varDeclList = node; this.visitList(varDeclList.declarations); break; case ts.SyntaxKind.VariableDeclaration: var varDecl = node; this.visitVariableDeclarationType(varDecl); this.visit(varDecl.name); if (varDecl.initializer) { this.emit('='); this.visit(varDecl.initializer); } break; case ts.SyntaxKind.ClassDeclaration: var classDecl = node; if (classDecl.modifiers && (classDecl.modifiers.flags & ts.NodeFlags.Abstract)) { this.visitClassLike('abstract class', classDecl); } else { this.visitClassLike('class', classDecl); } break; case ts.SyntaxKind.InterfaceDeclaration: var ifDecl = node; // Function type interface in an interface with a single declaration // of a call signature (http://goo.gl/ROC5jN). if (ifDecl.members.length === 1 && ifDecl.members[0].kind === ts.SyntaxKind.CallSignature) { var member_1 = ifDecl.members[0]; this.visitFunctionTypedefInterface(ifDecl.name.text, member_1, ifDecl.typeParameters); } else { this.visitClassLike('abstract class', ifDecl); } break; case ts.SyntaxKind.HeritageClause: var heritageClause = node; if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword && heritageClause.parent.kind !== ts.SyntaxKind.InterfaceDeclaration) { this.emit('extends'); } else { this.emit('implements'); } // Can only have one member for extends clauses. this.visitList(heritageClause.types); break; case ts.SyntaxKind.ExpressionWithTypeArguments: var exprWithTypeArgs = node; this.visit(exprWithTypeArgs.expression); this.maybeVisitTypeArguments(exprWithTypeArgs); break; case ts.SyntaxKind.EnumDeclaration: var decl = node; // The only legal modifier for an enum decl is const. var isConst = decl.modifiers && (decl.modifiers.flags & ts.NodeFlags.Const); if (isConst) { this.reportError(node, 'const enums are not supported'); } this.emit('enum'); this.fc.visitTypeName(decl.name); this.emit('{'); // Enums can be empty in TS ... if (decl.members.length === 0) { // ... but not in Dart. this.reportError(node, 'empty enums are not supported'); } this.visitList(decl.members); this.emit('}'); break; case ts.SyntaxKind.EnumMember: var member = node; this.visit(member.name); if (member.initializer) { this.reportError(node, 'enum initializers are not supported'); } break; case ts.SyntaxKind.Constructor: var ctorDecl = node; // Find containing class name. var className = void 0; for (var parent_1 = ctorDecl.parent; parent_1; parent_1 = parent_1.parent) { if (parent_1.kind === ts.SyntaxKind.ClassDeclaration) { className = parent_1.name; break; } } if (!className) this.reportError(ctorDecl, 'cannot find outer class node'); this.visitDeclarationMetadata(ctorDecl); if (this.fc.isConstClass(ctorDecl.parent)) { this.emit('const'); } this.visit(className); this.visitParameters(ctorDecl.parameters); this.visit(ctorDecl.body); break; case ts.SyntaxKind.PropertyDeclaration: this.visitProperty(node); break; case ts.SyntaxKind.SemicolonClassElement: // No-op, don't emit useless declarations. break; case ts.SyntaxKind.MethodDeclaration: this.visitDeclarationMetadata(node); this.visitFunctionLike(node); break; case ts.SyntaxKind.GetAccessor: this.visitDeclarationMetadata(node); this.visitFunctionLike(node, 'get'); break; case ts.SyntaxKind.SetAccessor: this.visitDeclarationMetadata(node); this.visitFunctionLike(node, 'set'); break; case ts.SyntaxKind.FunctionDeclaration: var funcDecl = node; this.visitDecorators(funcDecl.decorators); this.visitFunctionLike(funcDecl); break; case ts.SyntaxKind.ArrowFunction: var arrowFunc = node; // Dart only allows expressions following the fat arrow operator. // If the body is a block, we have to drop the fat arrow and emit an // anonymous function instead. if (arrowFunc.body.kind === ts.SyntaxKind.Block) { this.visitFunctionLike(arrowFunc); } else { this.visitParameters(arrowFunc.parameters); this.emit('=>'); this.visit(arrowFunc.body); } break; case ts.SyntaxKind.FunctionExpression: var funcExpr = node; this.visitFunctionLike(funcExpr); break; case ts.SyntaxKind.PropertySignature: var propSig = node; this.visitProperty(propSig); break; case ts.SyntaxKind.MethodSignature: var methodSignatureDecl = node; this.visitEachIfPresent(methodSignatureDecl.modifiers); this.visitFunctionLike(methodSignatureDecl); break; case ts.SyntaxKind.Parameter: var paramDecl = node; // Property parameters will have an explicit property declaration, so we just // need the dart assignment shorthand to reference the property. if (this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Public) || this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Private) || this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Protected)) { this.visitDeclarationMetadata(paramDecl); this.emit('this .'); this.visit(paramDecl.name); if (paramDecl.initializer) { this.emit('='); this.visit(paramDecl.initializer); } break; } if (paramDecl.dotDotDotToken) this.reportError(node, 'rest parameters are unsupported'); if (paramDecl.name.kind === ts.SyntaxKind.ObjectBindingPattern) { this.visitNamedParameter(paramDecl); break; } this.visitDecorators(paramDecl.decorators); if (paramDecl.type && paramDecl.type.kind === ts.SyntaxKind.FunctionType) { // Dart uses "returnType paramName ( parameters )" syntax. var fnType = paramDecl.type; var hasRestParameter = fnType.parameters.some(function (p) { return !!p.dotDotDotToken; }); if (hasRestParameter) { // Dart does not support rest parameters/varargs, degenerate to just "Function". this.emit('Function'); this.visit(paramDecl.name); } else { this.visit(fnType.type); this.visit(paramDecl.name); this.visitParameters(fnType.parameters); } } else { if (paramDecl.type) this.visit(paramDecl.type); this.visit(paramDecl.name); } if (paramDecl.initializer) { this.emit('='); this.visit(paramDecl.initializer); } break; case ts.SyntaxKind.StaticKeyword: this.emit('static'); break; case ts.SyntaxKind.AbstractKeyword: // Abstract methods in Dart simply lack implementation, // and don't use the 'abstract' modifier // Abstract classes are handled in `case ts.SyntaxKind.ClassDeclaration` above. break; case ts.SyntaxKind.PrivateKeyword: // no-op, handled through '_' naming convention in Dart. break; case ts.SyntaxKind.PublicKeyword: // Handled in `visitDeclarationMetadata` below. break; case ts.SyntaxKind.ProtectedKeyword: // Handled in `visitDeclarationMetadata` below. break; default: return false; } return true; }; DeclarationTranspiler.prototype.visitVariableDeclarationType = function (varDecl) { /* Note: VariableDeclarationList can only occur as part of a for loop. This helper method * is meant for processing for-loop variable declaration types only. * * In Dart, all variables in a variable declaration list must have the same type. Since * we are doing syntax directed translation, we cannot reliably determine if distinct * variables are declared with the same type or not. Hence we support the following cases: * * - A variable declaration list with a single variable can be explicitly typed. * - When more than one variable is in the list, all must be implicitly typed. */ var firstDecl = varDecl.parent.declarations[0]; var msg = 'Variables in a declaration list of more than one variable cannot by typed'; var isFinal = this.hasFlag(varDecl.parent, ts.NodeFlags.Const); var isConst = false; if (isFinal && varDecl.initializer) { // "const" in TypeScript/ES6 corresponds to "final" in Dart, i.e. reference constness. // If a "const" variable is immediately initialized to a CONST_EXPR(), special case it to be // a deeply const constant, and generate "const ...". isConst = varDecl.initializer.kind === ts.SyntaxKind.StringLiteral || varDecl.initializer.kind === ts.SyntaxKind.NumericLiteral || this.fc.isConstExpr(varDecl.initializer); } if (firstDecl === varDecl) { if (isConst) { this.emit('const'); } else if (isFinal) { this.emit('final'); } if (!varDecl.type) { if (!isFinal) this.emit('var'); } else if (varDecl.parent.declarations.length > 1) { this.reportError(varDecl, msg); } else { this.visit(varDecl.type); } } else if (varDecl.type) { this.reportError(varDecl, msg); } }; DeclarationTranspiler.prototype.visitFunctionLike = function (fn, accessor) { this.fc.pushTypeParameterNames(fn); try { if (fn.type) { if (fn.kind === ts.SyntaxKind.ArrowFunction || fn.kind === ts.SyntaxKind.FunctionExpression) { // The return type is silently dropped for function expressions (including arrow // functions), it is not supported in Dart. this.emit('/*'); this.visit(fn.type); this.emit('*/'); } else { this.visit(fn.type); } } if (accessor) this.emit(accessor); if (fn.name) this.visit(fn.name); if (fn.typeParameters) { this.emit('/*<'); // Emit the names literally instead of visiting, otherwise they will be replaced with the // comment hack themselves. this.emit(fn.typeParameters.map(function (p) { return base.ident(p.name); }).join(', ')); this.emit('>*/'); } // Dart does not even allow the parens of an empty param list on getter if (accessor !== 'get') { this.visitParameters(fn.parameters); } else { if (fn.parameters && fn.parameters.length > 0) { this.reportError(fn, 'getter should not accept parameters'); } } if (fn.body) { this.visit(fn.body); } else { this.emit(';'); } } finally { this.fc.popTypeParameterNames(fn); } }; DeclarationTranspiler.prototype.visitParameters = function (parameters) { this.emit('('); var firstInitParamIdx = 0; for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { // ObjectBindingPatterns are handled within the parameter visit. var isOpt = parameters[firstInitParamIdx].initializer || parameters[firstInitParamIdx].questionToken; if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.ObjectBindingPattern) { break; } } if (firstInitParamIdx !== 0) { var requiredParams = parameters.slice(0, firstInitParamIdx); this.visitList(requiredParams); } if (firstInitParamIdx !== parameters.length) { if (firstInitParamIdx !== 0) this.emit(','); var positionalOptional = parameters.slice(firstInitParamIdx, parameters.length); this.emit('['); this.visitList(positionalOptional); this.emit(']'); } this.emit(')'); }; /** * Visit a property declaration. * In the special case of property parameters in a constructor, we also allow a parameter to be * emitted as a property. */ DeclarationTranspiler.prototype.visitProperty = function (decl, isParameter) { if (isParameter === void 0) { isParameter = false; } if (!isParameter) this.visitDeclarationMetadata(decl); var containingClass = (isParameter ? decl.parent.parent : decl.parent); var isConstField = this.fc.hasConstComment(decl) || this.hasAnnotation(decl.decorators, 'CONST'); var hasConstCtor = this.fc.isConstClass(containingClass); if (isConstField) { // const implies final this.emit('const'); } else { if (hasConstCtor) { this.emit('final'); } } if (decl.type) { this.visit(decl.type); } else if (!isConstField && !hasConstCtor) { this.emit('var'); } this.visit(decl.name); if (decl.initializer && !isParameter) { this.emit('='); this.visit(decl.initializer); } this.emit(';'); }; DeclarationTranspiler.prototype.visitClassLike = function (keyword, decl) { var _this = this; this.visitDecorators(decl.decorators); this.emit(keyword); this.fc.visitTypeName(decl.name); if (decl.typeParameters) { this.emit('<'); this.visitList(decl.typeParameters); this.emit('>'); } this.visitEachIfPresent(decl.heritageClauses); this.emit('{'); // Synthesize explicit properties for ctor with 'property parameters' var synthesizePropertyParam = function (param) { if (_this.hasFlag(param.modifiers, ts.NodeFlags.Public) || _this.hasFlag(param.modifiers, ts.NodeFlags.Private) || _this.hasFlag(param.modifiers, ts.NodeFlags.Protected)) { // TODO: we should enforce the underscore prefix on privates _this.visitProperty(param, true); } }; decl.members .filter(function (m) { return m.kind === ts.SyntaxKind.Constructor; }) .forEach(function (ctor) { return ctor.parameters.forEach(synthesizePropertyParam); }); this.visitEachIfPresent(decl.members); // Generate a constructor to host the const modifier, if needed if (this.fc.isConstClass(decl) && !decl.members .some(function (m) { return m.kind === ts.SyntaxKind.Constructor; })) { this.emit('const'); this.fc.visitTypeName(decl.name); this.emit('();'); } this.emit('}'); }; DeclarationTranspiler.prototype.visitDecorators = function (decorators) { var _this = this; if (!decorators) return; decorators.forEach(function (d) { // Special case @CONST var name = base.ident(d.expression); if (!name && d.expression.kind === ts.SyntaxKind.CallExpression) { // Unwrap @CONST() var callExpr = d.expression; name = base.ident(callExpr.expression); } // Make sure these match IGNORED_ANNOTATIONS below. if (name === 'CONST') { // Ignore @CONST - it is handled above in visitClassLike. return; } _this.emit('@'); _this.visit(d.expression); }); }; DeclarationTranspiler.prototype.visitDeclarationMetadata = function (decl) { this.visitDecorators(decl.decorators); this.visitEachIfPresent(decl.modifiers); if (this.hasFlag(decl.modifiers, ts.NodeFlags.Protected)) { this.reportError(decl, 'protected declarations are unsupported'); return; } if (!this.enforceUnderscoreConventions) return; // Early return in case this is a decl with no name, such as a constructor if (!decl.name) return; var name = base.ident(decl.name); if (!name) return; var isPrivate = this.hasFlag(decl.modifiers, ts.NodeFlags.Private); var matchesPrivate = !!name.match(/^_/); if (isPrivate && !matchesPrivate) { this.reportError(decl, 'private members must be prefixed with "_"'); } if (!isPrivate && matchesPrivate) { this.reportError(decl, 'public members must not be prefixed with "_"'); } }; DeclarationTranspiler.prototype.visitNamedParameter = function (paramDecl) { this.visitDecorators(paramDecl.decorators); var bp = paramDecl.name; var propertyTypes = this.fc.resolvePropertyTypes(paramDecl.type); var initMap = this.getInitializers(paramDecl); this.emit('{'); for (var i = 0; i < bp.elements.length; i++) { var elem = bp.elements[i]; var propDecl = propertyTypes[base.ident(elem.name)]; if (propDecl && propDecl.type) this.visit(propDecl.type); this.visit(elem.name); if (elem.initializer && initMap[base.ident(elem.name)]) { this.reportError(elem, 'cannot have both an inner and outer initializer'); } var init = elem.initializer || initMap[base.ident(elem.name)]; if (init) { this.emit(':'); this.visit(init); } if (i + 1 < bp.elements.length) this.emit(','); } this.emit('}'); }; DeclarationTranspiler.prototype.getInitializers = function (paramDecl) { var res = {}; if (!paramDecl.initializer) return res; if (paramDecl.initializer.kind !== ts.SyntaxKind.ObjectLiteralExpression) { this.reportError(paramDecl, 'initializers for named parameters must be object literals'); return res; } for (var _i = 0, _a = paramDecl.initializer.properties; _i < _a.length; _i++) { var i = _a[_i]; if (i.kind !== ts.SyntaxKind.PropertyAssignment) { this.reportError(i, 'named parameter initializers must be properties, got ' + i.kind); continue; } var ole = i; res[base.ident(ole.name)] = ole.initializer; } return res; }; /** * Handles a function typedef-like interface, i.e. an interface that only declares a single * call signature, by translating to a Dart `typedef`. */ DeclarationTranspiler.prototype.visitFunctionTypedefInterface = function (name, signature, typeParameters) { this.emit('typedef'); if (signature.type) { this.visit(signature.type); } this.emit(name); if (typeParameters) { this.emit('<'); this.visitList(typeParameters); this.emit('>'); } this.visitParameters(signature.parameters); this.emit(';'); }; return DeclarationTranspiler; }(base.TranspilerBase)); exports.__esModule = true; exports["default"] = DeclarationTranspiler; //# sourceMappingURL=declaration.js.map